| | | 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) |
| | | 51 | | { |
| | | 52 | | Debug.Assert(buffer.Length > 0); |
| | | 53 | | |
| | | 54 | | _usePool = false; |
| | | 55 | | _bytes = buffer; |
| | | 56 | | _activeStart = 0; |
| | | 57 | | _availableStart = 0; |
| | | 58 | | } |
| | | 59 | | |
| | | 60 | | public void Dispose() |
| | 0 | 61 | | { |
| | 0 | 62 | | DiscardAll(); |
| | | 63 | | |
| | 0 | 64 | | byte[] array = _bytes; |
| | 0 | 65 | | _bytes = null!; |
| | | 66 | | |
| | 0 | 67 | | if (array is not null) |
| | 0 | 68 | | { |
| | 0 | 69 | | ReturnBufferIfPooled(array); |
| | 0 | 70 | | } |
| | 0 | 71 | | } |
| | | 72 | | |
| | | 73 | | // This is different from Dispose as the instance remains usable afterwards (_bytes will not be null). |
| | | 74 | | public void ClearAndReturnBuffer() |
| | 0 | 75 | | { |
| | 0 | 76 | | Debug.Assert(_usePool); |
| | 0 | 77 | | Debug.Assert(_bytes is not null); |
| | | 78 | | |
| | 0 | 79 | | DiscardAll(); |
| | | 80 | | |
| | 0 | 81 | | byte[] bufferToReturn = _bytes; |
| | 0 | 82 | | _bytes = Array.Empty<byte>(); |
| | 0 | 83 | | ReturnBufferIfPooled(bufferToReturn); |
| | 0 | 84 | | } |
| | | 85 | | |
| | 0 | 86 | | public int ActiveLength => _availableStart - _activeStart; |
| | 0 | 87 | | public Span<byte> ActiveSpan => new Span<byte>(_bytes, _activeStart, _availableStart - _activeStart); |
| | | 88 | | public ReadOnlySpan<byte> ActiveReadOnlySpan => new ReadOnlySpan<byte>(_bytes, _activeStart, _availableStart - _ |
| | 0 | 89 | | public Memory<byte> ActiveMemory => new Memory<byte>(_bytes, _activeStart, _availableStart - _activeStart); |
| | | 90 | | |
| | 0 | 91 | | public int AvailableLength => _bytes.Length - _availableStart; |
| | 0 | 92 | | public Span<byte> AvailableSpan => _bytes.AsSpan(_availableStart); |
| | 0 | 93 | | public Memory<byte> AvailableMemory => _bytes.AsMemory(_availableStart); |
| | | 94 | | public Memory<byte> AvailableMemorySliced(int length) => new Memory<byte>(_bytes, _availableStart, length); |
| | | 95 | | |
| | 0 | 96 | | public int Capacity => _bytes.Length; |
| | | 97 | | public int ActiveStartOffset => _activeStart; |
| | | 98 | | |
| | | 99 | | public byte[] DangerousGetUnderlyingBuffer() => _bytes; |
| | | 100 | | |
| | | 101 | | public void Discard(int byteCount) |
| | | 102 | | { |
| | | 103 | | Debug.Assert(byteCount <= ActiveLength, $"Expected {byteCount} <= {ActiveLength}"); |
| | | 104 | | _activeStart += byteCount; |
| | | 105 | | |
| | | 106 | | if (_activeStart == _availableStart) |
| | | 107 | | { |
| | | 108 | | _activeStart = 0; |
| | | 109 | | _availableStart = 0; |
| | | 110 | | } |
| | | 111 | | } |
| | | 112 | | |
| | | 113 | | public void DiscardAll() |
| | 0 | 114 | | { |
| | 0 | 115 | | _activeStart = 0; |
| | 0 | 116 | | _availableStart = 0; |
| | 0 | 117 | | } |
| | | 118 | | |
| | | 119 | | public void Commit(int byteCount) |
| | 0 | 120 | | { |
| | 0 | 121 | | Debug.Assert(byteCount <= AvailableLength); |
| | 0 | 122 | | _availableStart += byteCount; |
| | 0 | 123 | | } |
| | | 124 | | |
| | | 125 | | // Ensure at least [byteCount] bytes to write to. |
| | | 126 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 127 | | public void EnsureAvailableSpace(int byteCount) |
| | 0 | 128 | | { |
| | 0 | 129 | | if (byteCount > AvailableLength) |
| | 0 | 130 | | { |
| | 0 | 131 | | EnsureAvailableSpaceCore(byteCount); |
| | 0 | 132 | | } |
| | 0 | 133 | | } |
| | | 134 | | |
| | | 135 | | private void EnsureAvailableSpaceCore(int byteCount) |
| | 0 | 136 | | { |
| | 0 | 137 | | Debug.Assert(AvailableLength < byteCount); |
| | | 138 | | |
| | 0 | 139 | | if (_bytes.Length == 0) |
| | 0 | 140 | | { |
| | 0 | 141 | | Debug.Assert(_usePool && _activeStart == 0 && _availableStart == 0); |
| | 0 | 142 | | _bytes = ArrayPool<byte>.Shared.Rent(byteCount); |
| | 0 | 143 | | return; |
| | | 144 | | } |
| | | 145 | | |
| | 0 | 146 | | int totalFree = _activeStart + AvailableLength; |
| | 0 | 147 | | if (byteCount <= totalFree) |
| | 0 | 148 | | { |
| | | 149 | | // We can free up enough space by just shifting the bytes down, so do so. |
| | 0 | 150 | | Buffer.BlockCopy(_bytes, _activeStart, _bytes, 0, ActiveLength); |
| | 0 | 151 | | _availableStart = ActiveLength; |
| | 0 | 152 | | _activeStart = 0; |
| | 0 | 153 | | Debug.Assert(byteCount <= AvailableLength); |
| | 0 | 154 | | return; |
| | | 155 | | } |
| | | 156 | | |
| | 0 | 157 | | int desiredSize = ActiveLength + byteCount; |
| | | 158 | | |
| | 0 | 159 | | if ((uint)desiredSize > ArrayMaxLength) |
| | 0 | 160 | | { |
| | 0 | 161 | | throw new OutOfMemoryException(); |
| | | 162 | | } |
| | | 163 | | |
| | | 164 | | // Double the existing buffer size (capped at Array.MaxLength). |
| | 0 | 165 | | int newSize = Math.Max(desiredSize, (int)Math.Min(ArrayMaxLength, 2 * (uint)_bytes.Length)); |
| | | 166 | | |
| | 0 | 167 | | byte[] newBytes = _usePool ? |
| | 0 | 168 | | ArrayPool<byte>.Shared.Rent(newSize) : |
| | 0 | 169 | | new byte[newSize]; |
| | 0 | 170 | | byte[] oldBytes = _bytes; |
| | | 171 | | |
| | 0 | 172 | | if (ActiveLength != 0) |
| | 0 | 173 | | { |
| | 0 | 174 | | Buffer.BlockCopy(oldBytes, _activeStart, newBytes, 0, ActiveLength); |
| | 0 | 175 | | } |
| | | 176 | | |
| | 0 | 177 | | _availableStart = ActiveLength; |
| | 0 | 178 | | _activeStart = 0; |
| | | 179 | | |
| | 0 | 180 | | _bytes = newBytes; |
| | 0 | 181 | | ReturnBufferIfPooled(oldBytes); |
| | | 182 | | |
| | 0 | 183 | | Debug.Assert(byteCount <= AvailableLength); |
| | 0 | 184 | | } |
| | | 185 | | |
| | | 186 | | public void Grow() |
| | | 187 | | { |
| | | 188 | | EnsureAvailableSpaceCore(AvailableLength + 1); |
| | | 189 | | } |
| | | 190 | | |
| | | 191 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 192 | | private void ReturnBufferIfPooled(byte[] buffer) |
| | 0 | 193 | | { |
| | | 194 | | // The buffer may be Array.Empty<byte>() |
| | 0 | 195 | | if (_usePool && buffer.Length > 0) |
| | 0 | 196 | | { |
| | 0 | 197 | | ArrayPool<byte>.Shared.Return(buffer); |
| | 0 | 198 | | } |
| | 0 | 199 | | } |
| | | 200 | | } |
| | | 201 | | } |
| | | 202 | | |