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