diff --git a/NuGet.config b/NuGet.config index d263ff7579a8..a972af091630 100644 --- a/NuGet.config +++ b/NuGet.config @@ -6,10 +6,10 @@ - + - + @@ -30,10 +30,10 @@ - + - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 216f52bfdd7d..176de8a62280 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,37 +9,37 @@ --> - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d00955545e8afc997726aead9b0e6103b1ceade6 + 0118cb6810a48869bf7494aabd86ef44da5940a3 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -121,9 +121,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -139,7 +139,7 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -185,9 +185,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://github.com/dotnet/source-build-externals @@ -207,13 +207,13 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 2d7eea252964e69be94cb9c847b371b23e4dd470 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 81cabf2857a01351e5ab578947c7403a5b128ad1 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 5535e31a712343a63f5d7d796cd874e563e5ac14 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -275,17 +275,17 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 81cabf2857a01351e5ab578947c7403a5b128ad1 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -316,22 +316,22 @@ Win-x64 is used here because we have picked an arbitrary runtime identifier to flow the version of the latest NETCore.App runtime. All Runtime.$rid packages should have the same version. --> - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://github.com/dotnet/xdt @@ -368,34 +368,34 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 1584e493603cfc4e9b36b77d6d4afe97de6363f9 + 50c4cb9fc31c47f03eac865d7bc518af173b74b7 https://github.com/dotnet/winforms abda8e3bfa78319363526b5a5f86863ec979940e - + https://github.com/dotnet/arcade - c255aae7f2b128fa20a4441f0e192c3c53561621 + a319ada170a54ee87c7a81e3309948e3d3ea7aca - + https://github.com/dotnet/arcade - c255aae7f2b128fa20a4441f0e192c3c53561621 + a319ada170a54ee87c7a81e3309948e3d3ea7aca - + https://github.com/dotnet/arcade - c255aae7f2b128fa20a4441f0e192c3c53561621 + a319ada170a54ee87c7a81e3309948e3d3ea7aca - + https://github.com/dotnet/arcade - c255aae7f2b128fa20a4441f0e192c3c53561621 + a319ada170a54ee87c7a81e3309948e3d3ea7aca - + https://github.com/dotnet/arcade - c255aae7f2b128fa20a4441f0e192c3c53561621 + a319ada170a54ee87c7a81e3309948e3d3ea7aca https://github.com/dotnet/extensions diff --git a/eng/Versions.props b/eng/Versions.props index a4361bbfd167..5d05883df000 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,10 +8,10 @@ 8 0 - 14 + 15 - true + false 7.1.2 7.* 8.0.2 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14-servicing.25111.18 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15-servicing.25164.13 8.0.0 8.0.1 8.0.0 @@ -93,7 +93,7 @@ 8.0.0 8.0.0 8.0.0 - 8.0.14-servicing.25111.18 + 8.0.15-servicing.25164.13 8.0.1 8.0.1 8.0.1 @@ -109,11 +109,11 @@ 8.0.0 8.0.2 8.0.0 - 8.0.14-servicing.25111.18 + 8.0.15-servicing.25164.13 8.0.1 8.0.1 - 8.0.1 - 8.0.0 + 8.0.2 + 8.0.1 8.0.0-rtm.23520.14 8.0.0 8.0.1 @@ -129,9 +129,9 @@ 8.0.0 8.0.0 8.0.0 - 8.0.14-servicing.25111.18 + 8.0.15-servicing.25164.13 - 8.0.14-servicing.25111.18 + 8.0.15-servicing.25164.13 8.0.0 8.0.1 @@ -143,14 +143,14 @@ 8.1.0-preview.23604.1 8.1.0-preview.23604.1 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 - 8.0.14 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 + 8.0.15 4.8.0-7.24574.2 4.8.0-7.24574.2 @@ -162,9 +162,9 @@ 6.2.4 6.2.4 - 8.0.0-beta.25060.1 - 8.0.0-beta.25060.1 - 8.0.0-beta.25060.1 + 8.0.0-beta.25111.4 + 8.0.0-beta.25111.4 + 8.0.0-beta.25111.4 8.0.0-alpha.1.25104.1 diff --git a/eng/common/sdk-task.ps1 b/eng/common/sdk-task.ps1 index 73828dd30d31..4f0546dce120 100644 --- a/eng/common/sdk-task.ps1 +++ b/eng/common/sdk-task.ps1 @@ -64,7 +64,7 @@ try { $GlobalJson.tools | Add-Member -Name "vs" -Value (ConvertFrom-Json "{ `"version`": `"16.5`" }") -MemberType NoteProperty } if( -not ($GlobalJson.tools.PSObject.Properties.Name -match "xcopy-msbuild" )) { - $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.8.1-2" -MemberType NoteProperty + $GlobalJson.tools | Add-Member -Name "xcopy-msbuild" -Value "17.12.0" -MemberType NoteProperty } if ($GlobalJson.tools."xcopy-msbuild".Trim() -ine "none") { $xcopyMSBuildToolsFolder = InitializeXCopyMSBuild $GlobalJson.tools."xcopy-msbuild" -install $true diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml index 9f4c600ac094..41bbb915736a 100644 --- a/eng/common/templates/steps/source-build.yml +++ b/eng/common/templates/steps/source-build.yml @@ -44,7 +44,7 @@ steps: # in the default public locations. internalRuntimeDownloadArgs= if [ '$(dotnetbuilds-internal-container-read-token-base64)' != '$''(dotnetbuilds-internal-container-read-token-base64)' ]; then - internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://ci.dot.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://ci.dot.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' + internalRuntimeDownloadArgs='/p:DotNetRuntimeSourceFeed=https://dotnetbuilds.blob.core.windows.net/internal /p:DotNetRuntimeSourceFeedKey=$(dotnetbuilds-internal-container-read-token-base64) --runtimesourcefeed https://dotnetbuilds.blob.core.windows.net/internal --runtimesourcefeedkey $(dotnetbuilds-internal-container-read-token-base64)' fi buildConfig=Release diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 60352ede194e..a00577ed17aa 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -384,8 +384,8 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = # If the version of msbuild is going to be xcopied, # use this version. Version matches a package here: - # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.8.1-2 - $defaultXCopyMSBuildVersion = '17.8.1-2' + # https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/RoslynTools.MSBuild/versions/17.12.0 + $defaultXCopyMSBuildVersion = '17.12.0' if (!$vsRequirements) { if (Get-Member -InputObject $GlobalJson.tools -Name 'vs') { diff --git a/global.json b/global.json index 3002fbd32cbe..9d3930333424 100644 --- a/global.json +++ b/global.json @@ -25,8 +25,8 @@ }, "msbuild-sdks": { "Yarn.MSBuild": "1.22.19", - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.25060.1", - "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.25060.1" + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.25111.4", + "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.25111.4" }, "native-tools": { "jdk": "latest" diff --git a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/main.cpp b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/main.cpp index 86c764df8533..3e1bbc1add95 100644 --- a/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/main.cpp +++ b/src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/main.cpp @@ -8,5 +8,5 @@ DECLARE_DEBUG_PRINT_OBJECT2("tests", ASPNETCORE_DEBUG_FLAG_INFO | ASPNETCORE_DEB int wmain(int argc, wchar_t* argv[]) { ::testing::InitGoogleTest(&argc, argv); - RUN_ALL_TESTS(); + return RUN_ALL_TESTS(); } diff --git a/src/Servers/Kestrel/Core/src/CoreStrings.resx b/src/Servers/Kestrel/Core/src/CoreStrings.resx index 2c7c3e8ef18d..68908731bf54 100644 --- a/src/Servers/Kestrel/Core/src/CoreStrings.resx +++ b/src/Servers/Kestrel/Core/src/CoreStrings.resx @@ -734,4 +734,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l Call UseKestrelHttpsConfiguration() on IWebHostBuilder to automatically enable HTTPS when an https:// address is used. - + + The client sent a {frameType} frame to a control stream that was too large. + + \ No newline at end of file diff --git a/src/Servers/Kestrel/Core/src/Http3Limits.cs b/src/Servers/Kestrel/Core/src/Http3Limits.cs index 0d7801e48bf8..b6556557a340 100644 --- a/src/Servers/Kestrel/Core/src/Http3Limits.cs +++ b/src/Servers/Kestrel/Core/src/Http3Limits.cs @@ -37,7 +37,7 @@ internal int HeaderTableSize /// /// Indicates the size of the maximum allowed size of a request header field sequence. This limit applies to both name and value sequences in their compressed and uncompressed representations. /// - /// Value must be greater than 0, defaults to 2^14 (16,384). + /// Value must be greater than 0, defaults to 2^15 (32,768). /// /// public int MaxRequestHeaderFieldSize diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs index 95dbbcb8e4d5..ce1e9b0db815 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Data.cs @@ -7,7 +7,7 @@ internal partial class Http3RawFrame { public void PrepareData() { - Length = 0; + RemainingLength = 0; Type = Http3FrameType.Data; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs index fe2eb3a6e42e..de1a73cb830e 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.GoAway.cs @@ -7,7 +7,7 @@ internal partial class Http3RawFrame { public void PrepareGoAway() { - Length = 0; + RemainingLength = 0; Type = Http3FrameType.GoAway; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs index bcf65929694d..11e8c971ff21 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Headers.cs @@ -7,7 +7,7 @@ internal partial class Http3RawFrame { public void PrepareHeaders() { - Length = 0; + RemainingLength = 0; Type = Http3FrameType.Headers; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs index 9e74e07db5b8..03ed2a670250 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.Settings.cs @@ -7,7 +7,7 @@ internal partial class Http3RawFrame { public void PrepareSettings() { - Length = 0; + RemainingLength = 0; Type = Http3FrameType.Settings; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs index 076b9640d0bb..5839d515524c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Frames/Http3RawFrame.cs @@ -9,7 +9,7 @@ namespace System.Net.Http; internal partial class Http3RawFrame #pragma warning restore CA1852 // Seal internal types { - public long Length { get; set; } + public long RemainingLength { get; set; } public Http3FrameType Type { get; internal set; } @@ -17,6 +17,6 @@ internal partial class Http3RawFrame public override string ToString() { - return $"{FormattedType} Length: {Length}"; + return $"{FormattedType} Length: {RemainingLength}"; } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs index 599a55f50212..5bb9452a5da1 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics; using System.Globalization; using System.IO.Pipelines; using System.Net.Http; @@ -19,13 +20,18 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem private const int EncoderStreamTypeId = 2; private const int DecoderStreamTypeId = 3; + // Arbitrarily chosen max frame length + // ControlStream frames currently are very small, either a single variable length integer (max 8 bytes), two variable length integers, + // or in the case of SETTINGS a small collection of two variable length integers + // We'll use a generous value of 10k in case new optional frame(s) are added that might be a little larger than the current frames. + private const int MaxFrameSize = 10_000; + private readonly Http3FrameWriter _frameWriter; private readonly Http3StreamContext _context; private readonly Http3PeerSettings _serverPeerSettings; private readonly IStreamIdFeature _streamIdFeature; private readonly IStreamClosedFeature _streamClosedFeature; private readonly IProtocolErrorCodeFeature _errorCodeFeature; - private readonly Http3RawFrame _incomingFrame = new Http3RawFrame(); private volatile int _isClosed; private long _headerType; private readonly object _completionLock = new(); @@ -159,9 +165,9 @@ private async ValueTask TryReadStreamHeaderAsync() { if (!readableBuffer.IsEmpty) { - var id = VariableLengthIntegerHelper.GetInteger(readableBuffer, out consumed, out examined); - if (id != -1) + if (VariableLengthIntegerHelper.TryGetInteger(readableBuffer, out consumed, out var id)) { + examined = consumed; return id; } } @@ -240,6 +246,8 @@ public async Task ProcessRequestAsync(IHttpApplication appli } finally { + await _context.StreamContext.DisposeAsync(); + ApplyCompletionFlag(StreamCompletionFlags.Completed); _context.StreamLifetimeHandler.OnStreamCompleted(this); } @@ -247,6 +255,8 @@ public async Task ProcessRequestAsync(IHttpApplication appli private async Task HandleControlStream() { + var incomingFrame = new Http3RawFrame(); + var isContinuedFrame = false; while (_isClosed == 0) { var result = await Input.ReadAsync(); @@ -259,12 +269,33 @@ private async Task HandleControlStream() if (!readableBuffer.IsEmpty) { // need to kick off httpprotocol process request async here. - while (Http3FrameReader.TryReadFrame(ref readableBuffer, _incomingFrame, out var framePayload)) + while (Http3FrameReader.TryReadFrame(ref readableBuffer, incomingFrame, isContinuedFrame, out var framePayload)) { - Log.Http3FrameReceived(_context.ConnectionId, _streamIdFeature.StreamId, _incomingFrame); - - consumed = examined = framePayload.End; - await ProcessHttp3ControlStream(framePayload); + Debug.Assert(incomingFrame.RemainingLength >= framePayload.Length); + + // Only log when parsing the beginning of the frame + if (!isContinuedFrame) + { + Log.Http3FrameReceived(_context.ConnectionId, _streamIdFeature.StreamId, incomingFrame); + } + + examined = framePayload.End; + await ProcessHttp3ControlStream(incomingFrame, isContinuedFrame, framePayload, out consumed); + + if (incomingFrame.RemainingLength == framePayload.Length) + { + Debug.Assert(framePayload.Slice(0, consumed).Length == framePayload.Length); + + incomingFrame.RemainingLength = 0; + isContinuedFrame = false; + } + else + { + incomingFrame.RemainingLength -= framePayload.Slice(0, consumed).Length; + isContinuedFrame = true; + + Debug.Assert(incomingFrame.RemainingLength > 0); + } } } @@ -294,56 +325,71 @@ private async ValueTask HandleEncodingDecodingTask() } } - private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence payload) + private ValueTask ProcessHttp3ControlStream(Http3RawFrame incomingFrame, bool isContinuedFrame, in ReadOnlySequence payload, out SequencePosition consumed) { - switch (_incomingFrame.Type) + // default to consuming the entire payload, this is so that we don't need to set consumed from all the frame types that aren't implemented yet. + // individual frame types can set consumed if they're implemented and want to be able to partially consume the payload. + consumed = payload.End; + switch (incomingFrame.Type) { case Http3FrameType.Data: case Http3FrameType.Headers: case Http3FrameType.PushPromise: - // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2 - throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame); + // https://www.rfc-editor.org/rfc/rfc9114.html#section-8.1-2.12.1 + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame); case Http3FrameType.Settings: - return ProcessSettingsFrameAsync(payload); + CheckMaxFrameSize(incomingFrame); + return ProcessSettingsFrameAsync(isContinuedFrame, payload, out consumed); case Http3FrameType.GoAway: - return ProcessGoAwayFrameAsync(); + return ProcessGoAwayFrameAsync(isContinuedFrame, incomingFrame, payload, out consumed); case Http3FrameType.CancelPush: - return ProcessCancelPushFrameAsync(); + return ProcessCancelPushFrameAsync(incomingFrame, payload, out consumed); case Http3FrameType.MaxPushId: - return ProcessMaxPushIdFrameAsync(); + return ProcessMaxPushIdFrameAsync(incomingFrame, payload, out consumed); default: - return ProcessUnknownFrameAsync(_incomingFrame.Type); + CheckMaxFrameSize(incomingFrame); + return ProcessUnknownFrameAsync(incomingFrame.Type); } - } - private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence payload) - { - if (_haveReceivedSettingsFrame) + static void CheckMaxFrameSize(Http3RawFrame http3RawFrame) { - // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-settings - throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame); + // Not part of the RFC, but it's a good idea to limit the size of frames when we know they're supposed to be small. + if (http3RawFrame.RemainingLength >= MaxFrameSize) + { + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamFrameTooLarge(http3RawFrame.FormattedType), Http3ErrorCode.FrameError); + } } + } - _haveReceivedSettingsFrame = true; - _streamClosedFeature.OnClosed(static state => + private ValueTask ProcessSettingsFrameAsync(bool isContinuedFrame, ReadOnlySequence payload, out SequencePosition consumed) + { + if (!isContinuedFrame) { - var stream = (Http3ControlStream)state!; - stream.OnStreamClosed(); - }, this); + if (_haveReceivedSettingsFrame) + { + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.4 + throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame); + } + + _haveReceivedSettingsFrame = true; + _streamClosedFeature.OnClosed(static state => + { + var stream = (Http3ControlStream)state!; + stream.OnStreamClosed(); + }, this); + } while (true) { - var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out _); - if (id == -1) + if (!VariableLengthIntegerHelper.TryGetInteger(payload, out consumed, out var id)) { break; } - payload = payload.Slice(consumed); - - var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out _); - if (value == -1) + if (!VariableLengthIntegerHelper.TryGetInteger(payload.Slice(consumed), out consumed, out var value)) { + // Reset consumed to very start even though we successfully read 1 varint. It's because we want to keep the id for when we have the value as well. + consumed = payload.Start; break; } @@ -382,37 +428,48 @@ private void ProcessSetting(long id, long value) } } - private ValueTask ProcessGoAwayFrameAsync() + private ValueTask ProcessGoAwayFrameAsync(bool isContinuedFrame, Http3RawFrame incomingFrame, ReadOnlySequence payload, out SequencePosition consumed) { - EnsureSettingsFrame(Http3FrameType.GoAway); + // https://www.rfc-editor.org/rfc/rfc9114.html#name-goaway + + // We've already triggered RequestClose since isContinuedFrame is only true + // after we've already parsed the frame type and called the processing function at least once. + if (!isContinuedFrame) + { + EnsureSettingsFrame(Http3FrameType.GoAway); - // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. - _context.Connection.StopProcessingNextRequest(serverInitiated: false); - _context.ConnectionContext.Features.Get()?.RequestClose(); + // StopProcessingNextRequest must be called before RequestClose to ensure it's considered client initiated. + _context.Connection.StopProcessingNextRequest(serverInitiated: false); + _context.ConnectionContext.Features.Get()?.RequestClose(); + } - // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-goaway - // PUSH is not implemented so nothing to do. + // PUSH is not implemented but we still want to parse the frame to do error checking + ParseVarIntWithFrameLengthValidation(incomingFrame, payload, out consumed); // TODO: Double check the connection remains open. return default; } - private ValueTask ProcessCancelPushFrameAsync() + private ValueTask ProcessCancelPushFrameAsync(Http3RawFrame incomingFrame, ReadOnlySequence payload, out SequencePosition consumed) { + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.3 + EnsureSettingsFrame(Http3FrameType.CancelPush); - // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-cancel_push - // PUSH is not implemented so nothing to do. + // PUSH is not implemented but we still want to parse the frame to do error checking + ParseVarIntWithFrameLengthValidation(incomingFrame, payload, out consumed); return default; } - private ValueTask ProcessMaxPushIdFrameAsync() + private ValueTask ProcessMaxPushIdFrameAsync(Http3RawFrame incomingFrame, ReadOnlySequence payload, out SequencePosition consumed) { + // https://www.rfc-editor.org/rfc/rfc9114.html#section-7.2.7 + EnsureSettingsFrame(Http3FrameType.MaxPushId); - // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-cancel_push - // PUSH is not implemented so nothing to do. + // PUSH is not implemented but we still want to parse the frame to do error checking + ParseVarIntWithFrameLengthValidation(incomingFrame, payload, out consumed); return default; } @@ -426,6 +483,23 @@ private ValueTask ProcessUnknownFrameAsync(Http3FrameType frameType) return default; } + // Used for frame types that aren't (fully) implemented yet and contain a single var int as part of their framing. (CancelPush, MaxPushId, GoAway) + // We want to throw an error if the length field of the frame is larger than the spec defined format of the frame. + private static void ParseVarIntWithFrameLengthValidation(Http3RawFrame incomingFrame, ReadOnlySequence payload, out SequencePosition consumed) + { + if (!VariableLengthIntegerHelper.TryGetInteger(payload, out consumed, out _)) + { + return; + } + + if (incomingFrame.RemainingLength > payload.Slice(0, consumed).Length) + { + // https://www.rfc-editor.org/rfc/rfc9114.html#section-10.8 + // An implementation MUST ensure that the length of a frame exactly matches the length of the fields it contains. + throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ControlStreamFrameTooLarge(Http3Formatting.ToFormattedType(incomingFrame.Type)), Http3ErrorCode.FrameError); + } + } + private void EnsureSettingsFrame(Http3FrameType frameType) { if (!_haveReceivedSettingsFrame) diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs index 66740c710f10..2de0472483a1 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameReader.cs @@ -19,36 +19,44 @@ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | Frame Payload (*) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - internal static bool TryReadFrame(ref ReadOnlySequence readableBuffer, Http3RawFrame frame, out ReadOnlySequence framePayload) + // Reads and returns partial frames, don't rely on the frame being complete when using this method + // Set isContinuedFrame to true when expecting to read more of the previous frame + internal static bool TryReadFrame(ref ReadOnlySequence readableBuffer, Http3RawFrame frame, bool isContinuedFrame, out ReadOnlySequence framePayload) { framePayload = ReadOnlySequence.Empty; - SequencePosition consumed; + SequencePosition consumed = readableBuffer.Start; + var length = frame.RemainingLength; - var type = VariableLengthIntegerHelper.GetInteger(readableBuffer, out consumed, out _); - if (type == -1) + if (!isContinuedFrame) { - return false; - } + if (!VariableLengthIntegerHelper.TryGetInteger(readableBuffer, out consumed, out var type)) + { + return false; + } - var firstLengthBuffer = readableBuffer.Slice(consumed); + var firstLengthBuffer = readableBuffer.Slice(consumed); - var length = VariableLengthIntegerHelper.GetInteger(firstLengthBuffer, out consumed, out _); + if (!VariableLengthIntegerHelper.TryGetInteger(firstLengthBuffer, out consumed, out length)) + { + return false; + } - // Make sure the whole frame is buffered - if (length == -1) - { - return false; + frame.RemainingLength = length; + frame.Type = (Http3FrameType)type; } var startOfFramePayload = readableBuffer.Slice(consumed); - if (startOfFramePayload.Length < length) + + // Get all the available bytes or the rest of the frame whichever is less + length = Math.Min(startOfFramePayload.Length, length); + + // If we were expecting a non-empty payload, but haven't received any of it yet, + // there is nothing to process until we wait for more data. + if (length == 0 && frame.RemainingLength != 0) { return false; } - frame.Length = length; - frame.Type = (Http3FrameType)type; - // The remaining payload minus the extra fields framePayload = startOfFramePayload.Slice(0, length); readableBuffer = readableBuffer.Slice(framePayload.End); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs index dbcd774af4d6..44034fa365f2 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3FrameWriter.cs @@ -121,7 +121,7 @@ internal Task WriteSettingsAsync(List settings) WriteSettings(settings, buffer); // Advance pipe writer and flush - _outgoingFrame.Length = totalLength; + _outgoingFrame.RemainingLength = totalLength; _outputWriter.Advance(totalLength); return _outputWriter.FlushAsync().GetAsTask(); @@ -186,7 +186,7 @@ private void WriteDataUnsynchronized(in ReadOnlySequence data, long dataLe return; } - _outgoingFrame.Length = (int)dataLength; + _outgoingFrame.RemainingLength = (int)dataLength; WriteHeaderUnsynchronized(); @@ -209,7 +209,7 @@ void SplitAndWriteDataUnsynchronized(in ReadOnlySequence data, long dataLe do { var currentData = remainingData.Slice(0, dataPayloadLength); - _outgoingFrame.Length = dataPayloadLength; + _outgoingFrame.RemainingLength = dataPayloadLength; WriteHeaderUnsynchronized(); @@ -223,7 +223,7 @@ void SplitAndWriteDataUnsynchronized(in ReadOnlySequence data, long dataLe } while (dataLength > dataPayloadLength); - _outgoingFrame.Length = (int)dataLength; + _outgoingFrame.RemainingLength = (int)dataLength; WriteHeaderUnsynchronized(); @@ -240,7 +240,7 @@ internal ValueTask WriteGoAway(long id) var length = VariableLengthIntegerHelper.GetByteCount(id); - _outgoingFrame.Length = length; + _outgoingFrame.RemainingLength = length; WriteHeaderUnsynchronized(); @@ -253,10 +253,10 @@ internal ValueTask WriteGoAway(long id) private void WriteHeaderUnsynchronized() { _log.Http3FrameSending(_connectionId, _streamIdFeature.StreamId, _outgoingFrame); - var headerLength = WriteHeader(_outgoingFrame.Type, _outgoingFrame.Length, _outputWriter); + var headerLength = WriteHeader(_outgoingFrame.Type, _outgoingFrame.RemainingLength, _outputWriter); // We assume the payload will be written prior to the next flush. - _unflushedBytes += headerLength + _outgoingFrame.Length; + _unflushedBytes += headerLength + _outgoingFrame.RemainingLength; } public ValueTask Write100ContinueAsync() @@ -269,7 +269,7 @@ public ValueTask Write100ContinueAsync() } _outgoingFrame.PrepareHeaders(); - _outgoingFrame.Length = ContinueBytes.Length; + _outgoingFrame.RemainingLength = ContinueBytes.Length; WriteHeaderUnsynchronized(); _outputWriter.Write(ContinueBytes); return TimeFlushUnsynchronizedAsync(); @@ -394,7 +394,7 @@ private void FinishWritingHeaders(int payloadLength, bool done) ValidateHeadersTotalSize(); - _outgoingFrame.Length = _headerEncodingBuffer.WrittenCount; + _outgoingFrame.RemainingLength = _headerEncodingBuffer.WrittenCount; WriteHeaderUnsynchronized(); _outputWriter.Write(_headerEncodingBuffer.WrittenSpan); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs index b6db0bb810db..7dabb9654c56 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3PendingStream.cs @@ -60,8 +60,7 @@ public async ValueTask ReadNextStreamHeaderAsync(Http3StreamContext contex if (!readableBuffer.IsEmpty) { - var value = VariableLengthIntegerHelper.GetInteger(readableBuffer, out consumed, out _); - if (value != -1) + if (VariableLengthIntegerHelper.TryGetInteger(readableBuffer, out consumed, out var value)) { if (!advanceOn.HasValue || value == (long)advanceOn) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs index 61625f180cd2..17178ffbf133 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs @@ -59,7 +59,6 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS private readonly object _completionLock = new(); protected RequestHeaderParsingState _requestHeaderParsingState; - protected readonly Http3RawFrame _incomingFrame = new(); public bool EndStreamReceived => (_completionState & StreamCompletionFlags.EndStreamReceived) == StreamCompletionFlags.EndStreamReceived; public bool IsAborted => (_completionState & StreamCompletionFlags.Aborted) == StreamCompletionFlags.Aborted; @@ -607,6 +606,8 @@ public async Task ProcessRequestAsync(IHttpApplication appli try { + var incomingFrame = new Http3RawFrame(); + var isContinuedFrame = false; while (_isClosed == 0) { var result = await Input.ReadAsync(); @@ -618,12 +619,19 @@ public async Task ProcessRequestAsync(IHttpApplication appli { if (!readableBuffer.IsEmpty) { - while (Http3FrameReader.TryReadFrame(ref readableBuffer, _incomingFrame, out var framePayload)) + while (Http3FrameReader.TryReadFrame(ref readableBuffer, incomingFrame, isContinuedFrame, out var framePayload)) { - Log.Http3FrameReceived(ConnectionId, _streamIdFeature.StreamId, _incomingFrame); + // Only log when parsing the beginning of the frame + if (!isContinuedFrame) + { + Log.Http3FrameReceived(ConnectionId, _streamIdFeature.StreamId, incomingFrame); + } consumed = examined = framePayload.End; - await ProcessHttp3Stream(application, framePayload, result.IsCompleted && readableBuffer.IsEmpty); + await ProcessHttp3Stream(application, incomingFrame, isContinuedFrame, framePayload, result.IsCompleted && readableBuffer.IsEmpty); + + incomingFrame.RemainingLength -= framePayload.Length; + isContinuedFrame = incomingFrame.RemainingLength > 0 ? true : false; } } @@ -746,22 +754,23 @@ private ValueTask OnEndStreamReceived() return RequestBodyPipe.Writer.CompleteAsync(); } - private Task ProcessHttp3Stream(IHttpApplication application, in ReadOnlySequence payload, bool isCompleted) where TContext : notnull + private Task ProcessHttp3Stream(IHttpApplication application, Http3RawFrame incomingFrame, bool isContinuedFrame, + in ReadOnlySequence payload, bool isCompleted) where TContext : notnull { - return _incomingFrame.Type switch + return incomingFrame.Type switch { Http3FrameType.Data => ProcessDataFrameAsync(payload), - Http3FrameType.Headers => ProcessHeadersFrameAsync(application, payload, isCompleted), + Http3FrameType.Headers => ProcessHeadersFrameAsync(application, incomingFrame, isContinuedFrame, payload, isCompleted), // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2.4 // These frames need to be on a control stream Http3FrameType.Settings or Http3FrameType.CancelPush or Http3FrameType.GoAway or Http3FrameType.MaxPushId => throw new Http3ConnectionErrorException( - CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame), + CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame), // The server should never receive push promise Http3FrameType.PushPromise => throw new Http3ConnectionErrorException( - CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame), + CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame), _ => ProcessUnknownFrameAsync(), }; } @@ -773,11 +782,13 @@ private static Task ProcessUnknownFrameAsync() return Task.CompletedTask; } - private async Task ProcessHeadersFrameAsync(IHttpApplication application, ReadOnlySequence payload, bool isCompleted) where TContext : notnull + private async Task ProcessHeadersFrameAsync(IHttpApplication application, Http3RawFrame incomingFrame, bool isContinuedFrame, + ReadOnlySequence payload, bool isCompleted) where TContext : notnull { // HEADERS frame after trailing headers is invalid. // https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1 - if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers) + // Since we parse data as we get it, we can receive partial frames which means we need to check that we're in the middle of handling the trailers header frame + if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers && !isContinuedFrame) { throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame); } @@ -789,8 +800,17 @@ private async Task ProcessHeadersFrameAsync(IHttpApplication try { - QPackDecoder.Decode(payload, endHeaders: true, handler: this); - QPackDecoder.Reset(); + var endHeaders = payload.Length == incomingFrame.RemainingLength; + QPackDecoder.Decode(payload, endHeaders, handler: this); + if (endHeaders) + { + QPackDecoder.Reset(); + } + else + { + // Headers frame isn't complete, return to read more of the frame + return; + } } catch (QPackDecodingException ex) { diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.Http3.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.Http3.cs index 4159c927e531..54e32f258f00 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.Http3.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.Http3.cs @@ -37,7 +37,7 @@ public void Http3FrameReceived(string connectionId, long streamId, Http3RawFrame { if (_http3Logger.IsEnabled(LogLevel.Trace)) { - Http3Log.Http3FrameReceived(_http3Logger, connectionId, Http3Formatting.ToFormattedType(frame.Type), streamId, frame.Length); + Http3Log.Http3FrameReceived(_http3Logger, connectionId, Http3Formatting.ToFormattedType(frame.Type), streamId, frame.RemainingLength); } } @@ -45,7 +45,7 @@ public void Http3FrameSending(string connectionId, long streamId, Http3RawFrame { if (_http3Logger.IsEnabled(LogLevel.Trace)) { - Http3Log.Http3FrameSending(_http3Logger, connectionId, Http3Formatting.ToFormattedType(frame.Type), streamId, frame.Length); + Http3Log.Http3FrameSending(_http3Logger, connectionId, Http3Formatting.ToFormattedType(frame.Type), streamId, frame.RemainingLength); } } diff --git a/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs b/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs index 8b73bd0e2c48..f8fa53170829 100644 --- a/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs +++ b/src/Servers/Kestrel/Core/test/VariableIntHelperTests.cs @@ -14,7 +14,8 @@ public class VariableIntHelperTests [MemberData(nameof(IntegerData))] public void CheckDecoding(long expected, byte[] input) { - var decoded = VariableLengthIntegerHelper.GetInteger(new ReadOnlySequence(input), out _, out _); + var result = VariableLengthIntegerHelper.TryGetInteger(new ReadOnlySequence(input), out _, out var decoded); + Assert.True(result); Assert.Equal(expected, decoded); } diff --git a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs index e02573b7b225..544b338ee38f 100644 --- a/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs +++ b/src/Servers/Kestrel/shared/test/Http3/Http3InMemory.cs @@ -388,15 +388,15 @@ private static long GetOutputResponseBufferSize(ServiceContext serviceContext) return bufferSize ?? 0; } - internal ValueTask CreateControlStream() + internal ValueTask CreateControlStream(PipeScheduler clientWriterScheduler = null) { - return CreateControlStream(id: 0); + return CreateControlStream(id: 0, clientWriterScheduler); } - internal async ValueTask CreateControlStream(int? id) + internal async ValueTask CreateControlStream(int? id, PipeScheduler clientWriterScheduler = null) { var testStreamContext = new TestStreamContext(canRead: true, canWrite: false, this); - testStreamContext.Initialize(streamId: 2); + testStreamContext.Initialize(streamId: 2, clientWriterScheduler); var stream = new Http3ControlStream(this, testStreamContext); _runningStreams[stream.StreamId] = stream; @@ -409,16 +409,17 @@ internal async ValueTask CreateControlStream(int? id) return stream; } - internal async ValueTask CreateRequestStream(IEnumerable> headers, Http3RequestHeaderHandler headerHandler = null, bool endStream = false, TaskCompletionSource tsc = null) + internal async ValueTask CreateRequestStream(IEnumerable> headers, + Http3RequestHeaderHandler headerHandler = null, bool endStream = false, TaskCompletionSource tsc = null, PipeScheduler clientWriterScheduler = null) { - var stream = CreateRequestStreamCore(headerHandler); + var stream = CreateRequestStreamCore(headerHandler, clientWriterScheduler); if (tsc is not null) { stream.StartStreamDisposeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } - if (headers is not null) + if (headers is not null && headers.Any()) { await stream.SendHeadersAsync(headers, endStream); } @@ -430,9 +431,10 @@ internal async ValueTask CreateRequestStream(IEnumerable CreateRequestStream(Http3HeadersEnumerator headers, Http3RequestHeaderHandler headerHandler = null, bool endStream = false, TaskCompletionSource tsc = null) + internal async ValueTask CreateRequestStream(Http3HeadersEnumerator headers, Http3RequestHeaderHandler headerHandler = null, + bool endStream = false, TaskCompletionSource tsc = null, PipeScheduler clientWriterScheduler = null) { - var stream = CreateRequestStreamCore(headerHandler); + var stream = CreateRequestStreamCore(headerHandler, clientWriterScheduler); if (tsc is not null) { @@ -448,7 +450,7 @@ internal async ValueTask CreateRequestStream(Http3HeadersEnu return stream; } - private Http3RequestStream CreateRequestStreamCore(Http3RequestHeaderHandler headerHandler) + private Http3RequestStream CreateRequestStreamCore(Http3RequestHeaderHandler headerHandler, PipeScheduler clientWriterScheduler) { var requestStreamId = GetStreamId(0x00); if (!_streamContextPool.TryDequeue(out var testStreamContext)) @@ -459,7 +461,7 @@ private Http3RequestStream CreateRequestStreamCore(Http3RequestHeaderHandler hea { Logger.LogDebug($"Reusing context for request stream {requestStreamId}."); } - testStreamContext.Initialize(requestStreamId); + testStreamContext.Initialize(requestStreamId, clientWriterScheduler); return new Http3RequestStream(this, Connection, testStreamContext, headerHandler ?? new Http3RequestHeaderHandler()); } @@ -559,7 +561,7 @@ internal async ValueTask ReceiveFrameAsync(bool expectEnd throw new InvalidOperationException("No data received."); } - if (Http3FrameReader.TryReadFrame(ref buffer, frame, out var framePayload)) + if (Http3FrameReader.TryReadFrame(ref buffer, frame, isContinuedFrame: false, out var framePayload)) { consumed = examined = framePayload.End; frame.Payload = framePayload.ToArray(); @@ -837,16 +839,14 @@ internal async ValueTask> ExpectSettingsAsync() var settings = new Dictionary(); while (true) { - var id = VariableLengthIntegerHelper.GetInteger(payload, out var consumed, out _); - if (id == -1) + if (!VariableLengthIntegerHelper.TryGetInteger(payload, out var consumed, out var id)) { break; } payload = payload.Slice(consumed); - var value = VariableLengthIntegerHelper.GetInteger(payload, out consumed, out _); - if (value == -1) + if (!VariableLengthIntegerHelper.TryGetInteger(payload, out consumed, out var value)) { break; } @@ -927,9 +927,9 @@ public async ValueTask TryReadStreamIdAsync() { if (!readableBuffer.IsEmpty) { - var id = VariableLengthIntegerHelper.GetInteger(readableBuffer, out consumed, out examined); - if (id != -1) + if (VariableLengthIntegerHelper.TryGetInteger(readableBuffer, out consumed, out var id)) { + examined = consumed; return id; } } @@ -1005,6 +1005,7 @@ public TestMultiplexedConnectionContext(Http3InMemory testBase) Features.Set(this); Features.Set(this); ConnectionClosedRequested = ConnectionClosingCts.Token; + ConnectionClosed = ConnectionClosedCts.Token; } public override string ConnectionId { get; set; } @@ -1017,6 +1018,8 @@ public TestMultiplexedConnectionContext(Http3InMemory testBase) public CancellationTokenSource ConnectionClosingCts { get; set; } = new CancellationTokenSource(); + public CancellationTokenSource ConnectionClosedCts { get; set; } = new CancellationTokenSource(); + public long Error { get => _error ?? -1; @@ -1033,6 +1036,7 @@ public override void Abort(ConnectionAbortedException abortReason) { ToServerAcceptQueue.Writer.TryComplete(); ToClientAcceptQueue.Writer.TryComplete(); + ConnectionClosedCts.Cancel(); } public override async ValueTask AcceptAsync(CancellationToken cancellationToken = default) @@ -1106,38 +1110,30 @@ public TestStreamContext(bool canRead, bool canWrite, Http3InMemory testBase) _testBase = testBase; } - public void Initialize(long streamId) + public void Initialize(long streamId, PipeScheduler clientWriterScheduler = null) { - if (!_isComplete) - { - // Create new pipes when test stream context is reused rather than reseting them. - // This is required because the client tests read from these directly from these pipes. - // When a request is finished they'll check to see whether there is anymore content - // in the Application.Output pipe. If it has been reset then that code will error. - var inputOptions = Http3InMemory.GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); - var outputOptions = Http3InMemory.GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); - - _inputPipe = new Pipe(inputOptions); - _outputPipe = new Pipe(outputOptions); - - _transportPipeReader = new CompletionPipeReader(_inputPipe.Reader); - _transportPipeWriter = new CompletionPipeWriter(_outputPipe.Writer); - - _pair = new DuplexPipePair( - new DuplexPipe(_transportPipeReader, _transportPipeWriter), - new DuplexPipe(_outputPipe.Reader, _inputPipe.Writer)); - } - else + if (_isComplete) { _pair.Application.Input.Complete(); _pair.Application.Output.Complete(); + } - _transportPipeReader.Reset(); - _transportPipeWriter.Reset(); + // Create new pipes when test stream context is reused rather than reseting them. + // This is required because the client tests read from these directly from these pipes. + // When a request is finished they'll check to see whether there is anymore content + // in the Application.Output pipe. If it has been reset then that code will error. + var inputOptions = Http3InMemory.GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, clientWriterScheduler ?? PipeScheduler.ThreadPool); + var outputOptions = Http3InMemory.GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool); - _inputPipe.Reset(); - _outputPipe.Reset(); - } + _inputPipe = new Pipe(inputOptions); + _outputPipe = new Pipe(outputOptions); + + _transportPipeReader = new CompletionPipeReader(_inputPipe.Reader); + _transportPipeWriter = new CompletionPipeWriter(_outputPipe.Writer); + + _pair = new DuplexPipePair( + new DuplexPipe(_transportPipeReader, _transportPipeWriter), + new DuplexPipe(_outputPipe.Reader, _inputPipe.Writer)); Features.Set(this); Features.Set(this); diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs index b1ff24b43bf8..054577365d7d 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3ConnectionTests.cs @@ -354,6 +354,34 @@ await Http3Api.WaitForConnectionErrorAsync( expectedErrorMessage: CoreStrings.Http3ErrorControlStreamClosed); } + [Theory] + [InlineData((int)Http3FrameType.Settings, 20_000)] + //[InlineData((int)Http3FrameType.GoAway, 30)] // GoAway frames trigger graceful connection close which races with sending FRAME_ERROR + [InlineData((int)Http3FrameType.CancelPush, 30)] + [InlineData((int)Http3FrameType.MaxPushId, 30)] + [InlineData(int.MaxValue, 20_000)] // Unknown frame type + public async Task ControlStream_ClientToServer_LargeFrame_ConnectionError(int frameType, int length) + { + await Http3Api.InitializeConnectionAsync(_noopApplication); + + var controlStream = await Http3Api.CreateControlStream(); + + // Need to send settings frame before other frames, otherwise it's a connection error + if (frameType != (int)Http3FrameType.Settings) + { + await controlStream.SendSettingsAsync(new List()); + } + + await controlStream.SendFrameAsync((Http3FrameType)frameType, new byte[length]); + + await Http3Api.WaitForConnectionErrorAsync( + ignoreNonGoAwayFrames: true, + expectedLastStreamId: 0, + expectedErrorCode: Http3ErrorCode.FrameError, + matchExpectedErrorMessage: AssertExpectedErrorMessages, + expectedErrorMessage: CoreStrings.FormatHttp3ControlStreamFrameTooLarge(Http3Formatting.ToFormattedType((Http3FrameType)frameType))); + } + [Fact] public async Task SETTINGS_MaxFieldSectionSizeSent_ServerReceivesValue() { diff --git a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs index 728bc3458b74..c23e11328e43 100644 --- a/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs +++ b/src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; +using System.Buffers; using System.Globalization; -using System.IO; -using System.Linq; +using System.IO.Pipelines; using System.Net.Http; using System.Runtime.ExceptionServices; using System.Text; @@ -17,6 +15,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; @@ -1982,7 +1981,7 @@ public async Task RequestTrailers_CanReadTrailersFromRequest() var trailers = new[] { new KeyValuePair("TestName", "TestValue"), - }; + }; var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async c => { await c.Request.Body.DrainAsync(default); @@ -2360,6 +2359,21 @@ public Task HEADERS_Received_HeaderBlockOverLimitx2_ConnectionError() return HEADERS_Received_InvalidHeaderFields_StreamError(headers, CoreStrings.BadRequest_HeadersExceedMaxTotalSize, Http3ErrorCode.RequestRejected); } + [Fact] + public Task HEADERS_Received_HeaderValueOverLimit_ConnectionError() + { + var limit = _serviceContext.ServerOptions.Limits.Http3.MaxRequestHeaderFieldSize; + // Single header value exceeds limit + var headers = new[] + { + new KeyValuePair("a", new string('a', limit + 1)), + }; + + return HEADERS_Received_InvalidHeaderFields_StreamError(headers, + SR.Format(SR.net_http_headers_exceeded_length, limit), + Http3ErrorCode.InternalError); + } + [Fact] public async Task HEADERS_Received_TooManyHeaders_431() { @@ -2976,4 +2990,296 @@ public async Task GetMemory_AfterAbort_GetsFakeMemory(int sizeHint) context.Response.BodyWriter.Advance(memory.Length); }, headers); } + + [Fact] + public async Task ControlStream_CloseBeforeSendingSettings() + { + await Http3Api.InitializeConnectionAsync(_noopApplication); + + var outboundcontrolStream = await Http3Api.CreateControlStream(); + + await outboundcontrolStream.EndStreamAsync(); + + await outboundcontrolStream.ReceiveEndAsync(); + } + + [Fact] + public async Task ControlStream_PartialFrameThenClose() + { + await Http3Api.InitializeConnectionAsync(_noopApplication); + + var outboundcontrolStream = await Http3Api.CreateControlStream(); + + var settings = new List + { + new Http3PeerSetting(Internal.Http3.Http3SettingType.MaxFieldSectionSize, 100), + new Http3PeerSetting(Internal.Http3.Http3SettingType.EnableWebTransport, 1), + new Http3PeerSetting(Internal.Http3.Http3SettingType.H3Datagram, 1) + }; + var len = Http3FrameWriter.CalculateSettingsSize(settings); + + Http3FrameWriter.WriteHeader(Http3FrameType.Settings, len, outboundcontrolStream.Pair.Application.Output); + + var parameterLength = VariableLengthIntegerHelper.WriteInteger(outboundcontrolStream.Pair.Application.Output.GetSpan(), (long)Internal.Http3.Http3SettingType.MaxFieldSectionSize); + outboundcontrolStream.Pair.Application.Output.Advance(parameterLength); + await outboundcontrolStream.Pair.Application.Output.FlushAsync(); + + await outboundcontrolStream.EndStreamAsync(); + + await outboundcontrolStream.ReceiveEndAsync(); + } + + [Fact] + public async Task SendDataObservesBackpressureFromApp() + { + var headers = new[] + { + new KeyValuePair(InternalHeaderNames.Method, "Custom"), + new KeyValuePair(InternalHeaderNames.Path, "/"), + new KeyValuePair(InternalHeaderNames.Scheme, "http"), + new KeyValuePair(InternalHeaderNames.Authority, "localhost:80"), + }; + + // Http3Stream hardcodes a 64k size for the RequestBodyPipe there is also the transport Pipe which we can influence with MaxRequestBufferSize + // So we need to send enough to fill up the 64k Pipe as well as the 100 byte Pipe. + var sendSize = 1024 * 65; + _serviceContext.ServerOptions.Limits.MaxRequestBufferSize = 100; + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var startedReadingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(async c => + { + // Read a single byte to make sure data has gotten here before we start verifying backpressure in the test code + var res = await c.Request.BodyReader.ReadAsync(); + Assert.Equal(sendSize, res.Buffer.Length); + c.Request.BodyReader.AdvanceTo(res.Buffer.Slice(1).Start); + startedReadingTcs.SetResult(); + + await tcs.Task; + res = await c.Request.BodyReader.ReadAsync(); + Assert.Equal(sendSize - 1, res.Buffer.Length); + c.Request.BodyReader.AdvanceTo(res.Buffer.End); + }, headers); + + var sendTask = requestStream.SendDataAsync(Encoding.ASCII.GetBytes(new string('a', sendSize))); + + // Wait for "app" code to start reading to ensure it has gotten bytes before we start verifying backpressure + await startedReadingTcs.Task; + Assert.False(sendTask.IsCompleted); + tcs.SetResult(); + + await sendTask; + + var responseHeaders = await requestStream.ExpectHeadersAsync(); + Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]); + + await requestStream.ExpectReceiveEndOfStream(); + } + + [Fact] + public async Task Request_FrameParsingSingleByteAtATimeWorks() + { + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var total = 0; + var trailerValue = string.Empty; + await Http3Api.InitializeConnectionAsync(async context => + { + var buffer = new byte[100]; + var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length); + var captureTcs = tcs; + tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + captureTcs.SetResult(); + Assert.Equal(1, read); + total = read; + while (read > 0) + { + read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total); + captureTcs = tcs; + tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + captureTcs.SetResult(); + total += read; + if (read == 0) + { + break; + } + Assert.Equal(1, read); + } + + trailerValue = context.Request.GetTrailer("TestName"); + }); + + // Use Inline scheduling and buffer size of 1 to guarantee each write will wait for the parsing loop to complete before writing more data + _serviceContext.ServerOptions.Limits.MaxRequestBufferSize = 1; + var stream = await Http3Api.CreateRequestStream(headers: [], clientWriterScheduler: PipeScheduler.Inline); + + // Use local pipe to write frames so we can get the entire buffer in order to write it one byte at a time + var bufferPipe = new Pipe(); + Http3FrameWriter.WriteHeader(Http3FrameType.Headers, frameLength: 38, bufferPipe.Writer); + + var headersTotalSize = 0; + var headers = new Http3HeadersEnumerator(); + headers.Initialize(new Dictionary() { + { InternalHeaderNames.Method, "POST" }, + { InternalHeaderNames.Path, "/" }, + { InternalHeaderNames.Scheme, "http" }, }); + + var mem = bufferPipe.Writer.GetMemory(); + var done = QPackHeaderWriter.BeginEncodeHeaders(headers, mem.Span, ref headersTotalSize, out var length); + Assert.True(done); + bufferPipe.Writer.Advance(length); + await bufferPipe.Writer.FlushAsync(); + + // Write header frame one byte at a time + await WriteOneByteAtATime(bufferPipe.Reader, stream.Pair.Application.Output); + + Http3FrameWriter.WriteHeader(Http3FrameType.Data, frameLength: 12, bufferPipe.Writer); + await bufferPipe.Writer.FlushAsync(); + + // Write data header one byte at a time + await WriteOneByteAtATime(bufferPipe.Reader, stream.Pair.Application.Output); + + bufferPipe.Writer.Write(new byte[12]); + await bufferPipe.Writer.FlushAsync(); + + // Write data in data frame one byte at a time + // Don't use WriteOneByteAtATime() as we want to wait on the TCS after every flush to make sure app code consumed the data + // before we send another byte + var res = await bufferPipe.Reader.ReadAsync(); + for (var i = 0; i < res.Buffer.Length; i++) + { + mem = stream.Pair.Application.Output.GetMemory(); + mem.Span[0] = res.Buffer.Slice(i).FirstSpan[0]; + stream.Pair.Application.Output.Advance(1); + // Use TCS to make sure app can read data before we send more + var capturedTcs = tcs; + await stream.Pair.Application.Output.FlushAsync(); + await capturedTcs.Task; + } + bufferPipe.Reader.AdvanceTo(res.Buffer.End); + + var trailers = new Http3HeadersEnumerator(); + trailers.Initialize(new Dictionary() + { + { "TestName", "TestValue" } + }); + + Http3FrameWriter.WriteHeader(Http3FrameType.Headers, frameLength: 22, bufferPipe.Writer); + mem = bufferPipe.Writer.GetMemory(); + done = QPackHeaderWriter.BeginEncodeHeaders(trailers, mem.Span, ref headersTotalSize, out length); + Assert.True(done); + bufferPipe.Writer.Advance(length); + await bufferPipe.Writer.FlushAsync(); + + // Write trailer frame one byte at a time + await WriteOneByteAtATime(bufferPipe.Reader, stream.Pair.Application.Output); + + await stream.EndStreamAsync(); + + var responseHeaders = await stream.ExpectHeadersAsync(); + Assert.Equal(3, responseHeaders.Count); + Assert.Contains("date", responseHeaders.Keys, StringComparer.OrdinalIgnoreCase); + Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]); + Assert.Equal("0", responseHeaders["content-length"]); + + await stream.ExpectReceiveEndOfStream(); + + Assert.Equal(12, total); + Assert.Equal("TestValue", trailerValue); + } + + [Fact] + public async Task Control_FrameParsingSingleByteAtATimeWorks() + { + await Http3Api.InitializeConnectionAsync(_noopApplication); + + // Use Inline scheduling and buffer size of 1 to guarantee each write will wait for the parsing loop to complete before writing more data + _serviceContext.ServerOptions.Limits.MaxRequestBufferSize = 1; + var outboundcontrolStream = await Http3Api.CreateControlStream(clientWriterScheduler: PipeScheduler.Inline); + + // Use local pipe to write frames so we can get the entire buffer in order to write it one byte at a time + var bufferPipe = new Pipe(); + + var settings = new List + { + new Http3PeerSetting(Internal.Http3.Http3SettingType.MaxFieldSectionSize, 100), + new Http3PeerSetting(Internal.Http3.Http3SettingType.EnableWebTransport, 1), + new Http3PeerSetting(Internal.Http3.Http3SettingType.H3Datagram, 1) + }; + var len = Http3FrameWriter.CalculateSettingsSize(settings); + + Http3FrameWriter.WriteHeader(Http3FrameType.Settings, len, bufferPipe.Writer); + var mem = bufferPipe.Writer.GetMemory(); + Http3FrameWriter.WriteSettings(settings, mem.Span); + + bufferPipe.Writer.Advance(len); + await bufferPipe.Writer.FlushAsync(); + + // Write Settings frame one byte at a time + await WriteOneByteAtATime(bufferPipe.Reader, outboundcontrolStream.Pair.Application.Output); + + var fieldSetting = await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout(); + + Assert.Equal(Internal.Http3.Http3SettingType.MaxFieldSectionSize, fieldSetting.Key); + Assert.Equal(100, fieldSetting.Value); + + fieldSetting = await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout(); + Assert.Equal(Internal.Http3.Http3SettingType.EnableWebTransport, fieldSetting.Key); + Assert.Equal(1, fieldSetting.Value); + + fieldSetting = await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout(); + Assert.Equal(Internal.Http3.Http3SettingType.H3Datagram, fieldSetting.Key); + Assert.Equal(1, fieldSetting.Value); + + // Frames must be well-formed otherwise we close the connection with a frame error + Http3FrameWriter.WriteHeader(Http3FrameType.CancelPush, frameLength: 2, bufferPipe.Writer); + var idLength = VariableLengthIntegerHelper.WriteInteger(bufferPipe.Writer.GetSpan(), longToEncode: 1026); + bufferPipe.Writer.Advance(idLength); + await bufferPipe.Writer.FlushAsync(); + + // Write CancelPush frame one byte at a time + await WriteOneByteAtATime(bufferPipe.Reader, outboundcontrolStream.Pair.Application.Output); + + // Frames must be well-formed otherwise we close the connection with a frame error + Http3FrameWriter.WriteHeader(Http3FrameType.GoAway, frameLength: 4, bufferPipe.Writer); + idLength = VariableLengthIntegerHelper.WriteInteger(bufferPipe.Writer.GetSpan(), longToEncode: 100026); + bufferPipe.Writer.Advance(idLength); + await bufferPipe.Writer.FlushAsync(); + + try + { + // Write GoAway frame one byte at a time + await WriteOneByteAtATime(bufferPipe.Reader, outboundcontrolStream.Pair.Application.Output); + } + // As soon as the GOAWAY frame identifier is processed we initiate the connection close process. + // That means it's possible to still be writing to the stream when we close the + // connection which would result in an exception. We'll just ignore the exception in this case. + catch (Exception) { } + + await outboundcontrolStream.EndStreamAsync(); + + // Check that connection is closed. + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Http3Api.MultiplexedConnectionContext.ConnectionClosed.Register(() => tcs.TrySetResult()); + await tcs.Task; + + await outboundcontrolStream.ReceiveEndAsync(); + } + + private async Task WriteOneByteAtATime(PipeReader reader, PipeWriter writer) + { + var res = await reader.ReadAsync(); + try + { + for (var i = 0; i < res.Buffer.Length; i++) + { + var mem = writer.GetMemory(); + mem.Span[0] = res.Buffer.Slice(i).FirstSpan[0]; + writer.Advance(1); + await writer.FlushAsync(); + } + } + finally + { + reader.AdvanceTo(res.Buffer.End); + } + } } diff --git a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs index 3a343a62a4cc..c7f1ec908d0f 100644 --- a/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs +++ b/src/Shared/runtime/Http3/Helpers/VariableLengthIntegerHelper.cs @@ -128,19 +128,19 @@ static bool TryReadSlow(ref SequenceReader reader, out long value) } } - public static long GetInteger(in ReadOnlySequence buffer, out SequencePosition consumed, out SequencePosition examined) + // If callsite has 'examined', set it to buffer.End if the integer wasn't successfully read, otherwise set examined = consumed. + public static bool TryGetInteger(in ReadOnlySequence buffer, out SequencePosition consumed, out long integer) { var reader = new SequenceReader(buffer); - if (TryRead(ref reader, out long value)) + if (TryRead(ref reader, out integer)) { - consumed = examined = buffer.GetPosition(reader.Consumed); - return value; + consumed = buffer.GetPosition(reader.Consumed); + return true; } else { - consumed = default; - examined = buffer.End; - return -1; + consumed = buffer.Start; + return false; } } diff --git a/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs b/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs index d67d24c0ba25..e461bfd41ed4 100644 --- a/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs +++ b/src/Shared/test/Shared.Tests/runtime/Http3/VariableLengthIntegerHelperTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -223,12 +223,12 @@ public void GetInteger_ValidSegmentedSequence() MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 0, 2 }); ReadOnlySequence readOnlySequence = new ReadOnlySequence( memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); - long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence, - out SequencePosition consumed, out SequencePosition examined); + bool result = VariableLengthIntegerHelper.TryGetInteger(readOnlySequence, + out SequencePosition consumed, out long integer); - Assert.Equal(2, result); + Assert.True(result); + Assert.Equal(2, integer); Assert.Equal(7, consumed.GetInteger()); - Assert.Equal(7, examined.GetInteger()); } [Fact] @@ -238,12 +238,11 @@ public void GetInteger_NotValidSegmentedSequence() MemorySegment memorySegment2 = memorySegment1.Append(new byte[] { 0, 0, 0, 0, 0, 2 }); ReadOnlySequence readOnlySequence = new ReadOnlySequence( memorySegment1, 0, memorySegment2, memorySegment2.Memory.Length); - long result = VariableLengthIntegerHelper.GetInteger(readOnlySequence, - out SequencePosition consumed, out SequencePosition examined); + bool result = VariableLengthIntegerHelper.TryGetInteger(readOnlySequence, + out SequencePosition consumed, out long integer); - Assert.Equal(-1, result); + Assert.False(result); Assert.Equal(0, consumed.GetInteger()); - Assert.Equal(6, examined.GetInteger()); } [Fact] diff --git a/src/SignalR/clients/ts/FunctionalTests/Startup.cs b/src/SignalR/clients/ts/FunctionalTests/Startup.cs index 45d3f8546a71..dd3ee9378458 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Startup.cs @@ -184,9 +184,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger< { cookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None; cookieOptions.Secure = true; + cookieOptions.Extensions.Add("partitioned"); // Required by Chromium expiredCookieOptions.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None; expiredCookieOptions.Secure = true; + expiredCookieOptions.Extensions.Add("partitioned"); // Required by Chromium } context.Response.Cookies.Append("testCookie", "testValue", cookieOptions); context.Response.Cookies.Append("testCookie2", "testValue2", cookieOptions); diff --git a/src/submodules/googletest b/src/submodules/googletest index e235eb34c6c4..24a9e940d481 160000 --- a/src/submodules/googletest +++ b/src/submodules/googletest @@ -1 +1 @@ -Subproject commit e235eb34c6c4fed790ccdad4b16394301360dcd4 +Subproject commit 24a9e940d481f992ba852599c78bb2217362847b pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy