diff --git a/src/Servers/Kestrel/Core/src/Internal/BufferReader.cs b/src/Servers/Kestrel/Core/src/Internal/BufferReader.cs deleted file mode 100644 index 3fb844addb..0000000000 --- a/src/Servers/Kestrel/Core/src/Internal/BufferReader.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.CompilerServices; - -namespace System.Buffers -{ - internal ref struct BufferReader - { - private ReadOnlySpan _currentSpan; - private int _index; - - private ReadOnlySequence _sequence; - private SequencePosition _currentSequencePosition; - private SequencePosition _nextSequencePosition; - - private int _consumedBytes; - private bool _end; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferReader(in ReadOnlySequence buffer) - { - _index = 0; - _consumedBytes = 0; - _sequence = buffer; - _currentSequencePosition = _sequence.Start; - _nextSequencePosition = _currentSequencePosition; - - if (_sequence.TryGet(ref _nextSequencePosition, out var memory, true)) - { - _end = false; - _currentSpan = memory.Span; - if (_currentSpan.Length == 0) - { - // No space in first span, move to one with space - MoveNext(); - } - } - else - { - // No space in any spans and at end of sequence - _end = true; - _currentSpan = default; - } - } - - public bool End => _end; - - public int CurrentSegmentIndex => _index; - - public SequencePosition Position => _sequence.GetPosition(_index, _currentSequencePosition); - - public ReadOnlySpan CurrentSegment => _currentSpan; - - public ReadOnlySpan UnreadSegment => _currentSpan.Slice(_index); - - public int ConsumedBytes => _consumedBytes; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Peek() - { - if (_end) - { - return -1; - } - return _currentSpan[_index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Read() - { - if (_end) - { - return -1; - } - - var value = _currentSpan[_index]; - _index++; - _consumedBytes++; - - if (_index >= _currentSpan.Length) - { - MoveNext(); - } - - return value; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void MoveNext() - { - var previous = _nextSequencePosition; - while (_sequence.TryGet(ref _nextSequencePosition, out var memory, true)) - { - _currentSequencePosition = previous; - _currentSpan = memory.Span; - _index = 0; - if (_currentSpan.Length > 0) - { - return; - } - } - _end = true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int byteCount) - { - if (!_end && byteCount > 0 && (_index + byteCount) < _currentSpan.Length) - { - _consumedBytes += byteCount; - _index += byteCount; - } - else - { - AdvanceNext(byteCount); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void AdvanceNext(int byteCount) - { - if (byteCount < 0) - { - BuffersThrowHelper.ThrowArgumentOutOfRangeException(BuffersThrowHelper.ExceptionArgument.length); - } - - _consumedBytes += byteCount; - - while (!_end && byteCount > 0) - { - if ((_index + byteCount) < _currentSpan.Length) - { - _index += byteCount; - byteCount = 0; - break; - } - - var remaining = (_currentSpan.Length - _index); - - _index += remaining; - byteCount -= remaining; - - MoveNext(); - } - - if (byteCount > 0) - { - BuffersThrowHelper.ThrowArgumentOutOfRangeException(BuffersThrowHelper.ExceptionArgument.length); - } - } - } -} diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs index 1aba7faf4a..fb34300a4b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1MessageBody.cs @@ -528,11 +528,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { consumed = buffer.Start; examined = buffer.Start; - var reader = new BufferReader(buffer); - var ch1 = reader.Read(); - var ch2 = reader.Read(); + var reader = new SequenceReader(buffer); - if (ch1 == -1 || ch2 == -1) + if (!reader.TryRead(out var ch1) || !reader.TryRead(out var ch2)) { examined = reader.Position; return; @@ -541,21 +539,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var chunkSize = CalculateChunkSize(ch1, 0); ch1 = ch2; - while (reader.ConsumedBytes < MaxChunkPrefixBytes) + while (reader.Consumed < MaxChunkPrefixBytes) { if (ch1 == ';') { consumed = reader.Position; examined = reader.Position; - AddAndCheckConsumedBytes(reader.ConsumedBytes); + AddAndCheckConsumedBytes(reader.Consumed); _inputLength = chunkSize; _mode = Mode.Extension; return; } - ch2 = reader.Read(); - if (ch2 == -1) + if (!reader.TryRead(out ch2)) { examined = reader.Position; return; @@ -566,7 +563,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http consumed = reader.Position; examined = reader.Position; - AddAndCheckConsumedBytes(reader.ConsumedBytes); + AddAndCheckConsumedBytes(reader.Consumed); _inputLength = chunkSize; _mode = chunkSize > 0 ? Mode.Data : Mode.Trailer; return; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs index 5400a07e17..cab5637529 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpParser.cs @@ -182,25 +182,26 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http var bufferEnd = buffer.End; - var reader = new BufferReader(buffer); - var start = default(BufferReader); + var reader = new SequenceReader(buffer); + var start = default(SequenceReader); var done = false; try { while (!reader.End) { - var span = reader.CurrentSegment; - var remaining = span.Length - reader.CurrentSegmentIndex; + var span = reader.CurrentSpan; + var remaining = span.Length - reader.CurrentSpanIndex; fixed (byte* pBuffer = span) { while (remaining > 0) { - var index = reader.CurrentSegmentIndex; - int ch1; - int ch2; + var index = reader.CurrentSpanIndex; + byte ch1; + byte ch2; var readAhead = false; + bool readSecond = true; // Fast path, we're still looking at the same span if (remaining >= 2) @@ -215,8 +216,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http start = reader; // Possibly split across spans - ch1 = reader.Read(); - ch2 = reader.Read(); + reader.TryRead(out ch1); + readSecond = reader.TryRead(out ch2); readAhead = true; } @@ -224,7 +225,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (ch1 == ByteCR) { // Check for final CRLF. - if (ch2 == -1) + if (!readSecond) { // Reset the reader so we don't consume anything reader = start; @@ -252,7 +253,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (readAhead) { reader = start; - index = reader.CurrentSegmentIndex; + index = reader.CurrentSpanIndex; } var endIndex = new Span(pBuffer + index, remaining).IndexOf(ByteLF); @@ -308,7 +309,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http finally { consumed = reader.Position; - consumedBytes = reader.ConsumedBytes; + consumedBytes = (int)reader.Consumed; if (done) { diff --git a/src/Servers/Kestrel/Core/test/BufferReaderTests.cs b/src/Servers/Kestrel/Core/test/BufferReaderTests.cs deleted file mode 100644 index a33071db82..0000000000 --- a/src/Servers/Kestrel/Core/test/BufferReaderTests.cs +++ /dev/null @@ -1,300 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Xunit; - -namespace System.Buffers.Tests -{ - public abstract class ReadableBufferReaderFacts - { - public class Array : SingleSegment - { - public Array() : base(ReadOnlySequenceFactory.ArrayFactory) { } - internal Array(ReadOnlySequenceFactory factory) : base(factory) { } - } - - public class OwnedMemory : SingleSegment - { - public OwnedMemory() : base(ReadOnlySequenceFactory.OwnedMemoryFactory) { } - } - public class Memory : SingleSegment - { - public Memory() : base(ReadOnlySequenceFactory.MemoryFactory) { } - } - - public class SingleSegment : SegmentPerByte - { - public SingleSegment() : base(ReadOnlySequenceFactory.SingleSegmentFactory) { } - internal SingleSegment(ReadOnlySequenceFactory factory) : base(factory) { } - - [Fact] - public void AdvanceSingleBufferSkipsBytes() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5 })); - reader.Advance(2); - Assert.Equal(2, reader.CurrentSegmentIndex); - Assert.Equal(3, reader.CurrentSegment[reader.CurrentSegmentIndex]); - Assert.Equal(3, reader.Peek()); - reader.Advance(2); - Assert.Equal(5, reader.Peek()); - Assert.Equal(4, reader.CurrentSegmentIndex); - Assert.Equal(5, reader.CurrentSegment[reader.CurrentSegmentIndex]); - } - - [Fact] - public void TakeReturnsByteAndMoves() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 })); - Assert.Equal(0, reader.CurrentSegmentIndex); - Assert.Equal(1, reader.CurrentSegment[reader.CurrentSegmentIndex]); - Assert.Equal(1, reader.Read()); - Assert.Equal(1, reader.CurrentSegmentIndex); - Assert.Equal(2, reader.CurrentSegment[reader.CurrentSegmentIndex]); - Assert.Equal(2, reader.Read()); - Assert.Equal(-1, reader.Read()); - } - } - - public class SegmentPerByte : ReadableBufferReaderFacts - { - public SegmentPerByte() : base(ReadOnlySequenceFactory.SegmentPerByteFactory) { } - internal SegmentPerByte(ReadOnlySequenceFactory factory) : base(factory) { } - } - - internal ReadOnlySequenceFactory Factory { get; } - - internal ReadableBufferReaderFacts(ReadOnlySequenceFactory factory) - { - Factory = factory; - } - - [Fact] - public void PeekReturnsByteWithoutMoving() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 })); - Assert.Equal(1, reader.Peek()); - Assert.Equal(1, reader.Peek()); - } - - [Fact] - public void CursorIsCorrectAtEnd() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 })); - reader.Read(); - reader.Read(); - Assert.True(reader.End); - } - - [Fact] - public void CursorIsCorrectWithEmptyLastBlock() - { - var first = new BufferSegment(new byte[] { 1, 2 }); - var last = first.Append(new byte[4]); - - var reader = new BufferReader(new ReadOnlySequence(first, 0, last, 0)); - reader.Read(); - reader.Read(); - reader.Read(); - Assert.Same(last, reader.Position.GetObject()); - Assert.Equal(0, reader.Position.GetInteger()); - Assert.True(reader.End); - } - - [Fact] - public void PeekReturnsMinusOneByteInTheEnd() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2 })); - Assert.Equal(1, reader.Read()); - Assert.Equal(2, reader.Read()); - Assert.Equal(-1, reader.Peek()); - } - - [Fact] - public void AdvanceToEndThenPeekReturnsMinusOne() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5 })); - reader.Advance(5); - Assert.True(reader.End); - Assert.Equal(-1, reader.Peek()); - } - - [Fact] - public void AdvancingPastLengthThrows() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5 })); - try - { - reader.Advance(6); - Assert.True(false); - } - catch (Exception ex) - { - Assert.True(ex is ArgumentOutOfRangeException); - } - } - - [Fact] - public void CtorFindsFirstNonEmptySegment() - { - var buffer = Factory.CreateWithContent(new byte[] { 1 }); - var reader = new BufferReader(buffer); - - Assert.Equal(1, reader.Peek()); - } - - [Fact] - public void EmptySegmentsAreSkippedOnMoveNext() - { - var buffer = Factory.CreateWithContent(new byte[] { 1, 2 }); - var reader = new BufferReader(buffer); - - Assert.Equal(1, reader.Peek()); - reader.Advance(1); - Assert.Equal(2, reader.Peek()); - } - - [Fact] - public void PeekGoesToEndIfAllEmptySegments() - { - var buffer = Factory.CreateOfSize(0); - var reader = new BufferReader(buffer); - - Assert.Equal(-1, reader.Peek()); - Assert.True(reader.End); - } - - [Fact] - public void AdvanceTraversesSegments() - { - var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3 }); - var reader = new BufferReader(buffer); - - reader.Advance(2); - Assert.Equal(3, reader.CurrentSegment[reader.CurrentSegmentIndex]); - Assert.Equal(3, reader.Read()); - } - - [Fact] - public void AdvanceThrowsPastLengthMultipleSegments() - { - var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3 }); - var reader = new BufferReader(buffer); - - try - { - reader.Advance(4); - Assert.True(false); - } - catch (Exception ex) - { - Assert.True(ex is ArgumentOutOfRangeException); - } - } - - [Fact] - public void TakeTraversesSegments() - { - var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3 }); - var reader = new BufferReader(buffer); - - Assert.Equal(1, reader.Read()); - Assert.Equal(2, reader.Read()); - Assert.Equal(3, reader.Read()); - Assert.Equal(-1, reader.Read()); - } - - [Fact] - public void PeekTraversesSegments() - { - var buffer = Factory.CreateWithContent(new byte[] { 1, 2 }); - var reader = new BufferReader(buffer); - - Assert.Equal(1, reader.CurrentSegment[reader.CurrentSegmentIndex]); - Assert.Equal(1, reader.Read()); - - Assert.Equal(2, reader.CurrentSegment[reader.CurrentSegmentIndex]); - Assert.Equal(2, reader.Peek()); - Assert.Equal(2, reader.Read()); - Assert.Equal(-1, reader.Peek()); - Assert.Equal(-1, reader.Read()); - } - - [Fact] - public void PeekWorksWithEmptySegments() - { - var buffer = Factory.CreateWithContent(new byte[] { 1 }); - var reader = new BufferReader(buffer); - - Assert.Equal(0, reader.CurrentSegmentIndex); - Assert.Equal(1, reader.CurrentSegment.Length); - Assert.Equal(1, reader.Peek()); - Assert.Equal(1, reader.Read()); - Assert.Equal(-1, reader.Peek()); - Assert.Equal(-1, reader.Read()); - } - - [Fact] - public void WorksWithEmptyBuffer() - { - var reader = new BufferReader(Factory.CreateWithContent(new byte[] { })); - - Assert.Equal(0, reader.CurrentSegmentIndex); - Assert.Equal(0, reader.CurrentSegment.Length); - Assert.Equal(-1, reader.Peek()); - Assert.Equal(-1, reader.Read()); - } - - [Theory] - [InlineData(0, false)] - [InlineData(5, false)] - [InlineData(10, false)] - [InlineData(11, true)] - [InlineData(12, true)] - [InlineData(15, true)] - public void ReturnsCorrectCursor(int takes, bool end) - { - var readableBuffer = Factory.CreateWithContent(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - var reader = new BufferReader(readableBuffer); - for (int i = 0; i < takes; i++) - { - reader.Read(); - } - - var expected = end ? new byte[] { } : readableBuffer.Slice((long)takes).ToArray(); - Assert.Equal(expected, readableBuffer.Slice(reader.Position).ToArray()); - } - - [Fact] - public void SlicingBufferReturnsCorrectCursor() - { - var buffer = Factory.CreateWithContent(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - var sliced = buffer.Slice(2L); - - var reader = new BufferReader(sliced); - Assert.Equal(sliced.ToArray(), buffer.Slice(reader.Position).ToArray()); - Assert.Equal(2, reader.Peek()); - Assert.Equal(0, reader.CurrentSegmentIndex); - } - - [Fact] - public void ReaderIndexIsCorrect() - { - var buffer = Factory.CreateWithContent(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); - var reader = new BufferReader(buffer); - - var counter = 1; - while (!reader.End) - { - var span = reader.CurrentSegment; - for (int i = reader.CurrentSegmentIndex; i < span.Length; i++) - { - Assert.Equal(counter++, reader.CurrentSegment[i]); - } - reader.Advance(span.Length); - } - Assert.Equal(buffer.Length, reader.ConsumedBytes); - } - } - -}