1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package io.netty5.handler.codec.http2;
33
34 import io.netty5.buffer.api.Buffer;
35 import io.netty5.handler.codec.http2.HpackUtil.IndexType;
36 import io.netty5.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
37 import io.netty5.util.AsciiString;
38 import io.netty5.util.CharsetUtil;
39
40 import java.util.Arrays;
41 import java.util.Map;
42
43 import static io.netty5.handler.codec.http2.HpackUtil.equalsConstantTime;
44 import static io.netty5.handler.codec.http2.HpackUtil.equalsVariableTime;
45 import static io.netty5.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
46 import static io.netty5.handler.codec.http2.Http2CodecUtil.MAX_HEADER_LIST_SIZE;
47 import static io.netty5.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
48 import static io.netty5.handler.codec.http2.Http2CodecUtil.MIN_HEADER_LIST_SIZE;
49 import static io.netty5.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
50 import static io.netty5.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
51 import static io.netty5.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
52 import static io.netty5.handler.codec.http2.Http2Exception.connectionError;
53 import static io.netty5.util.internal.MathUtil.findNextPositivePowerOfTwo;
54 import static java.lang.Math.max;
55 import static java.lang.Math.min;
56
57
58
59
60
61
62
63
64 final class HpackEncoder {
65 static final int NOT_FOUND = -1;
66 static final int HUFF_CODE_THRESHOLD = 512;
67
68 private final HeaderEntry[] headerFields;
69 private final HeaderEntry head = new HeaderEntry(-1, AsciiString.EMPTY_STRING,
70 AsciiString.EMPTY_STRING, Integer.MAX_VALUE, null);
71 private final HpackHuffmanEncoder hpackHuffmanEncoder = new HpackHuffmanEncoder();
72 private final byte hashMask;
73 private final boolean ignoreMaxHeaderListSize;
74 private final int huffCodeThreshold;
75 private long size;
76 private long maxHeaderTableSize;
77 private long maxHeaderListSize;
78
79
80
81
82 HpackEncoder() {
83 this(false);
84 }
85
86
87
88
89 HpackEncoder(boolean ignoreMaxHeaderListSize) {
90 this(ignoreMaxHeaderListSize, 16, HUFF_CODE_THRESHOLD);
91 }
92
93
94
95
96 HpackEncoder(boolean ignoreMaxHeaderListSize, int arraySizeHint, int huffCodeThreshold) {
97 this.ignoreMaxHeaderListSize = ignoreMaxHeaderListSize;
98 maxHeaderTableSize = DEFAULT_HEADER_TABLE_SIZE;
99 maxHeaderListSize = MAX_HEADER_LIST_SIZE;
100
101
102 headerFields = new HeaderEntry[findNextPositivePowerOfTwo(max(2, min(arraySizeHint, 128)))];
103 hashMask = (byte) (headerFields.length - 1);
104 head.before = head.after = head;
105 this.huffCodeThreshold = huffCodeThreshold;
106 }
107
108
109
110
111
112
113 public void encodeHeaders(int streamId, Buffer out, Http2Headers headers, SensitivityDetector sensitivityDetector)
114 throws Http2Exception {
115 if (ignoreMaxHeaderListSize) {
116 encodeHeadersIgnoreMaxHeaderListSize(out, headers, sensitivityDetector);
117 } else {
118 encodeHeadersEnforceMaxHeaderListSize(streamId, out, headers, sensitivityDetector);
119 }
120 }
121
122 private void encodeHeadersEnforceMaxHeaderListSize(int streamId, Buffer out, Http2Headers headers,
123 SensitivityDetector sensitivityDetector)
124 throws Http2Exception {
125 long headerSize = 0;
126
127 for (Map.Entry<CharSequence, CharSequence> header : headers) {
128 CharSequence name = header.getKey();
129 CharSequence value = header.getValue();
130
131
132 headerSize += HpackHeaderField.sizeOf(name, value);
133 if (headerSize > maxHeaderListSize) {
134 headerListSizeExceeded(streamId, maxHeaderListSize, false);
135 }
136 }
137 encodeHeadersIgnoreMaxHeaderListSize(out, headers, sensitivityDetector);
138 }
139
140 private void encodeHeadersIgnoreMaxHeaderListSize(Buffer out, Http2Headers headers,
141 SensitivityDetector sensitivityDetector) throws Http2Exception {
142 for (Map.Entry<CharSequence, CharSequence> header : headers) {
143 CharSequence name = header.getKey();
144 CharSequence value = header.getValue();
145 encodeHeader(out, name, value, sensitivityDetector.isSensitive(name, value),
146 HpackHeaderField.sizeOf(name, value));
147 }
148 }
149
150
151
152
153
154
155 private void encodeHeader(Buffer out, CharSequence name, CharSequence value, boolean sensitive, long headerSize) {
156
157 if (sensitive) {
158 int nameIndex = getNameIndex(name);
159 encodeLiteral(out, name, value, IndexType.NEVER, nameIndex);
160 return;
161 }
162
163
164 if (maxHeaderTableSize == 0) {
165 int staticTableIndex = HpackStaticTable.getIndexInsensitive(name, value);
166 if (staticTableIndex == HpackStaticTable.NOT_FOUND) {
167 int nameIndex = HpackStaticTable.getIndex(name);
168 encodeLiteral(out, name, value, IndexType.NONE, nameIndex);
169 } else {
170 encodeInteger(out, 0x80, 7, staticTableIndex);
171 }
172 return;
173 }
174
175
176 if (headerSize > maxHeaderTableSize) {
177 int nameIndex = getNameIndex(name);
178 encodeLiteral(out, name, value, IndexType.NONE, nameIndex);
179 return;
180 }
181
182 HeaderEntry headerField = getEntryInsensitive(name, value);
183 if (headerField != null) {
184 int index = getIndex(headerField.index) + HpackStaticTable.length;
185
186 encodeInteger(out, 0x80, 7, index);
187 } else {
188 int staticTableIndex = HpackStaticTable.getIndexInsensitive(name, value);
189 if (staticTableIndex != HpackStaticTable.NOT_FOUND) {
190
191 encodeInteger(out, 0x80, 7, staticTableIndex);
192 } else {
193 ensureCapacity(headerSize);
194 encodeLiteral(out, name, value, IndexType.INCREMENTAL, getNameIndex(name));
195 add(name, value, headerSize);
196 }
197 }
198 }
199
200
201
202
203 public void setMaxHeaderTableSize(Buffer out, long maxHeaderTableSize) throws Http2Exception {
204 if (maxHeaderTableSize < MIN_HEADER_TABLE_SIZE || maxHeaderTableSize > MAX_HEADER_TABLE_SIZE) {
205 throw connectionError(PROTOCOL_ERROR, "Header Table Size must be >= %d and <= %d but was %d",
206 MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, maxHeaderTableSize);
207 }
208 if (this.maxHeaderTableSize == maxHeaderTableSize) {
209 return;
210 }
211 this.maxHeaderTableSize = maxHeaderTableSize;
212 ensureCapacity(0);
213
214 encodeInteger(out, 0x20, 5, maxHeaderTableSize);
215 }
216
217
218
219
220 public long getMaxHeaderTableSize() {
221 return maxHeaderTableSize;
222 }
223
224 public void setMaxHeaderListSize(long maxHeaderListSize) throws Http2Exception {
225 if (maxHeaderListSize < MIN_HEADER_LIST_SIZE || maxHeaderListSize > MAX_HEADER_LIST_SIZE) {
226 throw connectionError(PROTOCOL_ERROR, "Header List Size must be >= %d and <= %d but was %d",
227 MIN_HEADER_LIST_SIZE, MAX_HEADER_LIST_SIZE, maxHeaderListSize);
228 }
229 this.maxHeaderListSize = maxHeaderListSize;
230 }
231
232 public long getMaxHeaderListSize() {
233 return maxHeaderListSize;
234 }
235
236
237
238
239 private static void encodeInteger(Buffer out, int mask, int n, int i) {
240 encodeInteger(out, mask, n, (long) i);
241 }
242
243
244
245
246 private static void encodeInteger(Buffer out, int mask, int n, long i) {
247 assert n >= 0 && n <= 8 : "N: " + n;
248 int nbits = 0xFF >>> 8 - n;
249 if (i < nbits) {
250 out.writeByte((byte) (mask | i));
251 } else {
252 out.writeByte((byte) (mask | nbits));
253 long length = i - nbits;
254 for (; (length & ~0x7F) != 0; length >>>= 7) {
255 out.writeByte((byte) (length & 0x7F | 0x80));
256 }
257 out.writeByte((byte) length);
258 }
259 }
260
261
262
263
264 private void encodeStringLiteral(Buffer out, CharSequence string) {
265 int huffmanLength;
266 if (string.length() >= huffCodeThreshold
267 && (huffmanLength = hpackHuffmanEncoder.getEncodedLength(string)) < string.length()) {
268 encodeInteger(out, 0x80, 7, huffmanLength);
269 hpackHuffmanEncoder.encode(out, string);
270 } else {
271 encodeInteger(out, 0x00, 7, string.length());
272 if (string instanceof AsciiString) {
273
274 AsciiString asciiString = (AsciiString) string;
275 out.writeBytes(asciiString.array(), asciiString.arrayOffset(), asciiString.length());
276 } else {
277
278
279 out.writeCharSequence(string, CharsetUtil.ISO_8859_1);
280 }
281 }
282 }
283
284
285
286
287 private void encodeLiteral(Buffer out, CharSequence name, CharSequence value, IndexType indexType,
288 int nameIndex) {
289 boolean nameIndexValid = nameIndex != NOT_FOUND;
290 switch (indexType) {
291 case INCREMENTAL:
292 encodeInteger(out, 0x40, 6, nameIndexValid ? nameIndex : 0);
293 break;
294 case NONE:
295 encodeInteger(out, 0x00, 4, nameIndexValid ? nameIndex : 0);
296 break;
297 case NEVER:
298 encodeInteger(out, 0x10, 4, nameIndexValid ? nameIndex : 0);
299 break;
300 default:
301 throw new Error("should not reach here");
302 }
303 if (!nameIndexValid) {
304 encodeStringLiteral(out, name);
305 }
306 encodeStringLiteral(out, value);
307 }
308
309 private int getNameIndex(CharSequence name) {
310 int index = HpackStaticTable.getIndex(name);
311 if (index == HpackStaticTable.NOT_FOUND) {
312 index = getIndex(name);
313 if (index >= 0) {
314 index += HpackStaticTable.length;
315 }
316 }
317 return index;
318 }
319
320
321
322
323
324 private void ensureCapacity(long headerSize) {
325 while (maxHeaderTableSize - size < headerSize) {
326 int index = length();
327 if (index == 0) {
328 break;
329 }
330 remove();
331 }
332 }
333
334
335
336
337 int length() {
338 return size == 0 ? 0 : head.after.index - head.before.index + 1;
339 }
340
341
342
343
344 long size() {
345 return size;
346 }
347
348
349
350
351 HpackHeaderField getHeaderField(int index) {
352 HeaderEntry entry = head;
353 while (index-- >= 0) {
354 entry = entry.before;
355 }
356 return entry;
357 }
358
359
360
361
362
363 private HeaderEntry getEntryInsensitive(CharSequence name, CharSequence value) {
364 if (length() == 0 || name == null || value == null) {
365 return null;
366 }
367 int h = AsciiString.hashCode(name);
368 int i = index(h);
369 for (HeaderEntry e = headerFields[i]; e != null; e = e.next) {
370
371
372 if (e.hash == h && equalsVariableTime(value, e.value) && equalsVariableTime(name, e.name)) {
373 return e;
374 }
375 }
376 return null;
377 }
378
379
380
381
382
383 private int getIndex(CharSequence name) {
384 if (length() == 0 || name == null) {
385 return NOT_FOUND;
386 }
387 int h = AsciiString.hashCode(name);
388 int i = index(h);
389 for (HeaderEntry e = headerFields[i]; e != null; e = e.next) {
390 if (e.hash == h && equalsConstantTime(name, e.name) != 0) {
391 return getIndex(e.index);
392 }
393 }
394 return NOT_FOUND;
395 }
396
397
398
399
400 private int getIndex(int index) {
401 return index == NOT_FOUND ? NOT_FOUND : index - head.before.index + 1;
402 }
403
404
405
406
407
408
409 private void add(CharSequence name, CharSequence value, long headerSize) {
410
411 if (headerSize > maxHeaderTableSize) {
412 clear();
413 return;
414 }
415
416
417 while (maxHeaderTableSize - size < headerSize) {
418 remove();
419 }
420
421 int h = AsciiString.hashCode(name);
422 int i = index(h);
423 HeaderEntry old = headerFields[i];
424 HeaderEntry e = new HeaderEntry(h, name, value, head.before.index - 1, old);
425 headerFields[i] = e;
426 e.addBefore(head);
427 size += headerSize;
428 }
429
430
431
432
433 private HpackHeaderField remove() {
434 if (size == 0) {
435 return null;
436 }
437 HeaderEntry eldest = head.after;
438 int h = eldest.hash;
439 int i = index(h);
440 HeaderEntry prev = headerFields[i];
441 HeaderEntry e = prev;
442 while (e != null) {
443 HeaderEntry next = e.next;
444 if (e == eldest) {
445 if (prev == eldest) {
446 headerFields[i] = next;
447 } else {
448 prev.next = next;
449 }
450 eldest.remove();
451 size -= eldest.size();
452 return eldest;
453 }
454 prev = e;
455 e = next;
456 }
457 return null;
458 }
459
460
461
462
463 private void clear() {
464 Arrays.fill(headerFields, null);
465 head.before = head.after = head;
466 size = 0;
467 }
468
469
470
471
472 private int index(int h) {
473 return h & hashMask;
474 }
475
476
477
478
479 private static final class HeaderEntry extends HpackHeaderField {
480
481 HeaderEntry before, after;
482
483
484 HeaderEntry next;
485 int hash;
486
487
488 int index;
489
490
491
492
493 HeaderEntry(int hash, CharSequence name, CharSequence value, int index, HeaderEntry next) {
494 super(name, value);
495 this.index = index;
496 this.hash = hash;
497 this.next = next;
498 }
499
500
501
502
503 private void remove() {
504 before.after = after;
505 after.before = before;
506 before = null;
507 after = null;
508 next = null;
509 }
510
511
512
513
514 private void addBefore(HeaderEntry existingEntry) {
515 after = existingEntry;
516 before = existingEntry.before;
517 before.after = this;
518 after.before = this;
519 }
520 }
521 }