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