1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.netty.handler.codec.http3;
18
19 import java.util.Arrays;
20
21 import static io.netty.handler.codec.http3.QpackHeaderField.ENTRY_OVERHEAD;
22 import static io.netty.handler.codec.http3.QpackUtil.MAX_HEADER_TABLE_SIZE;
23 import static io.netty.handler.codec.http3.QpackUtil.MIN_HEADER_TABLE_SIZE;
24 import static io.netty.handler.codec.http3.QpackUtil.toIntOrThrow;
25 import static java.lang.Math.floorDiv;
26
27 final class QpackDecoderDynamicTable {
28 private static final QpackException GET_ENTRY_ILLEGAL_INDEX_VALUE =
29 QpackException.newStatic(QpackDecoderDynamicTable.class, "getEntry(...)",
30 "QPACK - illegal decoder dynamic table index value");
31 private static final QpackException HEADER_TOO_LARGE =
32 QpackException.newStatic(QpackDecoderDynamicTable.class, "add(...)", "QPACK - header entry too large.");
33
34
35 private QpackHeaderField[] fields;
36 private int head;
37 private int tail;
38 private long size;
39 private long capacity = -1;
40 private int insertCount;
41
42 int length() {
43 return head < tail ? fields.length - tail + head : head - tail;
44 }
45
46 long size() {
47 return size;
48 }
49
50 int insertCount() {
51 return insertCount;
52 }
53
54 QpackHeaderField getEntry(int index) throws QpackException {
55 if (index < 0 || fields == null || index >= fields.length) {
56 throw GET_ENTRY_ILLEGAL_INDEX_VALUE;
57 }
58 QpackHeaderField entry = fields[index];
59 if (entry == null) {
60 throw GET_ENTRY_ILLEGAL_INDEX_VALUE;
61 }
62 return entry;
63 }
64
65 QpackHeaderField getEntryRelativeEncodedField(int index) throws QpackException {
66
67 return getEntry(moduloIndex(index));
68 }
69
70 QpackHeaderField getEntryRelativeEncoderInstructions(int index) throws QpackException {
71
72
73 return getEntry(index > tail ? fields.length - index + tail : tail - index);
74 }
75
76 void add(QpackHeaderField header) throws QpackException {
77 long headerSize = header.size();
78 if (headerSize > capacity) {
79 throw HEADER_TOO_LARGE;
80 }
81 while (capacity - size < headerSize) {
82 remove();
83 }
84 insertCount++;
85 fields[getAndIncrementHead()] = header;
86 size += headerSize;
87 }
88
89 private void remove() {
90 QpackHeaderField removed = fields[tail];
91 if (removed == null) {
92 return;
93 }
94 size -= removed.size();
95 fields[getAndIncrementTail()] = null;
96 }
97
98 void clear() {
99 if (fields != null) {
100 Arrays.fill(fields, null);
101 }
102 head = 0;
103 tail = 0;
104 size = 0;
105 }
106
107 void setCapacity(long capacity) throws QpackException {
108 if (capacity < MIN_HEADER_TABLE_SIZE || capacity > MAX_HEADER_TABLE_SIZE) {
109 throw new IllegalArgumentException("capacity is invalid: " + capacity);
110 }
111
112 if (this.capacity == capacity) {
113 return;
114 }
115 this.capacity = capacity;
116
117 if (capacity == 0) {
118 clear();
119 } else {
120
121 while (size > capacity) {
122 remove();
123 }
124 }
125
126 int maxEntries = toIntOrThrow(2 * floorDiv(capacity, ENTRY_OVERHEAD));
127
128
129 if (fields != null && fields.length == maxEntries) {
130 return;
131 }
132
133 QpackHeaderField[] tmp = new QpackHeaderField[maxEntries];
134
135
136 int len = length();
137 if (fields != null && tail != head) {
138 if (head > tail) {
139 System.arraycopy(fields, tail, tmp, 0, head - tail);
140 } else {
141 System.arraycopy(fields, 0, tmp, 0, head);
142 System.arraycopy(fields, tail, tmp, head, fields.length - tail);
143 }
144 }
145
146 tail = 0;
147 head = tail + len;
148 fields = tmp;
149 }
150
151 private int getAndIncrementHead() {
152 int val = this.head;
153 this.head = safeIncrementIndex(val);
154 return val;
155 }
156
157 private int getAndIncrementTail() {
158 int val = this.tail;
159 this.tail = safeIncrementIndex(val);
160 return val;
161 }
162
163 private int safeIncrementIndex(int index) {
164 return ++index % fields.length;
165 }
166
167 private int moduloIndex(int index) {
168 return fields == null ? index : index % fields.length;
169 }
170 }