| | | 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.Diagnostics; |
| | | 5 | | |
| | | 6 | | namespace System.Net.Http.HPack |
| | | 7 | | { |
| | | 8 | | internal sealed class DynamicTable |
| | | 9 | | { |
| | | 10 | | private HeaderField[] _buffer; |
| | | 11 | | private int _maxSize; |
| | | 12 | | private int _size; |
| | | 13 | | private int _count; |
| | | 14 | | private int _insertIndex; |
| | | 15 | | private int _removeIndex; |
| | | 16 | | |
| | 0 | 17 | | public DynamicTable(int maxSize) |
| | 0 | 18 | | { |
| | 0 | 19 | | _buffer = []; |
| | 0 | 20 | | _maxSize = maxSize; |
| | 0 | 21 | | } |
| | | 22 | | |
| | | 23 | | public int Count => _count; |
| | | 24 | | |
| | | 25 | | public int Size => _size; |
| | | 26 | | |
| | | 27 | | public int MaxSize => _maxSize; |
| | | 28 | | |
| | | 29 | | public ref readonly HeaderField this[int index] |
| | | 30 | | { |
| | | 31 | | get |
| | 0 | 32 | | { |
| | 0 | 33 | | if (index >= _count) |
| | 0 | 34 | | { |
| | | 35 | | #pragma warning disable CA2201 // Do not raise reserved exception types |
| | | 36 | | // Helpful to act like static table (array) |
| | 0 | 37 | | throw new IndexOutOfRangeException(); |
| | | 38 | | #pragma warning restore CA2201 |
| | | 39 | | } |
| | | 40 | | |
| | 0 | 41 | | index = _insertIndex - index - 1; |
| | | 42 | | |
| | 0 | 43 | | if (index < 0) |
| | 0 | 44 | | { |
| | | 45 | | // _buffer is circular; wrap the index back around. |
| | 0 | 46 | | index += _buffer.Length; |
| | 0 | 47 | | } |
| | | 48 | | |
| | 0 | 49 | | return ref _buffer[index]; |
| | 0 | 50 | | } |
| | | 51 | | } |
| | | 52 | | |
| | | 53 | | public void Insert(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) |
| | 0 | 54 | | { |
| | 0 | 55 | | Insert(staticTableIndex: null, name, value); |
| | 0 | 56 | | } |
| | | 57 | | |
| | | 58 | | public void Insert(int? staticTableIndex, ReadOnlySpan<byte> name, ReadOnlySpan<byte> value) |
| | 0 | 59 | | { |
| | 0 | 60 | | int entryLength = HeaderField.GetLength(name.Length, value.Length); |
| | 0 | 61 | | EnsureAvailable(entryLength); |
| | | 62 | | |
| | 0 | 63 | | if (entryLength > _maxSize) |
| | 0 | 64 | | { |
| | | 65 | | // http://httpwg.org/specs/rfc7541.html#rfc.section.4.4 |
| | | 66 | | // It is not an error to attempt to add an entry that is larger than the maximum size; |
| | | 67 | | // an attempt to add an entry larger than the maximum size causes the table to be emptied |
| | | 68 | | // of all existing entries and results in an empty table. |
| | 0 | 69 | | return; |
| | | 70 | | } |
| | | 71 | | |
| | | 72 | | // Ensure that we have at least one slot available. |
| | 0 | 73 | | if (_count == _buffer.Length) |
| | 0 | 74 | | { |
| | 0 | 75 | | int maxCapacity = _maxSize / HeaderField.RfcOverhead; |
| | 0 | 76 | | Debug.Assert(_count + 1 <= maxCapacity); |
| | | 77 | | |
| | | 78 | | // Double the size of the current buffer, starting with at least 16 entries. |
| | 0 | 79 | | int newBufferSize = Math.Min(Math.Max(16, _buffer.Length * 2), maxCapacity); |
| | 0 | 80 | | Debug.Assert(newBufferSize > _count); |
| | | 81 | | |
| | 0 | 82 | | var newBuffer = new HeaderField[newBufferSize]; |
| | | 83 | | |
| | 0 | 84 | | int headCount = Math.Min(_buffer.Length - _removeIndex, _count); |
| | 0 | 85 | | int tailCount = _count - headCount; |
| | | 86 | | |
| | 0 | 87 | | Array.Copy(_buffer, _removeIndex, newBuffer, 0, headCount); |
| | 0 | 88 | | Array.Copy(_buffer, 0, newBuffer, headCount, tailCount); |
| | | 89 | | |
| | 0 | 90 | | _buffer = newBuffer; |
| | 0 | 91 | | _removeIndex = 0; |
| | 0 | 92 | | _insertIndex = _count; |
| | 0 | 93 | | } |
| | | 94 | | |
| | 0 | 95 | | var entry = new HeaderField(staticTableIndex, name, value); |
| | 0 | 96 | | _buffer[_insertIndex] = entry; |
| | | 97 | | |
| | 0 | 98 | | if (++_insertIndex == _buffer.Length) |
| | 0 | 99 | | { |
| | 0 | 100 | | _insertIndex = 0; |
| | 0 | 101 | | } |
| | | 102 | | |
| | 0 | 103 | | _size += entry.Length; |
| | 0 | 104 | | _count++; |
| | 0 | 105 | | } |
| | | 106 | | |
| | | 107 | | public void UpdateMaxSize(int maxSize) |
| | 0 | 108 | | { |
| | 0 | 109 | | int previousMax = _maxSize; |
| | 0 | 110 | | _maxSize = maxSize; |
| | | 111 | | |
| | 0 | 112 | | if (maxSize < previousMax) |
| | 0 | 113 | | { |
| | 0 | 114 | | EnsureAvailable(0); |
| | 0 | 115 | | } |
| | 0 | 116 | | } |
| | | 117 | | |
| | | 118 | | private void EnsureAvailable(int available) |
| | 0 | 119 | | { |
| | 0 | 120 | | while (_count > 0 && _maxSize - _size < available) |
| | 0 | 121 | | { |
| | 0 | 122 | | ref HeaderField field = ref _buffer[_removeIndex]; |
| | 0 | 123 | | _size -= field.Length; |
| | 0 | 124 | | field = default; |
| | | 125 | | |
| | 0 | 126 | | _count--; |
| | | 127 | | |
| | 0 | 128 | | if (++_removeIndex == _buffer.Length) |
| | 0 | 129 | | { |
| | 0 | 130 | | _removeIndex = 0; |
| | 0 | 131 | | } |
| | 0 | 132 | | } |
| | 0 | 133 | | } |
| | | 134 | | } |
| | | 135 | | } |