| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Runtime.CompilerServices; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | |
| | | 9 | | namespace System.Net |
| | | 10 | | { |
| | | 11 | | // Warning: Mutable struct! |
| | | 12 | | // The purpose of this struct is to simplify buffer management. |
| | | 13 | | // It manages a sliding buffer where bytes can be added at the end and removed at the beginning. |
| | | 14 | | // [ActiveSpan/Memory] contains the current buffer contents; these bytes will be preserved |
| | | 15 | | // (copied, if necessary) on any call to EnsureAvailableBytes. |
| | | 16 | | // [AvailableSpan/Memory] contains the available bytes past the end of the current content, |
| | | 17 | | // and can be written to in order to add data to the end of the buffer. |
| | | 18 | | // Commit(byteCount) will extend the ActiveSpan by [byteCount] bytes into the AvailableSpan. |
| | | 19 | | // Discard(byteCount) will discard [byteCount] bytes as the beginning of the ActiveSpan. |
| | | 20 | | |
| | | 21 | | [StructLayout(LayoutKind.Auto)] |
| | | 22 | | internal struct ArrayBuffer : IDisposable |
| | | 23 | | { |
| | | 24 | | #if NET |
| | 0 | 25 | | private static int ArrayMaxLength => Array.MaxLength; |
| | | 26 | | #else |
| | | 27 | | private const int ArrayMaxLength = 0X7FFFFFC7; |
| | | 28 | | #endif |
| | | 29 | | |
| | | 30 | | private readonly bool _usePool; |
| | | 31 | | private byte[] _bytes; |
| | | 32 | | private int _activeStart; |
| | | 33 | | private int _availableStart; |
| | | 34 | | |
| | | 35 | | // Invariants: |
| | | 36 | | // 0 <= _activeStart <= _availableStart <= bytes.Length |
| | | 37 | | |
| | | 38 | | public ArrayBuffer(int initialSize, bool usePool = false) |
| | 0 | 39 | | { |
| | 0 | 40 | | Debug.Assert(initialSize > 0 || usePool); |
| | | 41 | | |
| | 0 | 42 | | _usePool = usePool; |
| | 0 | 43 | | _bytes = initialSize == 0 |
| | 0 | 44 | | ? Array.Empty<byte>() |
| | 0 | 45 | | : usePool ? ArrayPool<byte>.Shared.Rent(initialSize) : new byte[initialSize]; |
| | 0 | 46 | | _activeStart = 0; |
| | 0 | 47 | | _availableStart = 0; |
| | 0 | 48 | | } |
| | | 49 | | |
| | | 50 | | public ArrayBuffer(byte[] buffer) |
| | 0 | 51 | | { |
| | 0 | 52 | | Debug.Assert(buffer.Length > 0); |
| | | 53 | | |
| | 0 | 54 | | _usePool = false; |
| | 0 | 55 | | _bytes = buffer; |
| | 0 | 56 | | _activeStart = 0; |
| | 0 | 57 | | _availableStart = 0; |
| | 0 | 58 | | } |
| | | 59 | | |
| | | 60 | | public void Dispose() |
| | 0 | 61 | | { |
| | 0 | 62 | | _activeStart = 0; |
| | 0 | 63 | | _availableStart = 0; |
| | | 64 | | |
| | 0 | 65 | | byte[] array = _bytes; |
| | 0 | 66 | | _bytes = null!; |
| | | 67 | | |
| | 0 | 68 | | if (array is not null) |
| | 0 | 69 | | { |
| | 0 | 70 | | ReturnBufferIfPooled(array); |
| | 0 | 71 | | } |
| | 0 | 72 | | } |
| | | 73 | | |
| | | 74 | | // This is different from Dispose as the instance remains usable afterwards (_bytes will not be null). |
| | | 75 | | public void ClearAndReturnBuffer() |
| | 0 | 76 | | { |
| | 0 | 77 | | Debug.Assert(_usePool); |
| | 0 | 78 | | Debug.Assert(_bytes is not null); |
| | | 79 | | |
| | 0 | 80 | | _activeStart = 0; |
| | 0 | 81 | | _availableStart = 0; |
| | | 82 | | |
| | 0 | 83 | | byte[] bufferToReturn = _bytes; |
| | 0 | 84 | | _bytes = Array.Empty<byte>(); |
| | 0 | 85 | | ReturnBufferIfPooled(bufferToReturn); |
| | 0 | 86 | | } |
| | | 87 | | |
| | 0 | 88 | | public int ActiveLength => _availableStart - _activeStart; |
| | 0 | 89 | | public Span<byte> ActiveSpan => new Span<byte>(_bytes, _activeStart, _availableStart - _activeStart); |
| | 0 | 90 | | public ReadOnlySpan<byte> ActiveReadOnlySpan => new ReadOnlySpan<byte>(_bytes, _activeStart, _availableStart - _ |
| | 0 | 91 | | public Memory<byte> ActiveMemory => new Memory<byte>(_bytes, _activeStart, _availableStart - _activeStart); |
| | | 92 | | |
| | 0 | 93 | | public int AvailableLength => _bytes.Length - _availableStart; |
| | 0 | 94 | | public Span<byte> AvailableSpan => _bytes.AsSpan(_availableStart); |
| | 0 | 95 | | public Memory<byte> AvailableMemory => _bytes.AsMemory(_availableStart); |
| | 0 | 96 | | public Memory<byte> AvailableMemorySliced(int length) => new Memory<byte>(_bytes, _availableStart, length); |
| | | 97 | | |
| | 0 | 98 | | public int Capacity => _bytes.Length; |
| | 0 | 99 | | public int ActiveStartOffset => _activeStart; |
| | | 100 | | |
| | 0 | 101 | | public byte[] DangerousGetUnderlyingBuffer() => _bytes; |
| | | 102 | | |
| | | 103 | | public void Discard(int byteCount) |
| | 0 | 104 | | { |
| | 0 | 105 | | Debug.Assert(byteCount <= ActiveLength, $"Expected {byteCount} <= {ActiveLength}"); |
| | 0 | 106 | | _activeStart += byteCount; |
| | | 107 | | |
| | 0 | 108 | | if (_activeStart == _availableStart) |
| | 0 | 109 | | { |
| | 0 | 110 | | _activeStart = 0; |
| | 0 | 111 | | _availableStart = 0; |
| | 0 | 112 | | } |
| | 0 | 113 | | } |
| | | 114 | | |
| | | 115 | | public void Commit(int byteCount) |
| | 0 | 116 | | { |
| | 0 | 117 | | Debug.Assert(byteCount <= AvailableLength); |
| | 0 | 118 | | _availableStart += byteCount; |
| | 0 | 119 | | } |
| | | 120 | | |
| | | 121 | | // Ensure at least [byteCount] bytes to write to. |
| | | 122 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 123 | | public void EnsureAvailableSpace(int byteCount) |
| | 0 | 124 | | { |
| | 0 | 125 | | if (byteCount > AvailableLength) |
| | 0 | 126 | | { |
| | 0 | 127 | | EnsureAvailableSpaceCore(byteCount); |
| | 0 | 128 | | } |
| | 0 | 129 | | } |
| | | 130 | | |
| | | 131 | | private void EnsureAvailableSpaceCore(int byteCount) |
| | 0 | 132 | | { |
| | 0 | 133 | | Debug.Assert(AvailableLength < byteCount); |
| | | 134 | | |
| | 0 | 135 | | if (_bytes.Length == 0) |
| | 0 | 136 | | { |
| | 0 | 137 | | Debug.Assert(_usePool && _activeStart == 0 && _availableStart == 0); |
| | 0 | 138 | | _bytes = ArrayPool<byte>.Shared.Rent(byteCount); |
| | 0 | 139 | | return; |
| | | 140 | | } |
| | | 141 | | |
| | 0 | 142 | | int totalFree = _activeStart + AvailableLength; |
| | 0 | 143 | | if (byteCount <= totalFree) |
| | 0 | 144 | | { |
| | | 145 | | // We can free up enough space by just shifting the bytes down, so do so. |
| | 0 | 146 | | Buffer.BlockCopy(_bytes, _activeStart, _bytes, 0, ActiveLength); |
| | 0 | 147 | | _availableStart = ActiveLength; |
| | 0 | 148 | | _activeStart = 0; |
| | 0 | 149 | | Debug.Assert(byteCount <= AvailableLength); |
| | 0 | 150 | | return; |
| | | 151 | | } |
| | | 152 | | |
| | 0 | 153 | | int desiredSize = ActiveLength + byteCount; |
| | | 154 | | |
| | 0 | 155 | | if ((uint)desiredSize > ArrayMaxLength) |
| | 0 | 156 | | { |
| | 0 | 157 | | throw new OutOfMemoryException(); |
| | | 158 | | } |
| | | 159 | | |
| | | 160 | | // Double the existing buffer size (capped at Array.MaxLength). |
| | 0 | 161 | | int newSize = Math.Max(desiredSize, (int)Math.Min(ArrayMaxLength, 2 * (uint)_bytes.Length)); |
| | | 162 | | |
| | 0 | 163 | | byte[] newBytes = _usePool ? |
| | 0 | 164 | | ArrayPool<byte>.Shared.Rent(newSize) : |
| | 0 | 165 | | new byte[newSize]; |
| | 0 | 166 | | byte[] oldBytes = _bytes; |
| | | 167 | | |
| | 0 | 168 | | if (ActiveLength != 0) |
| | 0 | 169 | | { |
| | 0 | 170 | | Buffer.BlockCopy(oldBytes, _activeStart, newBytes, 0, ActiveLength); |
| | 0 | 171 | | } |
| | | 172 | | |
| | 0 | 173 | | _availableStart = ActiveLength; |
| | 0 | 174 | | _activeStart = 0; |
| | | 175 | | |
| | 0 | 176 | | _bytes = newBytes; |
| | 0 | 177 | | ReturnBufferIfPooled(oldBytes); |
| | | 178 | | |
| | 0 | 179 | | Debug.Assert(byteCount <= AvailableLength); |
| | 0 | 180 | | } |
| | | 181 | | |
| | | 182 | | public void Grow() |
| | 0 | 183 | | { |
| | 0 | 184 | | EnsureAvailableSpaceCore(AvailableLength + 1); |
| | 0 | 185 | | } |
| | | 186 | | |
| | | 187 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 188 | | private void ReturnBufferIfPooled(byte[] buffer) |
| | 0 | 189 | | { |
| | | 190 | | // The buffer may be Array.Empty<byte>() |
| | 0 | 191 | | if (_usePool && buffer.Length > 0) |
| | 0 | 192 | | { |
| | 0 | 193 | | ArrayPool<byte>.Shared.Return(buffer); |
| | 0 | 194 | | } |
| | 0 | 195 | | } |
| | | 196 | | } |
| | | 197 | | } |