diff --git a/build/dependencies.props b/build/dependencies.props
index a53bc20e1f..90b286111a 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -18,8 +18,6 @@
2.1.0
2.1.1
2.1.1
- 2.1.1
- 2.1.1
2.1.1
2.1.1
2.1.1
diff --git a/src/Kestrel.Core/Internal/BufferReader.cs b/src/Kestrel.Core/Internal/BufferReader.cs
new file mode 100644
index 0000000000..5a1e75b69b
--- /dev/null
+++ b/src/Kestrel.Core/Internal/BufferReader.cs
@@ -0,0 +1,125 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Buffers;
+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;
+
+ public BufferReader(ReadOnlySequence buffer)
+ {
+ _end = false;
+ _index = 0;
+ _consumedBytes = 0;
+ _sequence = buffer;
+ _currentSequencePosition = _sequence.Start;
+ _nextSequencePosition = _currentSequencePosition;
+ _currentSpan = ReadOnlySpan.Empty;
+ MoveNext();
+ }
+
+ 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;
+ }
+
+ public void Advance(int byteCount)
+ {
+ if (byteCount < 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(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)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length);
+ }
+ }
+ }
+}
diff --git a/src/Kestrel.Core/Internal/BufferWriter.cs b/src/Kestrel.Core/Internal/BufferWriter.cs
new file mode 100644
index 0000000000..2b63ea920c
--- /dev/null
+++ b/src/Kestrel.Core/Internal/BufferWriter.cs
@@ -0,0 +1,93 @@
+// 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 System.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ internal ref struct BufferWriter where T: IBufferWriter
+ {
+ private T _output;
+ private Span _span;
+ private int _buffered;
+
+ public BufferWriter(T output)
+ {
+ _buffered = 0;
+ _output = output;
+ _span = output.GetSpan();
+ }
+
+ public Span Span => _span;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Commit()
+ {
+ var buffered = _buffered;
+ if (buffered > 0)
+ {
+ _buffered = 0;
+ _output.Advance(buffered);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Advance(int count)
+ {
+ _buffered += count;
+ _span = _span.Slice(count);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Write(ReadOnlySpan source)
+ {
+ if (_span.Length >= source.Length)
+ {
+ source.CopyTo(_span);
+ Advance(source.Length);
+ }
+ else
+ {
+ WriteMultiBuffer(source);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Ensure(int count = 1)
+ {
+ if (_span.Length < count)
+ {
+ EnsureMore(count);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void EnsureMore(int count = 0)
+ {
+ if (_buffered > 0)
+ {
+ Commit();
+ }
+
+ _output.GetMemory(count);
+ _span = _output.GetSpan();
+ }
+
+ private void WriteMultiBuffer(ReadOnlySpan source)
+ {
+ while (source.Length > 0)
+ {
+ if (_span.Length == 0)
+ {
+ EnsureMore();
+ }
+
+ var writable = Math.Min(source.Length, _span.Length);
+ source.Slice(0, writable).CopyTo(_span);
+ source = source.Slice(writable);
+ Advance(writable);
+ }
+ }
+ }
+}
diff --git a/src/Kestrel.Core/Kestrel.Core.csproj b/src/Kestrel.Core/Kestrel.Core.csproj
index b81428efa5..8551a56002 100644
--- a/src/Kestrel.Core/Kestrel.Core.csproj
+++ b/src/Kestrel.Core/Kestrel.Core.csproj
@@ -11,6 +11,10 @@
CS1591;$(NoWarn)
+
+
+
+
@@ -21,7 +25,6 @@
-
diff --git a/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolBlock.Debug.cs b/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolBlock.Debug.cs
new file mode 100644
index 0000000000..6f815716d4
--- /dev/null
+++ b/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolBlock.Debug.cs
@@ -0,0 +1,125 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if DEBUG
+
+using System.Threading;
+using System.Diagnostics;
+
+namespace System.Buffers
+{
+ ///
+ /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independent array segments.
+ ///
+ internal sealed class MemoryPoolBlock : MemoryManager
+ {
+ private readonly int _offset;
+ private readonly int _length;
+
+ private int _pinCount;
+
+ ///
+ /// This object cannot be instantiated outside of the static Create method
+ ///
+ internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
+ {
+ _offset = offset;
+ _length = length;
+
+ Pool = pool;
+ Slab = slab;
+ }
+
+ ///
+ /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
+ ///
+ public SlabMemoryPool Pool { get; }
+
+ ///
+ /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
+ ///
+ public MemoryPoolSlab Slab { get; }
+
+ public override Memory Memory
+ {
+ get
+ {
+ if (!Slab.IsActive) ThrowHelper.ThrowObjectDisposedException(ExceptionArgument.MemoryPoolBlock);
+
+ return CreateMemory(_length);
+ }
+ }
+
+
+#if BLOCK_LEASE_TRACKING
+ public bool IsLeased { get; set; }
+ public string Leaser { get; set; }
+#endif
+
+ ~MemoryPoolBlock()
+ {
+ if (Slab != null && Slab.IsActive)
+ {
+ Debug.Assert(false, $"{Environment.NewLine}{Environment.NewLine}*** Block being garbage collected instead of returned to pool" +
+#if BLOCK_LEASE_TRACKING
+ $": {Leaser}" +
+#endif
+ $" ***{ Environment.NewLine}");
+
+ // Need to make a new object because this one is being finalized
+ Pool.Return(new MemoryPoolBlock(Pool, Slab, _offset, _length));
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!Slab.IsActive) ThrowHelper.ThrowObjectDisposedException(ExceptionArgument.MemoryPoolBlock);
+
+ if (Volatile.Read(ref _pinCount) > 0)
+ {
+ ThrowHelper.ThrowInvalidOperationException_ReturningPinnedBlock();
+ }
+
+ Pool.Return(this);
+ }
+
+ public override Span GetSpan() => new Span(Slab.Array, _offset, _length);
+
+ public override MemoryHandle Pin(int byteOffset = 0)
+ {
+ if (!Slab.IsActive) ThrowHelper.ThrowObjectDisposedException(ExceptionArgument.MemoryPoolBlock);
+ if (byteOffset < 0 || byteOffset > _length) ThrowHelper.ThrowArgumentOutOfRangeException(_length, byteOffset);
+
+ Interlocked.Increment(ref _pinCount);
+ unsafe
+ {
+ return new MemoryHandle((Slab.NativePointer + _offset + byteOffset).ToPointer(), default, this);
+ }
+ }
+
+ protected override bool TryGetArray(out ArraySegment segment)
+ {
+ segment = new ArraySegment(Slab.Array, _offset, _length);
+ return true;
+ }
+
+ public override void Unpin()
+ {
+ if (Interlocked.Decrement(ref _pinCount) < 0)
+ {
+ ThrowHelper.ThrowInvalidOperationException_ReferenceCountZero();
+ }
+ }
+
+ public void Lease()
+ {
+#if BLOCK_LEASE_TRACKING
+ Leaser = Environment.StackTrace;
+ IsLeased = true;
+#endif
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolBlock.Release.cs b/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolBlock.Release.cs
new file mode 100644
index 0000000000..93784df05e
--- /dev/null
+++ b/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolBlock.Release.cs
@@ -0,0 +1,65 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#if RELEASE
+
+using System.Runtime.InteropServices;
+
+namespace System.Buffers
+{
+ ///
+ /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independent array segments.
+ ///
+ internal sealed class MemoryPoolBlock : IMemoryOwner
+ {
+ private readonly int _offset;
+ private readonly int _length;
+
+ ///
+ /// This object cannot be instantiated outside of the static Create method
+ ///
+ internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
+ {
+ _offset = offset;
+ _length = length;
+
+ Pool = pool;
+ Slab = slab;
+
+ Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length);
+ }
+
+ ///
+ /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
+ ///
+ public SlabMemoryPool Pool { get; }
+
+ ///
+ /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
+ ///
+ public MemoryPoolSlab Slab { get; }
+
+ public Memory Memory { get; }
+
+ ~MemoryPoolBlock()
+ {
+ if (Slab != null && Slab.IsActive)
+ {
+ // Need to make a new object because this one is being finalized
+ Pool.Return(new MemoryPoolBlock(Pool, Slab, _offset, _length));
+ }
+ }
+
+ public void Dispose()
+ {
+ Pool.Return(this);
+ }
+
+ public void Lease()
+ {
+ }
+ }
+}
+
+#endif
diff --git a/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolSlab.cs b/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolSlab.cs
new file mode 100644
index 0000000000..4b15f88233
--- /dev/null
+++ b/src/Kestrel.Transport.Abstractions/Internal/MemoryPoolSlab.cs
@@ -0,0 +1,97 @@
+// 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.InteropServices;
+
+namespace System.Buffers
+{
+ ///
+ /// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independant array segments.
+ ///
+ internal class MemoryPoolSlab : IDisposable
+ {
+ ///
+ /// This handle pins the managed array in memory until the slab is disposed. This prevents it from being
+ /// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls.
+ ///
+ private readonly GCHandle _gcHandle;
+ private readonly IntPtr _nativePointer;
+ private byte[] _data;
+
+ private bool _isActive;
+ private bool _disposedValue;
+
+ public MemoryPoolSlab(byte[] data)
+ {
+ _data = data;
+ _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
+ _nativePointer = _gcHandle.AddrOfPinnedObject();
+ _isActive = true;
+ }
+
+ ///
+ /// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the
+ /// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the
+ /// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will
+ /// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage
+ /// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will
+ /// be unpinned by the slab's Dispose.
+ ///
+ public bool IsActive => _isActive;
+
+ public IntPtr NativePointer => _nativePointer;
+
+ public byte[] Array => _data;
+
+ public int Length => _data.Length;
+
+ public static MemoryPoolSlab Create(int length)
+ {
+ // allocate and pin requested memory length
+ var array = new byte[length];
+
+ // allocate and return slab tracking object
+ return new MemoryPoolSlab(array);
+ }
+
+ protected void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // N/A: dispose managed state (managed objects).
+ }
+
+ _isActive = false;
+
+ if (_gcHandle.IsAllocated)
+ {
+ _gcHandle.Free();
+ }
+
+ // set large fields to null.
+ _data = null;
+
+ _disposedValue = true;
+ }
+ }
+
+ // override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
+ ~MemoryPoolSlab()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(false);
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ // uncomment the following line if the finalizer is overridden above.
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Kestrel.Transport.Abstractions/Internal/SlabMemoryPool.cs b/src/Kestrel.Transport.Abstractions/Internal/SlabMemoryPool.cs
new file mode 100644
index 0000000000..e36ec70adb
--- /dev/null
+++ b/src/Kestrel.Transport.Abstractions/Internal/SlabMemoryPool.cs
@@ -0,0 +1,193 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Concurrent;
+using System.Diagnostics;
+
+namespace System.Buffers
+{
+ ///
+ /// Used to allocate and distribute re-usable blocks of memory.
+ ///
+ internal class SlabMemoryPool : MemoryPool
+ {
+ ///
+ /// The size of a block. 4096 is chosen because most operating systems use 4k pages.
+ ///
+ private const int _blockSize = 4096;
+
+ ///
+ /// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory
+ /// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively
+ /// affect memory management's compactification.
+ ///
+ private const int _blockCount = 32;
+
+ ///
+ /// Max allocation block size for pooled blocks,
+ /// larger values can be leased but they will be disposed after use rather than returned to the pool.
+ ///
+ public override int MaxBufferSize { get; } = _blockSize;
+
+ ///
+ /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
+ ///
+ private static readonly int _slabLength = _blockSize * _blockCount;
+
+ ///
+ /// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects
+ /// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added.
+ ///
+ private readonly ConcurrentQueue _blocks = new ConcurrentQueue();
+
+ ///
+ /// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive,
+ /// the blocks will be added to _blocks when returned.
+ ///
+ private readonly ConcurrentStack _slabs = new ConcurrentStack();
+
+ ///
+ /// This is part of implementing the IDisposable pattern.
+ ///
+ private bool _disposedValue = false; // To detect redundant calls
+
+ ///
+ /// This default value passed in to Rent to use the default value for the pool.
+ ///
+ private const int AnySize = -1;
+
+ public override IMemoryOwner Rent(int size = AnySize)
+ {
+ if (size == AnySize) size = _blockSize;
+ else if (size > _blockSize)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize);
+ }
+
+ var block = Lease();
+ return block;
+ }
+
+ ///
+ /// Called to take a block from the pool.
+ ///
+ /// The block that is reserved for the called. It must be passed to Return when it is no longer being used.
+ private MemoryPoolBlock Lease()
+ {
+ Debug.Assert(!_disposedValue, "Block being leased from disposed pool!");
+
+ if (_blocks.TryDequeue(out MemoryPoolBlock block))
+ {
+ // block successfully taken from the stack - return it
+
+ block.Lease();
+ return block;
+ }
+ // no blocks available - grow the pool
+ block = AllocateSlab();
+ block.Lease();
+ return block;
+ }
+
+ ///
+ /// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
+ /// block tracking objects, and adds them all to the pool.
+ ///
+ private MemoryPoolBlock AllocateSlab()
+ {
+ var slab = MemoryPoolSlab.Create(_slabLength);
+ _slabs.Push(slab);
+
+ var basePtr = slab.NativePointer;
+ // Page align the blocks
+ var firstOffset = (int)((((ulong)basePtr + (uint)_blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr);
+ // Ensure page aligned
+ Debug.Assert((((ulong)basePtr + (uint)firstOffset) & (uint)(_blockSize - 1)) == 0);
+
+ var blockAllocationLength = ((_slabLength - firstOffset) & ~(_blockSize - 1));
+ var offset = firstOffset;
+ for (;
+ offset + _blockSize < blockAllocationLength;
+ offset += _blockSize)
+ {
+ var block = new MemoryPoolBlock(
+ this,
+ slab,
+ offset,
+ _blockSize);
+#if BLOCK_LEASE_TRACKING
+ block.IsLeased = true;
+#endif
+ Return(block);
+ }
+
+ Debug.Assert(offset + _blockSize - firstOffset == blockAllocationLength);
+ // return last block rather than adding to pool
+ var newBlock = new MemoryPoolBlock(
+ this,
+ slab,
+ offset,
+ _blockSize);
+
+ return newBlock;
+ }
+
+ ///
+ /// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and
+ /// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the
+ /// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and return
+ /// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to avoid
+ /// leaving "dead zones" in the slab due to lost block tracking objects.
+ ///
+ /// The block to return. It must have been acquired by calling Lease on the same memory pool instance.
+ internal void Return(MemoryPoolBlock block)
+ {
+#if BLOCK_LEASE_TRACKING
+ Debug.Assert(block.Pool == this, "Returned block was not leased from this pool");
+ Debug.Assert(block.IsLeased, $"Block being returned to pool twice: {block.Leaser}{Environment.NewLine}");
+ block.IsLeased = false;
+#endif
+
+ if (block.Slab != null && block.Slab.IsActive)
+ {
+ _blocks.Enqueue(block);
+ }
+ else
+ {
+ GC.SuppressFinalize(block);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ _disposedValue = true;
+#if DEBUG && !INNER_LOOP
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+#endif
+ if (disposing)
+ {
+ while (_slabs.TryPop(out MemoryPoolSlab slab))
+ {
+ // dispose managed state (managed objects).
+ slab.Dispose();
+ }
+ }
+
+ // Discard blocks in pool
+ while (_blocks.TryDequeue(out MemoryPoolBlock block))
+ {
+ GC.SuppressFinalize(block);
+ }
+
+ // N/A: free unmanaged resources (unmanaged objects) and override a finalizer below.
+
+ // N/A: set large fields to null.
+
+ }
+ }
+ }
+}
diff --git a/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj b/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj
index 58529f72d0..3dc81350fb 100644
--- a/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj
+++ b/src/Kestrel.Transport.Abstractions/Kestrel.Transport.Abstractions.csproj
@@ -13,10 +13,11 @@
-
+
+
-
+
diff --git a/src/shared/ThrowHelper.cs b/src/shared/ThrowHelper.cs
new file mode 100644
index 0000000000..da31583777
--- /dev/null
+++ b/src/shared/ThrowHelper.cs
@@ -0,0 +1,87 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ internal class ThrowHelper
+ {
+ public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset)
+ {
+ throw GetArgumentOutOfRangeException(sourceLength, offset);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset)
+ {
+ if ((uint)offset > (uint)sourceLength)
+ {
+ // Offset is negative or less than array length
+ return new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset));
+ }
+
+ // The third parameter (not passed) length must be out of range
+ return new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length));
+ }
+
+ public static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
+ {
+ throw GetArgumentOutOfRangeException(argument);
+ }
+
+ public static void ThrowInvalidOperationException_ReferenceCountZero()
+ {
+ throw new InvalidOperationException("Can't release when reference count is already zero");
+ }
+
+ public static void ThrowInvalidOperationException_ReturningPinnedBlock()
+ {
+ throw new InvalidOperationException("Can't release when reference count is already zero");
+ }
+
+ public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
+ {
+ throw GetArgumentOutOfRangeException_BufferRequestTooLarge(maxSize);
+ }
+
+ public static void ThrowObjectDisposedException(ExceptionArgument argument)
+ {
+ throw GetObjectDisposedException(argument);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument)
+ {
+ return new ArgumentOutOfRangeException(GetArgumentName(argument));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize)
+ {
+ return new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.size), $"Cannot allocate more than {maxSize} bytes in a single buffer");
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ObjectDisposedException GetObjectDisposedException(ExceptionArgument argument)
+ {
+ return new ObjectDisposedException(GetArgumentName(argument));
+ }
+
+ private static string GetArgumentName(ExceptionArgument argument)
+ {
+ Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument), "The enum value is not defined, please check the ExceptionArgument Enum.");
+
+ return argument.ToString();
+ }
+ }
+
+ internal enum ExceptionArgument
+ {
+ size,
+ offset,
+ length,
+ MemoryPoolBlock
+ }
+}
diff --git a/test/Kestrel.Core.Tests/BufferReaderTests.cs b/test/Kestrel.Core.Tests/BufferReaderTests.cs
new file mode 100644
index 0000000000..294f61cdb0
--- /dev/null
+++ b/test/Kestrel.Core.Tests/BufferReaderTests.cs
@@ -0,0 +1,300 @@
+// 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 PeekReturnsMinuOneByteInTheEnd()
+ {
+ 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 PeekWorkesWithEmptySegments()
+ {
+ 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 WorkesWithEmptyBuffer()
+ {
+ 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);
+ }
+ }
+
+}
diff --git a/test/Kestrel.Core.Tests/BufferWriterTests.cs b/test/Kestrel.Core.Tests/BufferWriterTests.cs
new file mode 100644
index 0000000000..8a071385a4
--- /dev/null
+++ b/test/Kestrel.Core.Tests/BufferWriterTests.cs
@@ -0,0 +1,223 @@
+// 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;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Pipelines.Tests
+{
+ public class BufferWriterTests : IDisposable
+ {
+ protected Pipe Pipe;
+ public BufferWriterTests()
+ {
+ Pipe = new Pipe(new PipeOptions(useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0));
+ }
+
+ public void Dispose()
+ {
+ Pipe.Writer.Complete();
+ Pipe.Reader.Complete();
+ }
+
+ private byte[] Read()
+ {
+ Pipe.Writer.FlushAsync().GetAwaiter().GetResult();
+ Pipe.Writer.Complete();
+ ReadResult readResult = Pipe.Reader.ReadAsync().GetAwaiter().GetResult();
+ byte[] data = readResult.Buffer.ToArray();
+ Pipe.Reader.AdvanceTo(readResult.Buffer.End);
+ return data;
+ }
+
+ [Theory]
+ [InlineData(3, -1, 0)]
+ [InlineData(3, 0, -1)]
+ [InlineData(3, 0, 4)]
+ [InlineData(3, 4, 0)]
+ [InlineData(3, -1, -1)]
+ [InlineData(3, 4, 4)]
+ public void ThrowsForInvalidParameters(int arrayLength, int offset, int length)
+ {
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+ var array = new byte[arrayLength];
+ for (var i = 0; i < array.Length; i++)
+ {
+ array[i] = (byte)(i + 1);
+ }
+
+ writer.Write(new Span(array, 0, 0));
+ writer.Write(new Span(array, array.Length, 0));
+
+ try
+ {
+ writer.Write(new Span(array, offset, length));
+ Assert.True(false);
+ }
+ catch (Exception ex)
+ {
+ Assert.True(ex is ArgumentOutOfRangeException);
+ }
+
+ writer.Write(new Span(array, 0, array.Length));
+ writer.Commit();
+
+ Assert.Equal(array, Read());
+ }
+
+ [Theory]
+ [InlineData(0, 3)]
+ [InlineData(1, 2)]
+ [InlineData(2, 1)]
+ [InlineData(1, 1)]
+ public void CanWriteWithOffsetAndLenght(int offset, int length)
+ {
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+ var array = new byte[] { 1, 2, 3 };
+
+ writer.Write(new Span(array, offset, length));
+ writer.Commit();
+
+ Assert.Equal(array.Skip(offset).Take(length).ToArray(), Read());
+ }
+
+ [Fact]
+ public void CanWriteEmpty()
+ {
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+ var array = new byte[] { };
+
+ writer.Write(array);
+ writer.Write(new Span(array, 0, array.Length));
+ writer.Commit();
+
+ Assert.Equal(array, Read());
+ }
+
+ [Fact]
+ public void CanWriteIntoHeadlessBuffer()
+ {
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+
+ writer.Write(new byte[] { 1, 2, 3 });
+ writer.Commit();
+
+ Assert.Equal(new byte[] { 1, 2, 3 }, Read());
+ }
+
+ [Fact]
+ public void CanWriteMultipleTimes()
+ {
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+
+ writer.Write(new byte[] { 1 });
+ writer.Write(new byte[] { 2 });
+ writer.Write(new byte[] { 3 });
+ writer.Commit();
+
+ Assert.Equal(new byte[] { 1, 2, 3 }, Read());
+ }
+
+ [Fact]
+ public void CanWriteOverTheBlockLength()
+ {
+ Memory memory = Pipe.Writer.GetMemory();
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+
+ IEnumerable source = Enumerable.Range(0, memory.Length).Select(i => (byte)i);
+ byte[] expectedBytes = source.Concat(source).Concat(source).ToArray();
+
+ writer.Write(expectedBytes);
+ writer.Commit();
+
+ Assert.Equal(expectedBytes, Read());
+ }
+
+ [Fact]
+ public void EnsureAllocatesSpan()
+ {
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+ writer.Ensure(10);
+ Assert.True(writer.Span.Length > 10);
+ Assert.Equal(new byte[] { }, Read());
+ }
+
+ [Fact]
+ public void ExposesSpan()
+ {
+ int initialLength = Pipe.Writer.GetMemory().Length;
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+ Assert.Equal(initialLength, writer.Span.Length);
+ Assert.Equal(new byte[] { }, Read());
+ }
+
+ [Fact]
+ public void SlicesSpanAndAdvancesAfterWrite()
+ {
+ int initialLength = Pipe.Writer.GetMemory().Length;
+
+ BufferWriter writer = new BufferWriter(Pipe.Writer);
+
+ writer.Write(new byte[] { 1, 2, 3 });
+ writer.Commit();
+
+ Assert.Equal(initialLength - 3, writer.Span.Length);
+ Assert.Equal(Pipe.Writer.GetMemory().Length, writer.Span.Length);
+ Assert.Equal(new byte[] { 1, 2, 3 }, Read());
+ }
+
+ [Theory]
+ [InlineData(5)]
+ [InlineData(50)]
+ [InlineData(500)]
+ [InlineData(5000)]
+ [InlineData(50000)]
+ public async Task WriteLargeDataBinary(int length)
+ {
+ var data = new byte[length];
+ new Random(length).NextBytes(data);
+ PipeWriter output = Pipe.Writer;
+ output.Write(data);
+ await output.FlushAsync();
+
+ ReadResult result = await Pipe.Reader.ReadAsync();
+ ReadOnlySequence input = result.Buffer;
+ Assert.Equal(data, input.ToArray());
+ Pipe.Reader.AdvanceTo(input.End);
+ }
+
+ [Fact]
+ public async Task CanWriteNothingToBuffer()
+ {
+ PipeWriter buffer = Pipe.Writer;
+ buffer.GetMemory(0);
+ buffer.Advance(0); // doing nothing, the hard way
+ await buffer.FlushAsync();
+ }
+
+ [Fact]
+ public void EmptyWriteDoesNotThrow()
+ {
+ Pipe.Writer.Write(new byte[0]);
+ }
+
+ [Fact]
+ public void ThrowsOnAdvanceOverMemorySize()
+ {
+ Memory buffer = Pipe.Writer.GetMemory(1);
+ var exception = Assert.Throws(() => Pipe.Writer.Advance(buffer.Length + 1));
+ Assert.Equal("Can't advance past buffer size.", exception.Message);
+ }
+
+ [Fact]
+ public void ThrowsOnAdvanceWithNoMemory()
+ {
+ PipeWriter buffer = Pipe.Writer;
+ var exception = Assert.Throws(() => buffer.Advance(1));
+ Assert.Equal("No writing operation. Make sure GetMemory() was called.", exception.Message);
+ }
+ }
+}
diff --git a/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj b/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj
index 3229fd0fa7..fdbbd000ef 100644
--- a/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj
+++ b/test/Kestrel.Core.Tests/Kestrel.Core.Tests.csproj
@@ -23,7 +23,6 @@
-
diff --git a/test/shared/BufferSegment.cs b/test/shared/BufferSegment.cs
new file mode 100644
index 0000000000..d89f4addd5
--- /dev/null
+++ b/test/shared/BufferSegment.cs
@@ -0,0 +1,24 @@
+// 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.
+
+namespace System.Buffers
+{
+ internal class BufferSegment : ReadOnlySequenceSegment
+ {
+ public BufferSegment(Memory memory)
+ {
+ Memory = memory;
+ }
+
+ public BufferSegment Append(Memory memory)
+ {
+ var segment = new BufferSegment(memory)
+ {
+ RunningIndex = RunningIndex + Memory.Length
+ };
+ Next = segment;
+ return segment;
+ }
+ }
+}
diff --git a/test/shared/CustomMemoryForTest.cs b/test/shared/CustomMemoryForTest.cs
new file mode 100644
index 0000000000..20406f0a99
--- /dev/null
+++ b/test/shared/CustomMemoryForTest.cs
@@ -0,0 +1,45 @@
+// 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.
+
+namespace System.Buffers
+{
+ internal class CustomMemoryForTest : IMemoryOwner
+ {
+ private bool _disposed;
+ private T[] _array;
+ private readonly int _offset;
+ private readonly int _length;
+
+ public CustomMemoryForTest(T[] array): this(array, 0, array.Length)
+ {
+ }
+
+ public CustomMemoryForTest(T[] array, int offset, int length)
+ {
+ _array = array;
+ _offset = offset;
+ _length = length;
+ }
+
+ public Memory Memory
+ {
+ get
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(CustomMemoryForTest));
+ return new Memory(_array, _offset, _length);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _array = null;
+ _disposed = true;
+ }
+ }
+}
+
diff --git a/test/shared/ReadOnlySequenceFactory.cs b/test/shared/ReadOnlySequenceFactory.cs
new file mode 100644
index 0000000000..0fc0c6585f
--- /dev/null
+++ b/test/shared/ReadOnlySequenceFactory.cs
@@ -0,0 +1,148 @@
+// 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 System.Collections.Generic;
+using System.Text;
+
+namespace System.Buffers
+{
+ internal abstract class ReadOnlySequenceFactory
+ {
+ public static ReadOnlySequenceFactory ArrayFactory { get; } = new ArrayTestSequenceFactory();
+ public static ReadOnlySequenceFactory MemoryFactory { get; } = new MemoryTestSequenceFactory();
+ public static ReadOnlySequenceFactory OwnedMemoryFactory { get; } = new OwnedMemoryTestSequenceFactory();
+ public static ReadOnlySequenceFactory SingleSegmentFactory { get; } = new SingleSegmentTestSequenceFactory();
+ public static ReadOnlySequenceFactory SegmentPerByteFactory { get; } = new BytePerSegmentTestSequenceFactory();
+
+ public abstract ReadOnlySequence CreateOfSize(int size);
+ public abstract ReadOnlySequence CreateWithContent(byte[] data);
+
+ public ReadOnlySequence CreateWithContent(string data)
+ {
+ return CreateWithContent(Encoding.ASCII.GetBytes(data));
+ }
+
+ internal class ArrayTestSequenceFactory : ReadOnlySequenceFactory
+ {
+ public override ReadOnlySequence CreateOfSize(int size)
+ {
+ return new ReadOnlySequence(new byte[size + 20], 10, size);
+ }
+
+ public override ReadOnlySequence CreateWithContent(byte[] data)
+ {
+ var startSegment = new byte[data.Length + 20];
+ Array.Copy(data, 0, startSegment, 10, data.Length);
+ return new ReadOnlySequence(startSegment, 10, data.Length);
+ }
+ }
+
+ internal class MemoryTestSequenceFactory : ReadOnlySequenceFactory
+ {
+ public override ReadOnlySequence CreateOfSize(int size)
+ {
+ return CreateWithContent(new byte[size]);
+ }
+
+ public override ReadOnlySequence CreateWithContent(byte[] data)
+ {
+ var startSegment = new byte[data.Length + 20];
+ Array.Copy(data, 0, startSegment, 10, data.Length);
+ return new ReadOnlySequence(new Memory(startSegment, 10, data.Length));
+ }
+ }
+
+ internal class OwnedMemoryTestSequenceFactory : ReadOnlySequenceFactory
+ {
+ public override ReadOnlySequence CreateOfSize(int size)
+ {
+ return CreateWithContent(new byte[size]);
+ }
+
+ public override ReadOnlySequence CreateWithContent(byte[] data)
+ {
+ var startSegment = new byte[data.Length + 20];
+ Array.Copy(data, 0, startSegment, 10, data.Length);
+ return new ReadOnlySequence(new CustomMemoryForTest(startSegment, 10, data.Length).Memory);
+ }
+ }
+
+ internal class SingleSegmentTestSequenceFactory : ReadOnlySequenceFactory
+ {
+ public override ReadOnlySequence CreateOfSize(int size)
+ {
+ return CreateWithContent(new byte[size]);
+ }
+
+ public override ReadOnlySequence CreateWithContent(byte[] data)
+ {
+ return CreateSegments(data);
+ }
+ }
+
+ internal class BytePerSegmentTestSequenceFactory : ReadOnlySequenceFactory
+ {
+ public override ReadOnlySequence CreateOfSize(int size)
+ {
+ return CreateWithContent(new byte[size]);
+ }
+
+ public override ReadOnlySequence CreateWithContent(byte[] data)
+ {
+ var segments = new List();
+
+ segments.Add(Array.Empty());
+ foreach (var b in data)
+ {
+ segments.Add(new[] { b });
+ segments.Add(Array.Empty());
+ }
+
+ return CreateSegments(segments.ToArray());
+ }
+ }
+
+ public static ReadOnlySequence CreateSegments(params byte[][] inputs)
+ {
+ if (inputs == null || inputs.Length == 0)
+ {
+ throw new InvalidOperationException();
+ }
+
+ int i = 0;
+
+ BufferSegment last = null;
+ BufferSegment first = null;
+
+ do
+ {
+ byte[] s = inputs[i];
+ int length = s.Length;
+ int dataOffset = length;
+ var chars = new byte[length * 2];
+
+ for (int j = 0; j < length; j++)
+ {
+ chars[dataOffset + j] = s[j];
+ }
+
+ // Create a segment that has offset relative to the OwnedMemory and OwnedMemory itself has offset relative to array
+ var memory = new Memory(chars).Slice(length, length);
+
+ if (first == null)
+ {
+ first = new BufferSegment(memory);
+ last = first;
+ }
+ else
+ {
+ last = last.Append(memory);
+ }
+ i++;
+ } while (i < inputs.Length);
+
+ return new ReadOnlySequence(first, 0, last, last.Memory.Length);
+ }
+ }
+}