Change how HTTP/2 frames are parsed and generated #2858
This commit is contained in:
parent
2999aa54cd
commit
b8423b8530
|
|
@ -572,13 +572,13 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
|||
<data name="GreaterThanZeroRequired" xml:space="preserve">
|
||||
<value>A value greater than zero is required.</value>
|
||||
</data>
|
||||
<data name="Http2FrameMissingFields" xml:space="preserve">
|
||||
<value>The frame is too short to contain the fields indicated by the given flags.</value>
|
||||
</data>
|
||||
<data name="ArgumentOutOfRange" xml:space="preserve">
|
||||
<value>A value between {min} and {max} is required.</value>
|
||||
</data>
|
||||
<data name="HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock" xml:space="preserve">
|
||||
<value>Dynamic tables size update did not occur at the beginning of the first header block.</value>
|
||||
</data>
|
||||
<data name="HPackErrorNotEnoughBuffer" xml:space="preserve">
|
||||
<value>The given buffer was too small to encode any headers.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
||||
|
|
@ -109,11 +110,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
_dynamicTable = dynamicTable;
|
||||
}
|
||||
|
||||
public void Decode(Span<byte> data, bool endHeaders, IHttpHeadersHandler handler)
|
||||
public void Decode(ReadOnlySequence<byte> data, bool endHeaders, IHttpHeadersHandler handler)
|
||||
{
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
foreach (var segment in data)
|
||||
{
|
||||
OnByte(data[i], handler);
|
||||
var span = segment.Span;
|
||||
for (var i = 0; i < span.Length; i++)
|
||||
{
|
||||
OnByte(span[i], handler);
|
||||
}
|
||||
}
|
||||
|
||||
if (endHeaders)
|
||||
|
|
|
|||
|
|
@ -24,13 +24,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
_enumerator.MoveNext();
|
||||
|
||||
var statusCodeLength = EncodeStatusCode(statusCode, buffer);
|
||||
var done = Encode(buffer.Slice(statusCodeLength), out var headersLength);
|
||||
var done = Encode(buffer.Slice(statusCodeLength), throwIfNoneEncoded: false, out var headersLength);
|
||||
length = statusCodeLength + headersLength;
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
public bool Encode(Span<byte> buffer, out int length)
|
||||
{
|
||||
return Encode(buffer, throwIfNoneEncoded: true, out length);
|
||||
}
|
||||
|
||||
private bool Encode(Span<byte> buffer, bool throwIfNoneEncoded, out int length)
|
||||
{
|
||||
length = 0;
|
||||
|
||||
|
|
@ -38,6 +43,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack
|
|||
{
|
||||
if (!EncodeHeader(_enumerator.Current.Key, _enumerator.Current.Value, buffer.Slice(length), out var headerLength))
|
||||
{
|
||||
if (length == 0 && throwIfNoneEncoded)
|
||||
{
|
||||
throw new HPackEncodingException(CoreStrings.HPackErrorNotEnoughBuffer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
|
||||
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
|
||||
|
||||
private readonly Http2Frame _incomingFrame;
|
||||
private readonly Http2Frame _incomingFrame = new Http2Frame();
|
||||
|
||||
private Http2Stream _currentHeadersStream;
|
||||
private RequestHeaderParsingState _requestHeaderParsingState;
|
||||
|
|
@ -92,7 +92,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_serverSettings.MaxFrameSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.MaxFrameSize;
|
||||
_serverSettings.HeaderTableSize = (uint)context.ServiceContext.ServerOptions.Limits.Http2.HeaderTableSize;
|
||||
_hpackDecoder = new HPackDecoder((int)_serverSettings.HeaderTableSize);
|
||||
_incomingFrame = new Http2Frame(_serverSettings.MaxFrameSize);
|
||||
_serverSettings.MaxHeaderListSize = (uint)context.ServiceContext.ServerOptions.Limits.MaxRequestHeadersTotalSize;
|
||||
}
|
||||
|
||||
|
|
@ -188,16 +187,21 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
var result = await Input.ReadAsync();
|
||||
var readableBuffer = result.Buffer;
|
||||
var consumed = readableBuffer.Start;
|
||||
var examined = readableBuffer.End;
|
||||
var examined = readableBuffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (!readableBuffer.IsEmpty)
|
||||
{
|
||||
if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out consumed, out examined))
|
||||
if (Http2FrameReader.ReadFrame(readableBuffer, _incomingFrame, _serverSettings.MaxFrameSize, out var framePayload))
|
||||
{
|
||||
Log.Http2FrameReceived(ConnectionId, _incomingFrame);
|
||||
await ProcessFrameAsync(application);
|
||||
consumed = examined = framePayload.End;
|
||||
await ProcessFrameAsync(application, framePayload);
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = readableBuffer.End;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -372,7 +376,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return true;
|
||||
}
|
||||
|
||||
private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application)
|
||||
private Task ProcessFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1.1
|
||||
// Streams initiated by a client MUST use odd-numbered stream identifiers; ...
|
||||
|
|
@ -386,31 +390,31 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
switch (_incomingFrame.Type)
|
||||
{
|
||||
case Http2FrameType.DATA:
|
||||
return ProcessDataFrameAsync();
|
||||
return ProcessDataFrameAsync(payload);
|
||||
case Http2FrameType.HEADERS:
|
||||
return ProcessHeadersFrameAsync(application);
|
||||
return ProcessHeadersFrameAsync(application, payload);
|
||||
case Http2FrameType.PRIORITY:
|
||||
return ProcessPriorityFrameAsync();
|
||||
case Http2FrameType.RST_STREAM:
|
||||
return ProcessRstStreamFrameAsync();
|
||||
case Http2FrameType.SETTINGS:
|
||||
return ProcessSettingsFrameAsync();
|
||||
return ProcessSettingsFrameAsync(payload);
|
||||
case Http2FrameType.PUSH_PROMISE:
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2ErrorPushPromiseReceived, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
case Http2FrameType.PING:
|
||||
return ProcessPingFrameAsync();
|
||||
return ProcessPingFrameAsync(payload);
|
||||
case Http2FrameType.GOAWAY:
|
||||
return ProcessGoAwayFrameAsync();
|
||||
case Http2FrameType.WINDOW_UPDATE:
|
||||
return ProcessWindowUpdateFrameAsync();
|
||||
case Http2FrameType.CONTINUATION:
|
||||
return ProcessContinuationFrameAsync(application);
|
||||
return ProcessContinuationFrameAsync(application, payload);
|
||||
default:
|
||||
return ProcessUnknownFrameAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessDataFrameAsync()
|
||||
private Task ProcessDataFrameAsync(ReadOnlySequence<byte> payload)
|
||||
{
|
||||
if (_currentHeadersStream != null)
|
||||
{
|
||||
|
|
@ -422,12 +426,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
// The Padding field is missing
|
||||
if (_incomingFrame.DataPayloadOffset > _incomingFrame.PayloadLength)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2FrameMissingFields, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
if (_incomingFrame.DataHasPadding && _incomingFrame.DataPadLength >= _incomingFrame.PayloadLength)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
|
||||
|
|
@ -449,7 +447,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamHalfClosedRemote(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||
}
|
||||
|
||||
return stream.OnDataAsync(_incomingFrame);
|
||||
return stream.OnDataAsync(_incomingFrame, payload);
|
||||
}
|
||||
|
||||
// If we couldn't find the stream, it was either alive previously but closed with
|
||||
|
|
@ -466,7 +464,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamClosed(_incomingFrame.Type, _incomingFrame.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||
}
|
||||
|
||||
private async Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application)
|
||||
private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
if (_currentHeadersStream != null)
|
||||
{
|
||||
|
|
@ -478,12 +476,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamIdZero(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
// Padding or priority fields are missing
|
||||
if (_incomingFrame.HeadersPayloadOffset > _incomingFrame.PayloadLength)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.Http2FrameMissingFields, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
if (_incomingFrame.HeadersHasPadding && _incomingFrame.HeadersPadLength >= _incomingFrame.PayloadLength - 1)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorPaddingTooLong(_incomingFrame.Type), Http2ErrorCode.PROTOCOL_ERROR);
|
||||
|
|
@ -518,7 +510,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_currentHeadersStream = stream;
|
||||
_requestHeaderParsingState = RequestHeaderParsingState.Trailers;
|
||||
|
||||
await DecodeTrailersAsync(_incomingFrame.HeadersEndHeaders, _incomingFrame.HeadersPayload);
|
||||
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
|
||||
return DecodeTrailersAsync(_incomingFrame.HeadersEndHeaders, headersPayload);
|
||||
}
|
||||
else if (_incomingFrame.StreamId <= _highestOpenedStreamId)
|
||||
{
|
||||
|
|
@ -554,7 +547,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_currentHeadersStream.Reset();
|
||||
_headerFlags = _incomingFrame.HeadersFlags;
|
||||
|
||||
await DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, _incomingFrame.HeadersPayload);
|
||||
var headersPayload = payload.Slice(0, _incomingFrame.HeadersPayloadLength); // Minus padding
|
||||
return DecodeHeadersAsync(application, _incomingFrame.HeadersEndHeaders, headersPayload);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -606,7 +600,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ProcessSettingsFrameAsync()
|
||||
private Task ProcessSettingsFrameAsync(ReadOnlySequence<byte> payload)
|
||||
{
|
||||
if (_currentHeadersStream != null)
|
||||
{
|
||||
|
|
@ -639,14 +633,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
var previousInitialWindowSize = (int)_clientSettings.InitialWindowSize;
|
||||
var previousMaxFrameSize = _clientSettings.MaxFrameSize;
|
||||
|
||||
_clientSettings.Update(_incomingFrame.GetSettings());
|
||||
_clientSettings.Update(Http2FrameReader.ReadSettings(payload));
|
||||
|
||||
// Ack before we update the windows, they could send data immediately.
|
||||
var ackTask = _frameWriter.WriteSettingsAckAsync();
|
||||
var ackTask = _frameWriter.WriteSettingsAckAsync();
|
||||
|
||||
if (_clientSettings.MaxFrameSize != previousMaxFrameSize)
|
||||
{
|
||||
_frameWriter.UpdateMaxFrameSize(_clientSettings.MaxFrameSize);
|
||||
// Don't let the client choose an arbitrarily large size, this will be used for response buffers.
|
||||
_frameWriter.UpdateMaxFrameSize(Math.Min(_clientSettings.MaxFrameSize, _serverSettings.MaxFrameSize));
|
||||
}
|
||||
|
||||
// This difference can be negative.
|
||||
|
|
@ -676,7 +671,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
}
|
||||
}
|
||||
|
||||
private Task ProcessPingFrameAsync()
|
||||
private Task ProcessPingFrameAsync(ReadOnlySequence<byte> payload)
|
||||
{
|
||||
if (_currentHeadersStream != null)
|
||||
{
|
||||
|
|
@ -699,7 +694,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return _frameWriter.WritePingAsync(Http2PingFrameFlags.ACK, _incomingFrame.Payload);
|
||||
return _frameWriter.WritePingAsync(Http2PingFrameFlags.ACK, payload);
|
||||
}
|
||||
|
||||
private Task ProcessGoAwayFrameAsync()
|
||||
|
|
@ -777,7 +772,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application)
|
||||
private Task ProcessContinuationFrameAsync<TContext>(IHttpApplication<TContext> application, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
if (_currentHeadersStream == null)
|
||||
{
|
||||
|
|
@ -791,11 +786,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
|
||||
{
|
||||
return DecodeTrailersAsync(_incomingFrame.ContinuationEndHeaders, _incomingFrame.Payload);
|
||||
return DecodeTrailersAsync(_incomingFrame.ContinuationEndHeaders, payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, _incomingFrame.Payload);
|
||||
return DecodeHeadersAsync(application, _incomingFrame.ContinuationEndHeaders, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -809,7 +804,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, Span<byte> payload)
|
||||
private Task DecodeHeadersAsync<TContext>(IHttpApplication<TContext> application, bool endHeaders, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -838,7 +833,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task DecodeTrailersAsync(bool endHeaders, Span<byte> payload)
|
||||
private Task DecodeTrailersAsync(bool endHeaders, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
_hpackDecoder.Decode(payload, endHeaders, handler: this);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||
{
|
||||
/*
|
||||
|
|
@ -26,32 +24,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public bool DataHasPadding => (DataFlags & Http2DataFrameFlags.PADDED) == Http2DataFrameFlags.PADDED;
|
||||
|
||||
public byte DataPadLength
|
||||
{
|
||||
get => DataHasPadding ? Payload[0] : (byte)0;
|
||||
set => Payload[0] = value;
|
||||
}
|
||||
public byte DataPadLength { get; set; }
|
||||
|
||||
public int DataPayloadOffset => DataHasPadding ? 1 : 0;
|
||||
private int DataPayloadOffset => DataHasPadding ? 1 : 0;
|
||||
|
||||
private int DataPayloadLength => PayloadLength - DataPayloadOffset - DataPadLength;
|
||||
|
||||
public Span<byte> DataPayload => Payload.Slice(DataPayloadOffset, DataPayloadLength);
|
||||
public int DataPayloadLength => PayloadLength - DataPayloadOffset - DataPadLength;
|
||||
|
||||
public void PrepareData(int streamId, byte? padLength = null)
|
||||
{
|
||||
var padded = padLength != null;
|
||||
|
||||
PayloadLength = (int)_maxFrameSize;
|
||||
PayloadLength = 0;
|
||||
Type = Http2FrameType.DATA;
|
||||
DataFlags = padded ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE;
|
||||
DataFlags = padLength.HasValue ? Http2DataFrameFlags.PADDED : Http2DataFrameFlags.NONE;
|
||||
StreamId = streamId;
|
||||
|
||||
if (padded)
|
||||
{
|
||||
DataPadLength = padLength.Value;
|
||||
Payload.Slice(PayloadLength - padLength.Value).Fill(0);
|
||||
}
|
||||
DataPadLength = padLength ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||
{
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||
|
|
@ -34,37 +32,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public bool HeadersHasPriority => (HeadersFlags & Http2HeadersFrameFlags.PRIORITY) == Http2HeadersFrameFlags.PRIORITY;
|
||||
|
||||
public byte HeadersPadLength
|
||||
{
|
||||
get => HeadersHasPadding ? Payload[0] : (byte)0;
|
||||
set => Payload[0] = value;
|
||||
}
|
||||
public byte HeadersPadLength { get; set; }
|
||||
|
||||
private int HeadersStreamDependencyOffset => HeadersHasPadding ? 1 : 0;
|
||||
public int HeadersStreamDependency { get; set; }
|
||||
|
||||
public int HeadersStreamDependency
|
||||
{
|
||||
get => (int)Bitshifter.ReadUInt31BigEndian(Payload.Slice(HeadersStreamDependencyOffset));
|
||||
set => Bitshifter.WriteUInt31BigEndian(Payload.Slice(HeadersStreamDependencyOffset), (uint)value);
|
||||
}
|
||||
public byte HeadersPriorityWeight { get; set; }
|
||||
|
||||
private int HeadersPriorityWeightOffset => HeadersStreamDependencyOffset + 4;
|
||||
private int HeadersPayloadOffset => (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0);
|
||||
|
||||
public byte HeadersPriorityWeight
|
||||
{
|
||||
get => Payload[HeadersPriorityWeightOffset];
|
||||
set => Payload[HeadersPriorityWeightOffset] = value;
|
||||
}
|
||||
|
||||
public int HeadersPayloadOffset => (HeadersHasPadding ? 1 : 0) + (HeadersHasPriority ? 5 : 0);
|
||||
|
||||
private int HeadersPayloadLength => PayloadLength - HeadersPayloadOffset - HeadersPadLength;
|
||||
|
||||
public Span<byte> HeadersPayload => Payload.Slice(HeadersPayloadOffset, HeadersPayloadLength);
|
||||
public int HeadersPayloadLength => PayloadLength - HeadersPayloadOffset - HeadersPadLength;
|
||||
|
||||
public void PrepareHeaders(Http2HeadersFrameFlags flags, int streamId)
|
||||
{
|
||||
PayloadLength = (int)_maxFrameSize;
|
||||
PayloadLength = 0;
|
||||
Type = Http2FrameType.HEADERS;
|
||||
HeadersFlags = flags;
|
||||
StreamId = streamId;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||
{
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.5.1
|
||||
|
|
@ -16,8 +13,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
*/
|
||||
public partial class Http2Frame
|
||||
{
|
||||
internal const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value.
|
||||
|
||||
public Http2SettingsFrameFlags SettingsFlags
|
||||
{
|
||||
get => (Http2SettingsFrameFlags)Flags;
|
||||
|
|
@ -26,51 +21,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
|
||||
public bool SettingsAck => (SettingsFlags & Http2SettingsFrameFlags.ACK) == Http2SettingsFrameFlags.ACK;
|
||||
|
||||
public int SettingsCount
|
||||
public void PrepareSettings(Http2SettingsFrameFlags flags)
|
||||
{
|
||||
get => PayloadLength / SettingSize;
|
||||
set => PayloadLength = value * SettingSize;
|
||||
}
|
||||
|
||||
public IList<Http2PeerSetting> GetSettings()
|
||||
{
|
||||
var settings = new Http2PeerSetting[SettingsCount];
|
||||
for (int i = 0; i < settings.Length; i++)
|
||||
{
|
||||
settings[i] = GetSetting(i);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private Http2PeerSetting GetSetting(int index)
|
||||
{
|
||||
var offset = index * SettingSize;
|
||||
var payload = Payload.Slice(offset);
|
||||
var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload);
|
||||
var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2));
|
||||
|
||||
return new Http2PeerSetting(id, value);
|
||||
}
|
||||
|
||||
public void PrepareSettings(Http2SettingsFrameFlags flags, IList<Http2PeerSetting> settings = null)
|
||||
{
|
||||
var settingCount = settings?.Count ?? 0;
|
||||
SettingsCount = settingCount;
|
||||
PayloadLength = 0;
|
||||
Type = Http2FrameType.SETTINGS;
|
||||
SettingsFlags = flags;
|
||||
StreamId = 0;
|
||||
for (int i = 0; i < settingCount; i++)
|
||||
{
|
||||
SetSetting(i, settings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSetting(int index, Http2PeerSetting setting)
|
||||
{
|
||||
var offset = index * SettingSize;
|
||||
var payload = Payload.Slice(offset);
|
||||
BinaryPrimitives.WriteUInt16BigEndian(payload, (ushort)setting.Parameter);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(payload.Slice(2), setting.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||
{
|
||||
/* https://tools.ietf.org/html/rfc7540#section-4.1
|
||||
|
|
@ -18,51 +16,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
*/
|
||||
public partial class Http2Frame
|
||||
{
|
||||
public const int HeaderLength = 9;
|
||||
public int PayloadLength { get; set; }
|
||||
|
||||
private const int LengthOffset = 0;
|
||||
private const int TypeOffset = 3;
|
||||
private const int FlagsOffset = 4;
|
||||
private const int StreamIdOffset = 5;
|
||||
private const int PayloadOffset = 9;
|
||||
public Http2FrameType Type { get; set; }
|
||||
|
||||
private uint _maxFrameSize;
|
||||
public byte Flags { get; set; }
|
||||
|
||||
private readonly byte[] _data;
|
||||
|
||||
public Http2Frame(uint maxFrameSize)
|
||||
{
|
||||
_maxFrameSize = maxFrameSize;
|
||||
_data = new byte[HeaderLength + _maxFrameSize];
|
||||
}
|
||||
|
||||
public Span<byte> Raw => new Span<byte>(_data, 0, HeaderLength + PayloadLength);
|
||||
|
||||
public int PayloadLength
|
||||
{
|
||||
get => (int)Bitshifter.ReadUInt24BigEndian(_data.AsSpan(LengthOffset));
|
||||
set => Bitshifter.WriteUInt24BigEndian(_data.AsSpan(LengthOffset), (uint)value);
|
||||
}
|
||||
|
||||
public Http2FrameType Type
|
||||
{
|
||||
get => (Http2FrameType)_data[TypeOffset];
|
||||
set => _data[TypeOffset] = (byte)value;
|
||||
}
|
||||
|
||||
public byte Flags
|
||||
{
|
||||
get => _data[FlagsOffset];
|
||||
set => _data[FlagsOffset] = value;
|
||||
}
|
||||
|
||||
public int StreamId
|
||||
{
|
||||
get => (int)Bitshifter.ReadUInt31BigEndian(_data.AsSpan(StreamIdOffset));
|
||||
set => Bitshifter.WriteUInt31BigEndian(_data.AsSpan(StreamIdOffset), (uint)value);
|
||||
}
|
||||
|
||||
public Span<byte> Payload => new Span<byte>(_data, PayloadOffset, PayloadLength);
|
||||
public int StreamId { get; set; }
|
||||
|
||||
internal object ShowFlags()
|
||||
{
|
||||
|
|
@ -91,5 +51,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return $"0x{Flags:x}";
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Type} Stream: {StreamId} Length: {PayloadLength} Flags: {ShowFlags()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,40 +3,241 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||
{
|
||||
public static class Http2FrameReader
|
||||
{
|
||||
public static bool ReadFrame(ReadOnlySequence<byte> readableBuffer, Http2Frame frame, uint maxFrameSize, out SequencePosition consumed, out SequencePosition examined)
|
||||
{
|
||||
consumed = readableBuffer.Start;
|
||||
examined = readableBuffer.End;
|
||||
/* https://tools.ietf.org/html/rfc7540#section-4.1
|
||||
+-----------------------------------------------+
|
||||
| Length (24) |
|
||||
+---------------+---------------+---------------+
|
||||
| Type (8) | Flags (8) |
|
||||
+-+-------------+---------------+-------------------------------+
|
||||
|R| Stream Identifier (31) |
|
||||
+=+=============================================================+
|
||||
| Frame Payload (0...) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
public const int HeaderLength = 9;
|
||||
|
||||
if (readableBuffer.Length < Http2Frame.HeaderLength)
|
||||
private const int TypeOffset = 3;
|
||||
private const int FlagsOffset = 4;
|
||||
private const int StreamIdOffset = 5;
|
||||
|
||||
public const int SettingSize = 6; // 2 bytes for the id, 4 bytes for the value.
|
||||
|
||||
public static bool ReadFrame(ReadOnlySequence<byte> readableBuffer, Http2Frame frame, uint maxFrameSize, out ReadOnlySequence<byte> framePayload)
|
||||
{
|
||||
framePayload = ReadOnlySequence<byte>.Empty;
|
||||
|
||||
if (readableBuffer.Length < HeaderLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var headerSlice = readableBuffer.Slice(0, Http2Frame.HeaderLength);
|
||||
headerSlice.CopyTo(frame.Raw);
|
||||
var headerSlice = readableBuffer.Slice(0, HeaderLength);
|
||||
var header = headerSlice.ToSpan();
|
||||
|
||||
var payloadLength = frame.PayloadLength;
|
||||
var payloadLength = (int)Bitshifter.ReadUInt24BigEndian(header);
|
||||
if (payloadLength > maxFrameSize)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorFrameOverLimit(payloadLength, maxFrameSize), Http2ErrorCode.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
var frameLength = Http2Frame.HeaderLength + payloadLength;
|
||||
// Make sure the whole frame is buffered
|
||||
var frameLength = HeaderLength + payloadLength;
|
||||
if (readableBuffer.Length < frameLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
readableBuffer.Slice(Http2Frame.HeaderLength, payloadLength).CopyTo(frame.Payload);
|
||||
consumed = examined = readableBuffer.GetPosition(frameLength);
|
||||
frame.PayloadLength = payloadLength;
|
||||
frame.Type = (Http2FrameType)header[TypeOffset];
|
||||
frame.Flags = header[FlagsOffset];
|
||||
frame.StreamId = (int)Bitshifter.ReadUInt31BigEndian(header.Slice(StreamIdOffset));
|
||||
|
||||
var extendedHeaderLength = ReadExtendedFields(frame, readableBuffer);
|
||||
|
||||
// The remaining payload minus the extra fields
|
||||
framePayload = readableBuffer.Slice(HeaderLength + extendedHeaderLength, payloadLength - extendedHeaderLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int ReadExtendedFields(Http2Frame frame, ReadOnlySequence<byte> readableBuffer)
|
||||
{
|
||||
// Copy in any extra fields for the given frame type
|
||||
var extendedHeaderLength = GetPayloadFieldsLength(frame);
|
||||
|
||||
if (extendedHeaderLength > frame.PayloadLength)
|
||||
{
|
||||
throw new Http2ConnectionErrorException(
|
||||
CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(frame.Type, expectedLength: extendedHeaderLength), Http2ErrorCode.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
var extendedHeaders = readableBuffer.Slice(HeaderLength, extendedHeaderLength).ToSpan();
|
||||
|
||||
// Parse frame type specific fields
|
||||
switch (frame.Type)
|
||||
{
|
||||
/*
|
||||
+---------------+
|
||||
|Pad Length? (8)|
|
||||
+---------------+-----------------------------------------------+
|
||||
| Data (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
| Padding (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
case Http2FrameType.DATA: // Variable 0 or 1
|
||||
frame.DataPadLength = frame.DataHasPadding ? extendedHeaders[0] : (byte)0;
|
||||
break;
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||
+---------------+
|
||||
|Pad Length? (8)|
|
||||
+-+-------------+-----------------------------------------------+
|
||||
|E| Stream Dependency? (31) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Weight? (8) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Header Block Fragment (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
| Padding (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
case Http2FrameType.HEADERS:
|
||||
if (frame.HeadersHasPadding)
|
||||
{
|
||||
frame.HeadersPadLength = extendedHeaders[0];
|
||||
extendedHeaders = extendedHeaders.Slice(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.HeadersPadLength = 0;
|
||||
}
|
||||
|
||||
if (frame.HeadersHasPriority)
|
||||
{
|
||||
frame.HeadersStreamDependency = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
|
||||
frame.HeadersPriorityWeight = extendedHeaders.Slice(4)[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.HeadersStreamDependency = 0;
|
||||
frame.HeadersPriorityWeight = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.8
|
||||
+-+-------------------------------------------------------------+
|
||||
|R| Last-Stream-ID (31) |
|
||||
+-+-------------------------------------------------------------+
|
||||
| Error Code (32) |
|
||||
+---------------------------------------------------------------+
|
||||
| Additional Debug Data (*) |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
case Http2FrameType.GOAWAY:
|
||||
frame.GoAwayLastStreamId = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
|
||||
frame.GoAwayErrorCode = (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(extendedHeaders.Slice(4));
|
||||
break;
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.3
|
||||
+-+-------------------------------------------------------------+
|
||||
|E| Stream Dependency (31) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Weight (8) |
|
||||
+-+-------------+
|
||||
*/
|
||||
case Http2FrameType.PRIORITY:
|
||||
frame.PriorityStreamDependency = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
|
||||
frame.PriorityWeight = extendedHeaders.Slice(4)[0];
|
||||
break;
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.4
|
||||
+---------------------------------------------------------------+
|
||||
| Error Code (32) |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
case Http2FrameType.RST_STREAM:
|
||||
frame.RstStreamErrorCode = (Http2ErrorCode)BinaryPrimitives.ReadUInt32BigEndian(extendedHeaders);
|
||||
break;
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.9
|
||||
+-+-------------------------------------------------------------+
|
||||
|R| Window Size Increment (31) |
|
||||
+-+-------------------------------------------------------------+
|
||||
*/
|
||||
case Http2FrameType.WINDOW_UPDATE:
|
||||
frame.WindowUpdateSizeIncrement = (int)Bitshifter.ReadUInt31BigEndian(extendedHeaders);
|
||||
break;
|
||||
|
||||
case Http2FrameType.PING: // Opaque payload 8 bytes long
|
||||
case Http2FrameType.SETTINGS: // Settings are general payload
|
||||
case Http2FrameType.CONTINUATION: // None
|
||||
case Http2FrameType.PUSH_PROMISE: // Not implemented frames are ignored at this phase
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return extendedHeaderLength;
|
||||
}
|
||||
|
||||
// The length in bytes of additional fields stored in the payload section.
|
||||
// This may be variable based on flags, but should be no more than 8 bytes.
|
||||
public static int GetPayloadFieldsLength(Http2Frame frame)
|
||||
{
|
||||
switch (frame.Type)
|
||||
{
|
||||
// TODO: Extract constants
|
||||
case Http2FrameType.DATA: // Variable 0 or 1
|
||||
return frame.DataHasPadding ? 1 : 0;
|
||||
case Http2FrameType.HEADERS:
|
||||
return (frame.HeadersHasPadding ? 1 : 0) + (frame.HeadersHasPriority ? 5 : 0); // Variable 0 to 6
|
||||
case Http2FrameType.GOAWAY:
|
||||
return 8; // Last stream id and error code.
|
||||
case Http2FrameType.PRIORITY:
|
||||
return 5; // Stream dependency and weight
|
||||
case Http2FrameType.RST_STREAM:
|
||||
return 4; // Error code
|
||||
case Http2FrameType.WINDOW_UPDATE:
|
||||
return 4; // Update size
|
||||
case Http2FrameType.PING: // 8 bytes of opaque data
|
||||
case Http2FrameType.SETTINGS: // Settings are general payload
|
||||
case Http2FrameType.CONTINUATION: // None
|
||||
case Http2FrameType.PUSH_PROMISE: // Not implemented frames are ignored at this phase
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<Http2PeerSetting> ReadSettings(ReadOnlySequence<byte> payload)
|
||||
{
|
||||
var data = payload.ToSpan();
|
||||
Debug.Assert(data.Length % SettingSize == 0, "Invalid settings payload length");
|
||||
var settingsCount = data.Length / SettingSize;
|
||||
|
||||
var settings = new Http2PeerSetting[settingsCount];
|
||||
for (int i = 0; i < settings.Length; i++)
|
||||
{
|
||||
settings[i] = ReadSetting(data);
|
||||
data = data.Slice(SettingSize);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static Http2PeerSetting ReadSetting(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
var id = (Http2SettingsParameter)BinaryPrimitives.ReadUInt16BigEndian(payload);
|
||||
var value = BinaryPrimitives.ReadUInt32BigEndian(payload.Slice(2));
|
||||
|
||||
return new Http2PeerSetting(id, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -22,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
private static readonly byte[] _continueBytes = new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' };
|
||||
|
||||
private uint _maxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize;
|
||||
private byte[] _headerEncodingBuffer;
|
||||
private Http2Frame _outgoingFrame;
|
||||
private readonly object _writeLock = new object();
|
||||
private readonly HPackEncoder _hpackEncoder = new HPackEncoder();
|
||||
|
|
@ -49,15 +52,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_connectionId = connectionId;
|
||||
_log = log;
|
||||
_flusher = new StreamSafePipeFlusher(_outputWriter, timeoutControl);
|
||||
_outgoingFrame = new Http2Frame(_maxFrameSize);
|
||||
_outgoingFrame = new Http2Frame();
|
||||
_headerEncodingBuffer = new byte[_maxFrameSize];
|
||||
}
|
||||
|
||||
public void UpdateMaxFrameSize(uint maxFrameSize)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
_maxFrameSize = maxFrameSize;
|
||||
_outgoingFrame = new Http2Frame(maxFrameSize);
|
||||
if (_maxFrameSize != maxFrameSize)
|
||||
{
|
||||
_maxFrameSize = maxFrameSize;
|
||||
_headerEncodingBuffer = new byte[_maxFrameSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,14 +116,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
|
||||
_outgoingFrame.PayloadLength = _continueBytes.Length;
|
||||
_continueBytes.CopyTo(_outgoingFrame.HeadersPayload);
|
||||
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
WriteHeaderUnsynchronized();
|
||||
_outputWriter.Write(_continueBytes);
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// Optional header fields for padding and priority are not implemented.
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||
+---------------+
|
||||
|Pad Length? (8)|
|
||||
+-+-------------+-----------------------------------------------+
|
||||
|E| Stream Dependency? (31) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Weight? (8) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Header Block Fragment (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
| Padding (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
public void WriteResponseHeaders(int streamId, int statusCode, IHeaderDictionary headers)
|
||||
{
|
||||
lock (_writeLock)
|
||||
|
|
@ -126,33 +152,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
return;
|
||||
}
|
||||
|
||||
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||
|
||||
var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), _outgoingFrame.Payload, out var payloadLength);
|
||||
_outgoingFrame.PayloadLength = payloadLength;
|
||||
|
||||
if (done)
|
||||
try
|
||||
{
|
||||
_outgoingFrame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
|
||||
}
|
||||
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||
var buffer = _headerEncodingBuffer.AsSpan();
|
||||
var done = _hpackEncoder.BeginEncode(statusCode, EnumerateHeaders(headers), buffer, out var payloadLength);
|
||||
|
||||
_log.Http2FrameSending(_connectionId, _outgoingFrame);
|
||||
_outputWriter.Write(_outgoingFrame.Raw);
|
||||
|
||||
while (!done)
|
||||
{
|
||||
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
|
||||
|
||||
done = _hpackEncoder.Encode(_outgoingFrame.Payload, out var length);
|
||||
_outgoingFrame.PayloadLength = length;
|
||||
_outgoingFrame.PayloadLength = payloadLength;
|
||||
|
||||
if (done)
|
||||
{
|
||||
_outgoingFrame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
|
||||
_outgoingFrame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
|
||||
}
|
||||
|
||||
_log.Http2FrameSending(_connectionId, _outgoingFrame);
|
||||
_outputWriter.Write(_outgoingFrame.Raw);
|
||||
WriteHeaderUnsynchronized();
|
||||
_outputWriter.Write(buffer.Slice(0, payloadLength));
|
||||
|
||||
while (!done)
|
||||
{
|
||||
_outgoingFrame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
|
||||
|
||||
done = _hpackEncoder.Encode(buffer, out payloadLength);
|
||||
_outgoingFrame.PayloadLength = payloadLength;
|
||||
|
||||
if (done)
|
||||
{
|
||||
_outgoingFrame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
|
||||
}
|
||||
|
||||
WriteHeaderUnsynchronized();
|
||||
_outputWriter.Write(buffer.Slice(0, payloadLength));
|
||||
}
|
||||
}
|
||||
catch (HPackEncodingException hex)
|
||||
{
|
||||
// Header errors are fatal to the connection. We don't have a direct way to signal this to the Http2Connection.
|
||||
_connectionContext.Abort(new ConnectionAbortedException("", hex));
|
||||
throw new InvalidOperationException("", hex); // Report the error to the user if this was the first write.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -182,45 +218,54 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
}
|
||||
}
|
||||
|
||||
/* Padding is not implemented
|
||||
+---------------+
|
||||
|Pad Length? (8)|
|
||||
+---------------+-----------------------------------------------+
|
||||
| Data (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
| Padding (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
private Task WriteDataUnsynchronizedAsync(int streamId, ReadOnlySequence<byte> data, bool endStream)
|
||||
{
|
||||
// Note padding is not implemented
|
||||
_outgoingFrame.PrepareData(streamId);
|
||||
|
||||
var payload = _outgoingFrame.Payload;
|
||||
var unwrittenPayloadLength = 0;
|
||||
var dataPayloadLength = (int)_maxFrameSize; // Minus padding
|
||||
|
||||
foreach (var buffer in data)
|
||||
while (data.Length > dataPayloadLength)
|
||||
{
|
||||
var current = buffer;
|
||||
var currentData = data.Slice(0, dataPayloadLength);
|
||||
_outgoingFrame.PayloadLength = dataPayloadLength; // Plus padding
|
||||
|
||||
while (current.Length > payload.Length)
|
||||
WriteHeaderUnsynchronized();
|
||||
|
||||
foreach (var buffer in currentData)
|
||||
{
|
||||
current.Span.Slice(0, payload.Length).CopyTo(payload);
|
||||
current = current.Slice(payload.Length);
|
||||
|
||||
_log.Http2FrameSending(_connectionId, _outgoingFrame);
|
||||
_outputWriter.Write(_outgoingFrame.Raw);
|
||||
payload = _outgoingFrame.Payload;
|
||||
unwrittenPayloadLength = 0;
|
||||
_outputWriter.Write(buffer.Span);
|
||||
}
|
||||
|
||||
if (current.Length > 0)
|
||||
{
|
||||
current.Span.CopyTo(payload);
|
||||
payload = payload.Slice(current.Length);
|
||||
unwrittenPayloadLength += current.Length;
|
||||
}
|
||||
// Plus padding
|
||||
|
||||
data = data.Slice(dataPayloadLength);
|
||||
}
|
||||
|
||||
if (endStream)
|
||||
{
|
||||
_outgoingFrame.DataFlags = Http2DataFrameFlags.END_STREAM;
|
||||
_outgoingFrame.DataFlags |= Http2DataFrameFlags.END_STREAM;
|
||||
}
|
||||
|
||||
_outgoingFrame.PayloadLength = unwrittenPayloadLength;
|
||||
_outgoingFrame.PayloadLength = (int)data.Length; // Plus padding
|
||||
|
||||
_log.Http2FrameSending(_connectionId, _outgoingFrame);
|
||||
_outputWriter.Write(_outgoingFrame.Raw);
|
||||
WriteHeaderUnsynchronized();
|
||||
|
||||
foreach (var buffer in data)
|
||||
{
|
||||
_outputWriter.Write(buffer.Span);
|
||||
}
|
||||
|
||||
// Plus padding
|
||||
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
|
|
@ -271,71 +316,194 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
await ThreadPoolAwaitable.Instance;
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.9
|
||||
+-+-------------------------------------------------------------+
|
||||
|R| Window Size Increment (31) |
|
||||
+-+-------------------------------------------------------------+
|
||||
*/
|
||||
public Task WriteWindowUpdateAsync(int streamId, int sizeIncrement)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_outgoingFrame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
WriteHeaderUnsynchronized();
|
||||
var buffer = _outputWriter.GetSpan(4);
|
||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement);
|
||||
_outputWriter.Advance(4);
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.4
|
||||
+---------------------------------------------------------------+
|
||||
| Error Code (32) |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
public Task WriteRstStreamAsync(int streamId, Http2ErrorCode errorCode)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_outgoingFrame.PrepareRstStream(streamId, errorCode);
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
WriteHeaderUnsynchronized();
|
||||
var buffer = _outputWriter.GetSpan(4);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode);
|
||||
_outputWriter.Advance(4);
|
||||
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.5.1
|
||||
List of:
|
||||
+-------------------------------+
|
||||
| Identifier (16) |
|
||||
+-------------------------------+-------------------------------+
|
||||
| Value (32) |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
public Task WriteSettingsAsync(IList<Http2PeerSetting> settings)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.NONE, settings);
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.NONE);
|
||||
var settingsSize = settings.Count * Http2FrameReader.SettingSize;
|
||||
_outgoingFrame.PayloadLength = settingsSize;
|
||||
WriteHeaderUnsynchronized();
|
||||
|
||||
var buffer = _outputWriter.GetSpan(settingsSize).Slice(0, settingsSize); // GetSpan isn't precise
|
||||
WriteSettings(settings, buffer);
|
||||
_outputWriter.Advance(settingsSize);
|
||||
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void WriteSettings(IList<Http2PeerSetting> settings, Span<byte> destination)
|
||||
{
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
BinaryPrimitives.WriteUInt16BigEndian(destination, (ushort)setting.Parameter);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(2), setting.Value);
|
||||
destination = destination.Slice(Http2FrameReader.SettingSize);
|
||||
}
|
||||
}
|
||||
|
||||
// No payload
|
||||
public Task WriteSettingsAckAsync()
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_outgoingFrame.PrepareSettings(Http2SettingsFrameFlags.ACK);
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
WriteHeaderUnsynchronized();
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySpan<byte> payload)
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.7
|
||||
+---------------------------------------------------------------+
|
||||
| |
|
||||
| Opaque Data (64) |
|
||||
| |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
public Task WritePingAsync(Http2PingFrameFlags flags, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_outgoingFrame.PreparePing(Http2PingFrameFlags.ACK);
|
||||
payload.CopyTo(_outgoingFrame.Payload);
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
Debug.Assert(payload.Length == _outgoingFrame.PayloadLength); // 8
|
||||
WriteHeaderUnsynchronized();
|
||||
foreach (var segment in payload)
|
||||
{
|
||||
_outputWriter.Write(segment.Span);
|
||||
}
|
||||
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.8
|
||||
+-+-------------------------------------------------------------+
|
||||
|R| Last-Stream-ID (31) |
|
||||
+-+-------------------------------------------------------------+
|
||||
| Error Code (32) |
|
||||
+---------------------------------------------------------------+
|
||||
| Additional Debug Data (*) | (not implemented)
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
public Task WriteGoAwayAsync(int lastStreamId, Http2ErrorCode errorCode)
|
||||
{
|
||||
lock (_writeLock)
|
||||
{
|
||||
_outgoingFrame.PrepareGoAway(lastStreamId, errorCode);
|
||||
return WriteFrameUnsynchronizedAsync();
|
||||
WriteHeaderUnsynchronized();
|
||||
|
||||
var buffer = _outputWriter.GetSpan(8);
|
||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)lastStreamId);
|
||||
buffer = buffer.Slice(4);
|
||||
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)errorCode);
|
||||
_outputWriter.Advance(8);
|
||||
|
||||
return _flusher.FlushAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private Task WriteFrameUnsynchronizedAsync()
|
||||
private void WriteHeaderUnsynchronized()
|
||||
{
|
||||
if (_completed)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
_log.Http2FrameSending(_connectionId, _outgoingFrame);
|
||||
_outputWriter.Write(_outgoingFrame.Raw);
|
||||
return _flusher.FlushAsync();
|
||||
WriteHeader(_outgoingFrame, _outputWriter);
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-4.1
|
||||
+-----------------------------------------------+
|
||||
| Length (24) |
|
||||
+---------------+---------------+---------------+
|
||||
| Type (8) | Flags (8) |
|
||||
+-+-------------+---------------+-------------------------------+
|
||||
|R| Stream Identifier (31) |
|
||||
+=+=============================================================+
|
||||
| Frame Payload (0...) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
internal static void WriteHeader(Http2Frame frame, PipeWriter output)
|
||||
{
|
||||
var buffer = output.GetSpan(Http2FrameReader.HeaderLength);
|
||||
|
||||
Bitshifter.WriteUInt24BigEndian(buffer, (uint)frame.PayloadLength);
|
||||
buffer = buffer.Slice(3);
|
||||
|
||||
buffer[0] = (byte)frame.Type;
|
||||
buffer[1] = frame.Flags;
|
||||
buffer = buffer.Slice(2);
|
||||
|
||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)frame.StreamId);
|
||||
|
||||
output.Advance(Http2FrameReader.HeaderLength);
|
||||
}
|
||||
|
||||
public bool TryUpdateConnectionWindow(int bytes)
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
}
|
||||
}
|
||||
|
||||
public Task OnDataAsync(Http2Frame dataFrame)
|
||||
public Task OnDataAsync(Http2Frame dataFrame, ReadOnlySequence<byte> payload)
|
||||
{
|
||||
// Since padding isn't buffered, immediately count padding bytes as read for flow control purposes.
|
||||
if (dataFrame.DataHasPadding)
|
||||
|
|
@ -288,10 +288,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
OnDataRead(dataFrame.DataPadLength + 1);
|
||||
}
|
||||
|
||||
var payload = dataFrame.DataPayload;
|
||||
var dataPayload = payload.Slice(0, dataFrame.DataPayloadLength); // minus padding
|
||||
var endStream = dataFrame.DataEndStream;
|
||||
|
||||
if (payload.Length > 0)
|
||||
if (dataPayload.Length > 0)
|
||||
{
|
||||
RequestBodyStarted = true;
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
_inputFlowControl.StopWindowUpdates();
|
||||
}
|
||||
|
||||
_inputFlowControl.Advance(payload.Length);
|
||||
_inputFlowControl.Advance((int)dataPayload.Length);
|
||||
|
||||
if (IsAborted)
|
||||
{
|
||||
|
|
@ -315,15 +315,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
if (InputRemaining.HasValue)
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.6
|
||||
if (payload.Length > InputRemaining.Value)
|
||||
if (dataPayload.Length > InputRemaining.Value)
|
||||
{
|
||||
throw new Http2StreamErrorException(StreamId, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
InputRemaining -= payload.Length;
|
||||
InputRemaining -= dataPayload.Length;
|
||||
}
|
||||
|
||||
RequestBodyPipe.Writer.Write(payload);
|
||||
foreach (var segment in dataPayload)
|
||||
{
|
||||
RequestBodyPipe.Writer.Write(segment.Span);
|
||||
}
|
||||
var flushTask = RequestBodyPipe.Writer.FlushAsync();
|
||||
|
||||
// It shouldn't be possible for the RequestBodyPipe to fill up an return an incomplete task if
|
||||
|
|
|
|||
|
|
@ -2128,20 +2128,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatGreaterThanZeroRequired()
|
||||
=> GetString("GreaterThanZeroRequired");
|
||||
|
||||
/// <summary>
|
||||
/// The frame is too short to contain the fields indicated by the given flags.
|
||||
/// </summary>
|
||||
internal static string Http2FrameMissingFields
|
||||
{
|
||||
get => GetString("Http2FrameMissingFields");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The frame is too short to contain the fields indicated by the given flags.
|
||||
/// </summary>
|
||||
internal static string FormatHttp2FrameMissingFields()
|
||||
=> GetString("Http2FrameMissingFields");
|
||||
|
||||
/// <summary>
|
||||
/// A value between {min} and {max} is required.
|
||||
/// </summary>
|
||||
|
|
@ -2170,6 +2156,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
|||
internal static string FormatHPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock()
|
||||
=> GetString("HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock");
|
||||
|
||||
/// <summary>
|
||||
/// The given buffer was too small to encode any headers.
|
||||
/// </summary>
|
||||
internal static string HPackErrorNotEnoughBuffer
|
||||
{
|
||||
get => GetString("HPackErrorNotEnoughBuffer");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The given buffer was too small to encode any headers.
|
||||
/// </summary>
|
||||
internal static string FormatHPackErrorNotEnoughBuffer()
|
||||
=> GetString("HPackErrorNotEnoughBuffer");
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
|
@ -104,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Fact]
|
||||
public void DecodesIndexedHeaderField_StaticTable()
|
||||
{
|
||||
_decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderStatic), endHeaders: true, handler: this);
|
||||
Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]);
|
||||
}
|
||||
|
||||
|
|
@ -115,14 +116,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
_dynamicTable.Insert(_headerNameBytes, _headerValueBytes);
|
||||
|
||||
// Index it
|
||||
_decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderDynamic), endHeaders: true, handler: this);
|
||||
Assert.Equal(_headerValueString, _decodedHeaders[_headerNameString]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DecodesIndexedHeaderField_OutOfRange_Error()
|
||||
{
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(_indexedHeaderDynamic, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() =>
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderDynamic), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -198,7 +200,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
// 11 1110 (Indexed Name - Index 62 encoded with 6-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
|
||||
// Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw.
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x7e }, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x7e }), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -274,7 +276,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
// 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
|
||||
// Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw.
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x0f, 0x2f }, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x0f, 0x2f }), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -356,7 +358,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
// 1111 0010 1111 (Indexed Name - Index 62 encoded with 4-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
|
||||
// Index 62 is the first entry in the dynamic table. If there's nothing there, the decoder should throw.
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x1f, 0x2f }, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x1f, 0x2f }), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackErrorIndexOutOfRange(62), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -369,7 +371,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
|
||||
|
||||
_decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x3e }), endHeaders: true, handler: this);
|
||||
|
||||
Assert.Equal(30, _dynamicTable.MaxSize);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
|
|
@ -383,7 +385,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray(), endHeaders: true, handler: this));
|
||||
var data = new ReadOnlySequence<byte>(_indexedHeaderStatic.Concat(new byte[] { 0x3e }).ToArray());
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(data, endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -392,12 +395,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
|
||||
|
||||
_decoder.Decode(_indexedHeaderStatic, endHeaders: false, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderStatic), endHeaders: false, handler: this);
|
||||
Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]);
|
||||
|
||||
// 001 (Dynamic Table Size Update)
|
||||
// 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this));
|
||||
var data = new ReadOnlySequence<byte>(new byte[] { 0x3e });
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(data, endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.HPackErrorDynamicTableSizeUpdateNotAtBeginningOfHeaderBlock, exception.Message);
|
||||
}
|
||||
|
||||
|
|
@ -406,12 +410,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
|
||||
|
||||
_decoder.Decode(_indexedHeaderStatic, endHeaders: true, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(_indexedHeaderStatic), endHeaders: true, handler: this);
|
||||
Assert.Equal("GET", _decodedHeaders[HeaderNames.Method]);
|
||||
|
||||
// 001 (Dynamic Table Size Update)
|
||||
// 11110 (30 encoded with 5-bit prefix - see http://httpwg.org/specs/rfc7541.html#integer.representation)
|
||||
_decoder.Decode(new byte[] { 0x3e }, endHeaders: true, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x3e }), endHeaders: true, handler: this);
|
||||
|
||||
Assert.Equal(30, _dynamicTable.MaxSize);
|
||||
}
|
||||
|
|
@ -424,7 +428,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
Assert.Equal(DynamicTableInitialMaxSize, _dynamicTable.MaxSize);
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new byte[] { 0x3f, 0xe2, 0x1f }, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() =>
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(new byte[] { 0x3f, 0xe2, 0x1f }), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackErrorDynamicTableSizeUpdateTooLarge(4097, DynamicTableInitialMaxSize), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -436,7 +441,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
.Concat(new byte[] { 0xff, 0x82, 0x1f }) // 4097 encoded with 7-bit prefix
|
||||
.ToArray();
|
||||
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.FormatHPackStringLengthTooLarge(4097, HPackDecoder.MaxStringOctets), exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -526,7 +531,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[MemberData(nameof(_incompleteHeaderBlockData))]
|
||||
public void DecodesIncompleteHeaderBlock_Error(byte[] encoded)
|
||||
{
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.HPackErrorIncompleteHeaderBlock, exception.Message);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
}
|
||||
|
|
@ -560,7 +565,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[MemberData(nameof(_huffmanDecodingErrorData))]
|
||||
public void WrapsHuffmanDecodingExceptionInHPackDecodingException(byte[] encoded)
|
||||
{
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(encoded, endHeaders: true, handler: this));
|
||||
var exception = Assert.Throws<HPackDecodingException>(() => _decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this));
|
||||
Assert.Equal(CoreStrings.HPackHuffmanError, exception.Message);
|
||||
Assert.IsType<HuffmanDecodingException>(exception.InnerException);
|
||||
Assert.Empty(_decodedHeaders);
|
||||
|
|
@ -581,7 +586,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(0, _dynamicTable.Count);
|
||||
Assert.Equal(0, _dynamicTable.Size);
|
||||
|
||||
_decoder.Decode(encoded, endHeaders: true, handler: this);
|
||||
_decoder.Decode(new ReadOnlySequence<byte>(encoded), endHeaders: true, handler: this);
|
||||
|
||||
Assert.Equal(expectedHeaderValue, _decodedHeaders[expectedHeaderName]);
|
||||
|
||||
|
|
|
|||
|
|
@ -84,8 +84,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await InitializeConnectionAsync(_echoApplication);
|
||||
|
||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
|
||||
|
||||
uint length = Http2PeerSettings.MinAllowedMaxFrameSize + 1;
|
||||
await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[length], endStream: true);
|
||||
|
||||
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||
ignoreNonGoAwayFrames: true,
|
||||
|
|
@ -104,7 +105,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await InitializeConnectionAsync(_echoApplication, expectedSettingsCount: 3);
|
||||
|
||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
|
||||
await SendDataAsync(1, new byte[length].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[length], endStream: true);
|
||||
|
||||
await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 37,
|
||||
|
|
@ -150,7 +151,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -177,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -249,10 +250,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.PayloadSequence.ToArray()));
|
||||
Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame1.WindowUpdateSizeIncrement);
|
||||
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
|
||||
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement);
|
||||
|
|
@ -287,7 +288,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -350,10 +351,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.DataPayload));
|
||||
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.DataPayload));
|
||||
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream3DataFrame1.DataPayload));
|
||||
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream3DataFrame2.DataPayload));
|
||||
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream1DataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream1DataFrame2.PayloadSequence.ToArray()));
|
||||
Assert.True(_helloBytes.AsSpan().SequenceEqual(stream3DataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_worldBytes.AsSpan().SequenceEqual(stream3DataFrame2.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -443,16 +444,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
|
||||
Assert.Equal(_maxData.Length * 2, streamWindowUpdateFrame.WindowUpdateSizeIncrement);
|
||||
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame1.WindowUpdateSizeIncrement);
|
||||
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame4.PayloadSequence.ToArray()));
|
||||
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame2.WindowUpdateSizeIncrement);
|
||||
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame5.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame5.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -547,7 +548,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -564,7 +565,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await InitializeConnectionAsync(_echoApplication);
|
||||
|
||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
|
||||
await SendDataWithPaddingAsync(1, maxDataMinusPadding.Span, padLength, endStream: false);
|
||||
await SendDataWithPaddingAsync(1, maxDataMinusPadding, padLength, endStream: false);
|
||||
|
||||
await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 37,
|
||||
|
|
@ -595,8 +596,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(maxDataMinusPadding.Span.SequenceEqual(dataFrame1.DataPayload));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.DataPayload));
|
||||
Assert.True(maxDataMinusPadding.Span.SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_maxData.AsSpan().SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
|
||||
|
||||
Assert.Equal(_maxData.Length * 2, connectionWindowUpdateFrame.WindowUpdateSizeIncrement);
|
||||
}
|
||||
|
|
@ -703,8 +704,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||
ignoreNonGoAwayFrames: true,
|
||||
expectedLastStreamId: 1,
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: CoreStrings.Http2FrameMissingFields);
|
||||
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
|
||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.DATA, expectedLength: 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1127,6 +1128,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
|
||||
Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.PayloadSequence.ToArray());
|
||||
|
||||
await SendDataAsync(1, _helloBytes, endStream: true);
|
||||
|
||||
await ExpectAsync(Http2FrameType.HEADERS,
|
||||
|
|
@ -1142,8 +1145,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
Assert.Equal(new byte[] { 0x08, 0x03, (byte)'1', (byte)'0', (byte)'0' }, frame.HeadersPayload.ToArray());
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
|
|
@ -1361,8 +1362,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
|
||||
ignoreNonGoAwayFrames: true,
|
||||
expectedLastStreamId: 0,
|
||||
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
|
||||
expectedErrorMessage: CoreStrings.Http2FrameMissingFields);
|
||||
expectedErrorCode: Http2ErrorCode.FRAME_SIZE_ERROR,
|
||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorUnexpectedFrameLength(Http2FrameType.HEADERS, expectedLength: 1));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -1924,7 +1925,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||
withStreamId: streamId);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||
Assert.False(writeTasks[streamId].IsCompleted);
|
||||
}
|
||||
|
||||
|
|
@ -2064,13 +2065,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await SendSettingsAsync();
|
||||
|
||||
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: Http2Frame.SettingSize * 2,
|
||||
withLength: Http2FrameReader.SettingSize * 2,
|
||||
withFlags: 0,
|
||||
withStreamId: 0);
|
||||
|
||||
// Only non protocol defaults are sent
|
||||
Assert.Equal(2, frame.SettingsCount);
|
||||
var settings = frame.GetSettings();
|
||||
var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
|
||||
Assert.Equal(2, settings.Count);
|
||||
|
||||
var setting = settings[0];
|
||||
|
|
@ -2101,13 +2101,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await SendSettingsAsync();
|
||||
|
||||
var frame = await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: 6 * 2,
|
||||
withLength: Http2FrameReader.SettingSize * 2,
|
||||
withFlags: 0,
|
||||
withStreamId: 0);
|
||||
|
||||
// Only non protocol defaults are sent
|
||||
Assert.Equal(2, frame.SettingsCount);
|
||||
var settings = frame.GetSettings();
|
||||
var settings = Http2FrameReader.ReadSettings(frame.PayloadSequence);
|
||||
Assert.Equal(2, settings.Count);
|
||||
|
||||
var setting = settings[0];
|
||||
|
|
@ -2139,9 +2138,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
{
|
||||
await InitializeConnectionAsync(_noopApplication);
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, _pair.Application.Output);
|
||||
await FlushAsync(_pair.Application.Output);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 0, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
|
@ -2260,6 +2260,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Fact]
|
||||
public async Task SETTINGS_Received_ChangesAllowedResponseMaxFrameSize()
|
||||
{
|
||||
_connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MaxAllowedMaxFrameSize;
|
||||
// This includes the default response headers such as :status, etc
|
||||
var defaultResponseHeaderLength = 37;
|
||||
var headerValueLength = Http2PeerSettings.MinAllowedMaxFrameSize;
|
||||
|
|
@ -2276,7 +2277,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
context.Response.Headers["A"] = new string('a', headerValueLength);
|
||||
context.Response.Headers["B"] = new string('b', headerValueLength);
|
||||
return context.Response.Body.WriteAsync(new byte[payloadLength], 0, payloadLength);
|
||||
});
|
||||
}, expectedSettingsCount: 3);
|
||||
|
||||
// Update client settings
|
||||
_clientSettings.MaxFrameSize = (uint)payloadLength;
|
||||
|
|
@ -2311,6 +2312,42 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SETTINGS_Received_ClientMaxFrameSizeCannotExceedServerMaxFrameSize()
|
||||
{
|
||||
var serverMaxFrame = Http2PeerSettings.MinAllowedMaxFrameSize + 1024;
|
||||
_connection.ServerSettings.MaxFrameSize = Http2PeerSettings.MinAllowedMaxFrameSize + 1024;
|
||||
var clientMaxFrame = serverMaxFrame + 1024 * 5;
|
||||
_clientSettings.MaxFrameSize = (uint)clientMaxFrame;
|
||||
|
||||
await InitializeConnectionAsync(context =>
|
||||
{
|
||||
return context.Response.Body.WriteAsync(new byte[clientMaxFrame], 0, clientMaxFrame);
|
||||
}, expectedSettingsCount: 3);
|
||||
|
||||
// Start request
|
||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
|
||||
|
||||
await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 37,
|
||||
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: serverMaxFrame,
|
||||
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: clientMaxFrame - serverMaxFrame,
|
||||
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||
withStreamId: 1);
|
||||
await ExpectAsync(Http2FrameType.DATA,
|
||||
withLength: 0,
|
||||
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
|
||||
withStreamId: 1);
|
||||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SETTINGS_Received_ChangesHeaderTableSize()
|
||||
{
|
||||
|
|
@ -2627,7 +2664,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withFlags: (byte)Http2DataFrameFlags.NONE,
|
||||
withStreamId: streamId);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame.PayloadSequence.ToArray()));
|
||||
Assert.False(writeTasks[streamId].IsCompleted);
|
||||
}
|
||||
|
||||
|
|
@ -2931,8 +2968,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame1.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, initialWindowSize).SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_helloWorldBytes.AsSpan(initialWindowSize, initialWindowSize).SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -2986,9 +3023,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, 6).SequenceEqual(dataFrame1.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(6, 3).SequenceEqual(dataFrame2.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.DataPayload));
|
||||
Assert.True(_helloWorldBytes.AsSpan(0, 6).SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
|
||||
Assert.True(_helloWorldBytes.AsSpan(6, 3).SequenceEqual(dataFrame2.PayloadSequence.ToArray()));
|
||||
Assert.True(_helloWorldBytes.AsSpan(9, 3).SequenceEqual(dataFrame3.PayloadSequence.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -3190,9 +3227,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(continuationFrame1.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(continuationFrame2.HeadersPayload, endHeaders: true, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(continuationFrame1.PayloadSequence, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(continuationFrame2.PayloadSequence, endHeaders: true, handler: this);
|
||||
|
||||
Assert.Equal(11, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(5, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -178,7 +178,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(5, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(6, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -394,7 +394,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -429,7 +429,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -464,7 +464,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -499,7 +499,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(4, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -610,7 +610,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[12], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
|
|
@ -623,7 +623,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -653,7 +653,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
|
||||
};
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[12], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
|
|
@ -666,7 +666,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -699,9 +699,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[3].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[8].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[1], endStream: false);
|
||||
await SendDataAsync(1, new byte[3], endStream: false);
|
||||
await SendDataAsync(1, new byte[8], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
|
|
@ -714,7 +714,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -786,7 +786,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[13].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[13], endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
|
||||
|
||||
|
|
@ -821,7 +821,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[11].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[11], endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
|
||||
|
||||
|
|
@ -856,10 +856,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[2].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[10].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[2].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[1], endStream: false);
|
||||
await SendDataAsync(1, new byte[2], endStream: false);
|
||||
await SendDataAsync(1, new byte[10], endStream: false);
|
||||
await SendDataAsync(1, new byte[2], endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
|
||||
|
||||
|
|
@ -894,8 +894,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[1].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[2].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[1], endStream: false);
|
||||
await SendDataAsync(1, new byte[2], endStream: true);
|
||||
|
||||
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
|
||||
|
||||
|
|
@ -938,7 +938,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -977,7 +977,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1015,7 +1015,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1053,7 +1053,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1080,7 +1080,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[12], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
|
|
@ -1093,7 +1093,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1136,7 +1136,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1164,7 +1164,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[12], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
|
|
@ -1177,7 +1177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1207,9 +1207,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[6].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[6], endStream: false);
|
||||
await SendDataAsync(1, new byte[6], endStream: false);
|
||||
await SendDataAsync(1, new byte[6], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 59,
|
||||
|
|
@ -1222,7 +1222,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1266,9 +1266,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[6].AsSpan(), endStream: false);
|
||||
await SendDataAsync(1, new byte[6].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[6], endStream: false);
|
||||
await SendDataAsync(1, new byte[6], endStream: false);
|
||||
await SendDataAsync(1, new byte[6], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 59,
|
||||
|
|
@ -1281,7 +1281,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1321,7 +1321,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
});
|
||||
|
||||
await StartStreamAsync(1, headers, endStream: false);
|
||||
await SendDataAsync(1, new byte[12].AsSpan(), endStream: true);
|
||||
await SendDataAsync(1, new byte[12], endStream: true);
|
||||
|
||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||
withLength: 55,
|
||||
|
|
@ -1334,7 +1334,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1371,7 +1371,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(3, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1409,7 +1409,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
|
||||
_hpackDecoder.Decode(headersFrame.HeadersPayload, endHeaders: false, handler: this);
|
||||
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||
|
||||
Assert.Equal(2, _decodedHeaders.Count);
|
||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -1735,5 +1735,23 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResponseWithHeadersTooLarge_AbortsConnection()
|
||||
{
|
||||
var appFinished = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
await InitializeConnectionAsync(async context =>
|
||||
{
|
||||
context.Response.Headers["too_long"] = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync("Hello World")).DefaultTimeout();
|
||||
appFinished.TrySetResult(ex.InnerException.Message);
|
||||
});
|
||||
|
||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
|
||||
|
||||
var message = await appFinished.Task.DefaultTimeout();
|
||||
Assert.Equal(CoreStrings.HPackErrorNotEnoughBuffer, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
@ -11,6 +12,7 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
|
||||
|
|
@ -20,7 +22,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.HPack;
|
|||
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
protected readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
|
||||
protected readonly HPackEncoder _hpackEncoder = new HPackEncoder();
|
||||
protected readonly HPackDecoder _hpackDecoder;
|
||||
private readonly byte[] _headerEncodingBuffer = new byte[Http2PeerSettings.MinAllowedMaxFrameSize];
|
||||
|
||||
protected readonly ConcurrentDictionary<int, TaskCompletionSource<object>> _runningStreams = new ConcurrentDictionary<int, TaskCompletionSource<object>>();
|
||||
protected readonly Dictionary<string, string> _receivedHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
|
@ -315,7 +317,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
await SendSettingsAsync();
|
||||
|
||||
await ExpectAsync(Http2FrameType.SETTINGS,
|
||||
withLength: expectedSettingsCount * Http2Frame.SettingSize,
|
||||
withLength: expectedSettingsCount * Http2FrameReader.SettingSize,
|
||||
withFlags: 0,
|
||||
withStreamId: 0);
|
||||
|
||||
|
|
@ -325,14 +327,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
withStreamId: 0);
|
||||
}
|
||||
|
||||
protected async Task StartStreamAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, bool endStream)
|
||||
protected Task StartStreamAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, bool endStream)
|
||||
{
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_runningStreams[streamId] = tcs;
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
|
||||
var done = _hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
|
||||
|
||||
var buffer = _headerEncodingBuffer.AsSpan();
|
||||
var done = _hpackEncoder.BeginEncode(headers, buffer, out var length);
|
||||
frame.PayloadLength = length;
|
||||
|
||||
if (done)
|
||||
|
|
@ -345,12 +350,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
|
||||
}
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
writableBuffer.Write(buffer.Slice(0, length));
|
||||
|
||||
while (!done)
|
||||
{
|
||||
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
|
||||
done = _hpackEncoder.Encode(frame.HeadersPayload, out length);
|
||||
|
||||
done = _hpackEncoder.Encode(buffer, out length);
|
||||
frame.PayloadLength = length;
|
||||
|
||||
if (done)
|
||||
|
|
@ -358,77 +365,143 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
|
||||
}
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
writableBuffer.Write(buffer.Slice(0, length));
|
||||
}
|
||||
|
||||
return FlushAsync(writableBuffer);
|
||||
}
|
||||
|
||||
protected async Task SendHeadersWithPaddingAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, bool endStream)
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||
+---------------+
|
||||
|Pad Length? (8)|
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Header Block Fragment (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
| Padding (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
protected Task SendHeadersWithPaddingAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, bool endStream)
|
||||
{
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_runningStreams[streamId] = tcs;
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED, streamId);
|
||||
frame.HeadersPadLength = padLength;
|
||||
|
||||
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
|
||||
var extendedHeaderLength = 1; // Padding length field
|
||||
var buffer = _headerEncodingBuffer.AsSpan();
|
||||
var extendedHeader = buffer.Slice(0, extendedHeaderLength);
|
||||
extendedHeader[0] = padLength;
|
||||
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
||||
|
||||
frame.PayloadLength = 1 + length + padLength;
|
||||
frame.Payload.Slice(1 + length).Fill(0);
|
||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
||||
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
||||
padding.Fill(0);
|
||||
|
||||
frame.PayloadLength = extendedHeaderLength + length + padLength;
|
||||
|
||||
if (endStream)
|
||||
{
|
||||
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
|
||||
}
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
writableBuffer.Write(buffer.Slice(0, frame.PayloadLength));
|
||||
return FlushAsync(writableBuffer);
|
||||
}
|
||||
|
||||
protected async Task SendHeadersWithPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte priority, int streamDependency, bool endStream)
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||
+-+-------------+-----------------------------------------------+
|
||||
|E| Stream Dependency? (31) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Weight? (8) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Header Block Fragment (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
protected Task SendHeadersWithPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte priority, int streamDependency, bool endStream)
|
||||
{
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_runningStreams[streamId] = tcs;
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PRIORITY, streamId);
|
||||
frame.HeadersPriorityWeight = priority;
|
||||
frame.HeadersStreamDependency = streamDependency;
|
||||
|
||||
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
|
||||
var extendedHeaderLength = 5; // stream dependency + weight
|
||||
var buffer = _headerEncodingBuffer.AsSpan();
|
||||
var extendedHeader = buffer.Slice(0, extendedHeaderLength);
|
||||
Bitshifter.WriteUInt31BigEndian(extendedHeader, (uint)streamDependency);
|
||||
extendedHeader[4] = priority;
|
||||
var payload = buffer.Slice(extendedHeaderLength);
|
||||
|
||||
frame.PayloadLength = 5 + length;
|
||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
||||
|
||||
frame.PayloadLength = extendedHeaderLength + length;
|
||||
|
||||
if (endStream)
|
||||
{
|
||||
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
|
||||
}
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
writableBuffer.Write(buffer.Slice(0, frame.PayloadLength));
|
||||
return FlushAsync(writableBuffer);
|
||||
}
|
||||
|
||||
protected async Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, byte priority, int streamDependency, bool endStream)
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.2
|
||||
+---------------+
|
||||
|Pad Length? (8)|
|
||||
+-+-------------+-----------------------------------------------+
|
||||
|E| Stream Dependency? (31) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Weight? (8) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Header Block Fragment (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
| Padding (*) ...
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
protected Task SendHeadersWithPaddingAndPriorityAsync(int streamId, IEnumerable<KeyValuePair<string, string>> headers, byte padLength, byte priority, int streamDependency, bool endStream)
|
||||
{
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_runningStreams[streamId] = tcs;
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.PADDED | Http2HeadersFrameFlags.PRIORITY, streamId);
|
||||
frame.HeadersPadLength = padLength;
|
||||
frame.HeadersPriorityWeight = priority;
|
||||
frame.HeadersStreamDependency = streamDependency;
|
||||
|
||||
_hpackEncoder.BeginEncode(headers, frame.HeadersPayload, out var length);
|
||||
var extendedHeaderLength = 6; // pad length + stream dependency + weight
|
||||
var buffer = _headerEncodingBuffer.AsSpan();
|
||||
var extendedHeader = buffer.Slice(0, extendedHeaderLength);
|
||||
extendedHeader[0] = padLength;
|
||||
Bitshifter.WriteUInt31BigEndian(extendedHeader.Slice(1), (uint)streamDependency);
|
||||
extendedHeader[5] = priority;
|
||||
var payload = buffer.Slice(extendedHeaderLength, buffer.Length - padLength - extendedHeaderLength);
|
||||
|
||||
frame.PayloadLength = 6 + length + padLength;
|
||||
frame.Payload.Slice(6 + length).Fill(0);
|
||||
_hpackEncoder.BeginEncode(headers, payload, out var length);
|
||||
var padding = buffer.Slice(extendedHeaderLength + length, padLength);
|
||||
padding.Fill(0);
|
||||
|
||||
frame.PayloadLength = extendedHeaderLength + length + padLength;
|
||||
|
||||
if (endStream)
|
||||
{
|
||||
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
|
||||
}
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
writableBuffer.Write(buffer.Slice(0, frame.PayloadLength));
|
||||
return FlushAsync(writableBuffer);
|
||||
}
|
||||
|
||||
protected Task WaitForAllStreamsAsync()
|
||||
|
|
@ -450,324 +523,428 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
|
||||
protected Task SendPreambleAsync() => SendAsync(new ArraySegment<byte>(Http2Connection.ClientPreface));
|
||||
|
||||
protected Task SendSettingsAsync()
|
||||
protected async Task SendSettingsAsync()
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||
return SendAsync(frame.Raw);
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
|
||||
var settings = _clientSettings.GetNonProtocolDefaults();
|
||||
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
|
||||
frame.PayloadLength = payload.Length;
|
||||
Http2FrameWriter.WriteSettings(settings, payload);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendSettingsAckWithInvalidLengthAsync(int length)
|
||||
protected async Task SendSettingsAckWithInvalidLengthAsync(int length)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.ACK);
|
||||
frame.PayloadLength = length;
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
await SendAsync(new byte[length]);
|
||||
}
|
||||
|
||||
protected Task SendSettingsWithInvalidStreamIdAsync(int streamId)
|
||||
protected async Task SendSettingsWithInvalidStreamIdAsync(int streamId)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
|
||||
frame.StreamId = streamId;
|
||||
return SendAsync(frame.Raw);
|
||||
var settings = _clientSettings.GetNonProtocolDefaults();
|
||||
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
|
||||
frame.PayloadLength = payload.Length;
|
||||
Http2FrameWriter.WriteSettings(settings, payload);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendSettingsWithInvalidLengthAsync(int length)
|
||||
protected async Task SendSettingsWithInvalidLengthAsync(int length)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE, _clientSettings.GetNonProtocolDefaults());
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
|
||||
|
||||
frame.PayloadLength = length;
|
||||
return SendAsync(frame.Raw);
|
||||
var payload = new byte[length];
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value)
|
||||
protected async Task SendSettingsWithInvalidParameterValueAsync(Http2SettingsParameter parameter, uint value)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
|
||||
frame.PayloadLength = 6;
|
||||
var payload = new byte[Http2FrameReader.SettingSize];
|
||||
payload[0] = (byte)((ushort)parameter >> 8);
|
||||
payload[1] = (byte)(ushort)parameter;
|
||||
payload[2] = (byte)(value >> 24);
|
||||
payload[3] = (byte)(value >> 16);
|
||||
payload[4] = (byte)(value >> 8);
|
||||
payload[5] = (byte)value;
|
||||
|
||||
frame.Payload[0] = (byte)((ushort)parameter >> 8);
|
||||
frame.Payload[1] = (byte)(ushort)parameter;
|
||||
frame.Payload[2] = (byte)(value >> 24);
|
||||
frame.Payload[3] = (byte)(value >> 16);
|
||||
frame.Payload[4] = (byte)(value >> 8);
|
||||
frame.Payload[5] = (byte)value;
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendPushPromiseFrameAsync()
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var writableBuffer = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PayloadLength = 0;
|
||||
frame.Type = Http2FrameType.PUSH_PROMISE;
|
||||
frame.StreamId = 1;
|
||||
return SendAsync(frame.Raw);
|
||||
|
||||
Http2FrameWriter.WriteHeader(frame, writableBuffer);
|
||||
return FlushAsync(writableBuffer);
|
||||
}
|
||||
|
||||
protected async Task<bool> SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, IEnumerable<KeyValuePair<string, string>> headers)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareHeaders(flags, streamId);
|
||||
var done = _hpackEncoder.BeginEncode(headers, frame.Payload, out var length);
|
||||
var buffer = _headerEncodingBuffer.AsMemory();
|
||||
var done = _hpackEncoder.BeginEncode(headers, buffer.Span, out var length);
|
||||
frame.PayloadLength = length;
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(buffer.Span.Slice(0, length));
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
protected Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock)
|
||||
protected async Task SendHeadersAsync(int streamId, Http2HeadersFrameFlags flags, byte[] headerBlock)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareHeaders(flags, streamId);
|
||||
frame.PayloadLength = headerBlock.Length;
|
||||
headerBlock.CopyTo(frame.HeadersPayload);
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(headerBlock);
|
||||
}
|
||||
|
||||
protected Task SendInvalidHeadersFrameAsync(int streamId, int payloadLength, byte padLength)
|
||||
protected async Task SendInvalidHeadersFrameAsync(int streamId, int payloadLength, byte padLength)
|
||||
{
|
||||
Assert.True(padLength >= payloadLength, $"{nameof(padLength)} must be greater than or equal to {nameof(payloadLength)} to create an invalid frame.");
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.PADDED, streamId);
|
||||
frame.Payload[0] = padLength;
|
||||
|
||||
// Set length last so .Payload can be written to
|
||||
frame.PayloadLength = payloadLength;
|
||||
var payload = new byte[payloadLength];
|
||||
if (payloadLength > 0)
|
||||
{
|
||||
payload[0] = padLength;
|
||||
}
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendIncompleteHeadersFrameAsync(int streamId)
|
||||
protected async Task SendIncompleteHeadersFrameAsync(int streamId)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareHeaders(Http2HeadersFrameFlags.END_HEADERS, streamId);
|
||||
frame.PayloadLength = 3;
|
||||
|
||||
var payload = new byte[3];
|
||||
// Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
|
||||
// with an incomplete new name
|
||||
frame.Payload[0] = 0;
|
||||
frame.Payload[1] = 2;
|
||||
frame.Payload[2] = (byte)'a';
|
||||
payload[0] = 0;
|
||||
payload[1] = 2;
|
||||
payload[2] = (byte)'a';
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected async Task<bool> SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareContinuation(flags, streamId);
|
||||
var done = _hpackEncoder.Encode(frame.Payload, out var length);
|
||||
var buffer = _headerEncodingBuffer.AsMemory();
|
||||
var done = _hpackEncoder.Encode(buffer.Span, out var length);
|
||||
frame.PayloadLength = length;
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(buffer.Span.Slice(0, length));
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
protected async Task SendContinuationAsync(int streamId, Http2ContinuationFrameFlags flags, byte[] payload)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareContinuation(flags, streamId);
|
||||
frame.PayloadLength = payload.Length;
|
||||
payload.CopyTo(frame.Payload);
|
||||
|
||||
await SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendEmptyContinuationFrameAsync(int streamId, Http2ContinuationFrameFlags flags)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareContinuation(flags, streamId);
|
||||
frame.PayloadLength = 0;
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return FlushAsync(outputWriter);
|
||||
}
|
||||
|
||||
protected Task SendIncompleteContinuationFrameAsync(int streamId)
|
||||
protected async Task SendIncompleteContinuationFrameAsync(int streamId)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareContinuation(Http2ContinuationFrameFlags.END_HEADERS, streamId);
|
||||
frame.PayloadLength = 3;
|
||||
|
||||
var payload = new byte[3];
|
||||
// Set up an incomplete Literal Header Field w/ Incremental Indexing frame,
|
||||
// with an incomplete new name
|
||||
frame.Payload[0] = 0;
|
||||
frame.Payload[1] = 2;
|
||||
frame.Payload[2] = (byte)'a';
|
||||
payload[0] = 0;
|
||||
payload[1] = 2;
|
||||
payload[2] = (byte)'a';
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
await SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendDataAsync(int streamId, Span<byte> data, bool endStream)
|
||||
protected Task SendDataAsync(int streamId, Memory<byte> data, bool endStream)
|
||||
{
|
||||
var frame = new Http2Frame((uint)data.Length);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareData(streamId);
|
||||
frame.PayloadLength = data.Length;
|
||||
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
|
||||
data.CopyTo(frame.DataPayload);
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return SendAsync(data.Span);
|
||||
}
|
||||
|
||||
protected Task SendDataWithPaddingAsync(int streamId, Span<byte> data, byte padLength, bool endStream)
|
||||
protected async Task SendDataWithPaddingAsync(int streamId, Memory<byte> data, byte padLength, bool endStream)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareData(streamId, padLength);
|
||||
frame.PayloadLength = data.Length + 1 + padLength;
|
||||
data.CopyTo(frame.DataPayload);
|
||||
|
||||
if (endStream)
|
||||
{
|
||||
frame.DataFlags |= Http2DataFrameFlags.END_STREAM;
|
||||
}
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
outputWriter.GetSpan(1)[0] = padLength;
|
||||
outputWriter.Advance(1);
|
||||
await SendAsync(data.Span);
|
||||
await SendAsync(new byte[padLength]);
|
||||
}
|
||||
|
||||
protected Task SendInvalidDataFrameAsync(int streamId, int frameLength, byte padLength)
|
||||
{
|
||||
Assert.True(padLength >= frameLength, $"{nameof(padLength)} must be greater than or equal to {nameof(frameLength)} to create an invalid frame.");
|
||||
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
|
||||
frame.PrepareData(streamId);
|
||||
frame.DataFlags = Http2DataFrameFlags.PADDED;
|
||||
frame.Payload[0] = padLength;
|
||||
|
||||
// Set length last so .Payload can be written to
|
||||
frame.PayloadLength = frameLength;
|
||||
var payload = new byte[frameLength];
|
||||
if (frameLength > 0)
|
||||
{
|
||||
payload[0] = padLength;
|
||||
}
|
||||
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendPingAsync(Http2PingFrameFlags flags)
|
||||
{
|
||||
var pingFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var pingFrame = new Http2Frame();
|
||||
pingFrame.PreparePing(flags);
|
||||
return SendAsync(pingFrame.Raw);
|
||||
Http2FrameWriter.WriteHeader(pingFrame, outputWriter);
|
||||
return SendAsync(new byte[8]); // Empty payload
|
||||
}
|
||||
|
||||
protected Task SendPingWithInvalidLengthAsync(int length)
|
||||
{
|
||||
var pingFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var pingFrame = new Http2Frame();
|
||||
pingFrame.PreparePing(Http2PingFrameFlags.NONE);
|
||||
pingFrame.PayloadLength = length;
|
||||
return SendAsync(pingFrame.Raw);
|
||||
Http2FrameWriter.WriteHeader(pingFrame, outputWriter);
|
||||
return SendAsync(new byte[length]);
|
||||
}
|
||||
|
||||
protected Task SendPingWithInvalidStreamIdAsync(int streamId)
|
||||
{
|
||||
Assert.NotEqual(0, streamId);
|
||||
|
||||
var pingFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var pingFrame = new Http2Frame();
|
||||
pingFrame.PreparePing(Http2PingFrameFlags.NONE);
|
||||
pingFrame.StreamId = streamId;
|
||||
return SendAsync(pingFrame.Raw);
|
||||
Http2FrameWriter.WriteHeader(pingFrame, outputWriter);
|
||||
return SendAsync(new byte[pingFrame.PayloadLength]);
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.3
|
||||
+-+-------------------------------------------------------------+
|
||||
|E| Stream Dependency (31) |
|
||||
+-+-------------+-----------------------------------------------+
|
||||
| Weight (8) |
|
||||
+-+-------------+
|
||||
*/
|
||||
protected Task SendPriorityAsync(int streamId, int streamDependency = 0)
|
||||
{
|
||||
var priorityFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var priorityFrame = new Http2Frame();
|
||||
priorityFrame.PreparePriority(streamId, streamDependency: streamDependency, exclusive: false, weight: 0);
|
||||
return SendAsync(priorityFrame.Raw);
|
||||
|
||||
var payload = new byte[priorityFrame.PayloadLength].AsSpan();
|
||||
Bitshifter.WriteUInt31BigEndian(payload, (uint)streamDependency);
|
||||
payload[4] = 0; // Weight
|
||||
|
||||
Http2FrameWriter.WriteHeader(priorityFrame, outputWriter);
|
||||
return SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendInvalidPriorityFrameAsync(int streamId, int length)
|
||||
{
|
||||
var priorityFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var priorityFrame = new Http2Frame();
|
||||
priorityFrame.PreparePriority(streamId, streamDependency: 0, exclusive: false, weight: 0);
|
||||
priorityFrame.PayloadLength = length;
|
||||
return SendAsync(priorityFrame.Raw);
|
||||
|
||||
Http2FrameWriter.WriteHeader(priorityFrame, outputWriter);
|
||||
return SendAsync(new byte[length]);
|
||||
}
|
||||
|
||||
/* https://tools.ietf.org/html/rfc7540#section-6.4
|
||||
+---------------------------------------------------------------+
|
||||
| Error Code (32) |
|
||||
+---------------------------------------------------------------+
|
||||
*/
|
||||
protected Task SendRstStreamAsync(int streamId)
|
||||
{
|
||||
var rstStreamFrame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var rstStreamFrame = new Http2Frame();
|
||||
rstStreamFrame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL);
|
||||
return SendAsync(rstStreamFrame.Raw);
|
||||
var payload = new byte[rstStreamFrame.PayloadLength];
|
||||
BinaryPrimitives.WriteUInt32BigEndian(payload, (uint)Http2ErrorCode.CANCEL);
|
||||
|
||||
Http2FrameWriter.WriteHeader(rstStreamFrame, outputWriter);
|
||||
return SendAsync(payload);
|
||||
}
|
||||
|
||||
protected Task SendInvalidRstStreamFrameAsync(int streamId, int length)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareRstStream(streamId, Http2ErrorCode.CANCEL);
|
||||
frame.PayloadLength = length;
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return SendAsync(new byte[length]);
|
||||
}
|
||||
|
||||
protected Task SendGoAwayAsync()
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR);
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return SendAsync(new byte[frame.PayloadLength]);
|
||||
}
|
||||
|
||||
protected Task SendInvalidGoAwayFrameAsync()
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareGoAway(0, Http2ErrorCode.NO_ERROR);
|
||||
frame.StreamId = 1;
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return SendAsync(new byte[frame.PayloadLength]);
|
||||
}
|
||||
|
||||
protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
var buffer = outputWriter.GetSpan(4);
|
||||
Bitshifter.WriteUInt31BigEndian(buffer, (uint)sizeIncrement);
|
||||
outputWriter.Advance(4);
|
||||
return FlushAsync(outputWriter);
|
||||
}
|
||||
|
||||
protected Task SendInvalidWindowUpdateAsync(int streamId, int sizeIncrement, int length)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.PrepareWindowUpdate(streamId, sizeIncrement);
|
||||
frame.PayloadLength = length;
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return SendAsync(new byte[length]);
|
||||
}
|
||||
|
||||
protected Task SendUnknownFrameTypeAsync(int streamId, int frameType)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var outputWriter = _pair.Application.Output;
|
||||
var frame = new Http2Frame();
|
||||
frame.StreamId = streamId;
|
||||
frame.Type = (Http2FrameType)frameType;
|
||||
frame.PayloadLength = 0;
|
||||
return SendAsync(frame.Raw);
|
||||
Http2FrameWriter.WriteHeader(frame, outputWriter);
|
||||
return FlushAsync(outputWriter);
|
||||
}
|
||||
|
||||
protected async Task<Http2Frame> ReceiveFrameAsync(uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize)
|
||||
protected async Task<Http2FrameWithPayload> ReceiveFrameAsync(uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize)
|
||||
{
|
||||
var frame = new Http2Frame(maxFrameSize);
|
||||
var frame = new Http2FrameWithPayload();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var result = await _pair.Application.Input.ReadAsync().AsTask().DefaultTimeout();
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
var examined = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(buffer.Length > 0);
|
||||
|
||||
if (Http2FrameReader.ReadFrame(buffer, frame, maxFrameSize, out consumed, out examined))
|
||||
if (Http2FrameReader.ReadFrame(buffer, frame, maxFrameSize, out var framePayload))
|
||||
{
|
||||
consumed = examined = framePayload.End;
|
||||
frame.Payload = framePayload.ToArray();
|
||||
return frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = buffer.End;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
|
|
@ -781,7 +958,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
}
|
||||
}
|
||||
|
||||
protected async Task<Http2Frame> ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId)
|
||||
protected async Task<Http2FrameWithPayload> ExpectAsync(Http2FrameType type, int withLength, byte withFlags, int withStreamId)
|
||||
{
|
||||
var frame = await ReceiveFrameAsync((uint)withLength);
|
||||
|
||||
|
|
@ -864,5 +1041,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
Assert.Equal(header.Value, value, ignoreCase: true);
|
||||
}
|
||||
}
|
||||
|
||||
public class Http2FrameWithPayload : Http2Frame
|
||||
{
|
||||
public Http2FrameWithPayload() : base()
|
||||
{
|
||||
}
|
||||
|
||||
// This does not contain extended headers
|
||||
public Memory<byte> Payload { get; set; }
|
||||
|
||||
public ReadOnlySequence<byte> PayloadSequence => new ReadOnlySequence<byte>(Payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,21 +92,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests.Http2
|
|||
|
||||
private async Task<Http2Frame> ReceiveFrameAsync(PipeReader reader)
|
||||
{
|
||||
var frame = new Http2Frame(Http2PeerSettings.MinAllowedMaxFrameSize);
|
||||
var frame = new Http2Frame();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var result = await reader.ReadAsync();
|
||||
var buffer = result.Buffer;
|
||||
var consumed = buffer.Start;
|
||||
var examined = buffer.End;
|
||||
var examined = buffer.Start;
|
||||
|
||||
try
|
||||
{
|
||||
if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out consumed, out examined))
|
||||
if (Http2FrameReader.ReadFrame(buffer, frame, 16_384, out var framePayload))
|
||||
{
|
||||
consumed = examined = framePayload.End;
|
||||
return frame;
|
||||
}
|
||||
else
|
||||
{
|
||||
examined = buffer.End;
|
||||
}
|
||||
|
||||
if (result.IsCompleted)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue