View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  /*
18   * Copyright 2014 Twitter, Inc.
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *     http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
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.util.AsciiString;
37  
38  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
39  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_LIST_SIZE;
40  import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
41  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_LIST_SIZE;
42  import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
43  import static io.netty.handler.codec.http2.Http2CodecUtil.headerListSizeExceeded;
44  import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
45  import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
46  import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
47  import static io.netty.handler.codec.http2.Http2Exception.connectionError;
48  import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.getPseudoHeader;
49  import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
50  import static io.netty.util.AsciiString.EMPTY_STRING;
51  import static io.netty.util.internal.ObjectUtil.checkPositive;
52  import static io.netty.util.internal.ThrowableUtil.unknownStackTrace;
53  
54  final class HpackDecoder {
55      private static final Http2Exception DECODE_ULE_128_DECOMPRESSION_EXCEPTION = unknownStackTrace(
56              connectionError(COMPRESSION_ERROR, "HPACK - decompression failure"), HpackDecoder.class,
57              "decodeULE128(..)");
58      private static final Http2Exception DECODE_ULE_128_TO_LONG_DECOMPRESSION_EXCEPTION = unknownStackTrace(
59              connectionError(COMPRESSION_ERROR, "HPACK - long overflow"), HpackDecoder.class, "decodeULE128(..)");
60      private static final Http2Exception DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION = unknownStackTrace(
61              connectionError(COMPRESSION_ERROR, "HPACK - int overflow"), HpackDecoder.class, "decodeULE128ToInt(..)");
62      private static final Http2Exception DECODE_ILLEGAL_INDEX_VALUE = unknownStackTrace(
63              connectionError(COMPRESSION_ERROR, "HPACK - illegal index value"), HpackDecoder.class, "decode(..)");
64      private static final Http2Exception INDEX_HEADER_ILLEGAL_INDEX_VALUE = unknownStackTrace(
65              connectionError(COMPRESSION_ERROR, "HPACK - illegal index value"), HpackDecoder.class, "indexHeader(..)");
66      private static final Http2Exception READ_NAME_ILLEGAL_INDEX_VALUE = unknownStackTrace(
67              connectionError(COMPRESSION_ERROR, "HPACK - illegal index value"), HpackDecoder.class, "readName(..)");
68      private static final Http2Exception INVALID_MAX_DYNAMIC_TABLE_SIZE = unknownStackTrace(
69              connectionError(COMPRESSION_ERROR, "HPACK - invalid max dynamic table size"), HpackDecoder.class,
70              "setDynamicTableSize(..)");
71      private static final Http2Exception MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED = unknownStackTrace(
72              connectionError(COMPRESSION_ERROR, "HPACK - max dynamic table size change required"), HpackDecoder.class,
73              "decode(..)");
74      private static final byte READ_HEADER_REPRESENTATION = 0;
75      private static final byte READ_MAX_DYNAMIC_TABLE_SIZE = 1;
76      private static final byte READ_INDEXED_HEADER = 2;
77      private static final byte READ_INDEXED_HEADER_NAME = 3;
78      private static final byte READ_LITERAL_HEADER_NAME_LENGTH_PREFIX = 4;
79      private static final byte READ_LITERAL_HEADER_NAME_LENGTH = 5;
80      private static final byte READ_LITERAL_HEADER_NAME = 6;
81      private static final byte READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX = 7;
82      private static final byte READ_LITERAL_HEADER_VALUE_LENGTH = 8;
83      private static final byte READ_LITERAL_HEADER_VALUE = 9;
84  
85      private final HpackDynamicTable hpackDynamicTable;
86      private final HpackHuffmanDecoder hpackHuffmanDecoder;
87      private long maxHeaderListSizeGoAway;
88      private long maxHeaderListSize;
89      private long maxDynamicTableSize;
90      private long encoderMaxDynamicTableSize;
91      private boolean maxDynamicTableSizeChangeRequired;
92  
93      /**
94       * Create a new instance.
95       * @param maxHeaderListSize This is the only setting that can be configured before notifying the peer.
96       *  This is because <a href="https://tools.ietf.org/html/rfc7540#section-6.5.1">SETTINGS_MAX_HEADER_LIST_SIZE</a>
97       *  allows a lower than advertised limit from being enforced, and the default limit is unlimited
98       *  (which is dangerous).
99       * @param initialHuffmanDecodeCapacity Size of an intermediate buffer used during huffman decode.
100      */
101     HpackDecoder(long maxHeaderListSize, int initialHuffmanDecodeCapacity) {
102         this(maxHeaderListSize, initialHuffmanDecodeCapacity, DEFAULT_HEADER_TABLE_SIZE);
103     }
104 
105     /**
106      * Exposed Used for testing only! Default values used in the initial settings frame are overridden intentionally
107      * for testing but violate the RFC if used outside the scope of testing.
108      */
109     HpackDecoder(long maxHeaderListSize, int initialHuffmanDecodeCapacity, int maxHeaderTableSize) {
110         this.maxHeaderListSize = checkPositive(maxHeaderListSize, "maxHeaderListSize");
111         this.maxHeaderListSizeGoAway = Http2CodecUtil.calculateMaxHeaderListSizeGoAway(maxHeaderListSize);
112 
113         maxDynamicTableSize = encoderMaxDynamicTableSize = maxHeaderTableSize;
114         maxDynamicTableSizeChangeRequired = false;
115         hpackDynamicTable = new HpackDynamicTable(maxHeaderTableSize);
116         hpackHuffmanDecoder = new HpackHuffmanDecoder(initialHuffmanDecodeCapacity);
117     }
118 
119     /**
120      * Decode the header block into header fields.
121      * <p>
122      * This method assumes the entire header block is contained in {@code in}.
123      */
124     public void decode(int streamId, ByteBuf in, Http2Headers headers, boolean validateHeaders) throws Http2Exception {
125         int index = 0;
126         long headersLength = 0;
127         int nameLength = 0;
128         int valueLength = 0;
129         byte state = READ_HEADER_REPRESENTATION;
130         boolean huffmanEncoded = false;
131         CharSequence name = null;
132         HeaderType headerType = null;
133         IndexType indexType = IndexType.NONE;
134         while (in.isReadable()) {
135             switch (state) {
136                 case READ_HEADER_REPRESENTATION:
137                     byte b = in.readByte();
138                     if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
139                         // HpackEncoder MUST signal maximum dynamic table size change
140                         throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
141                     }
142                     if (b < 0) {
143                         // Indexed Header Field
144                         index = b & 0x7F;
145                         switch (index) {
146                             case 0:
147                                 throw DECODE_ILLEGAL_INDEX_VALUE;
148                             case 0x7F:
149                                 state = READ_INDEXED_HEADER;
150                                 break;
151                             default:
152                                 HpackHeaderField indexedHeader = getIndexedHeader(index);
153                                 headerType = validate(indexedHeader.name, headerType, validateHeaders);
154                                 headersLength = addHeader(headers, indexedHeader.name, indexedHeader.value,
155                                         headersLength);
156                         }
157                     } else if ((b & 0x40) == 0x40) {
158                         // Literal Header Field with Incremental Indexing
159                         indexType = IndexType.INCREMENTAL;
160                         index = b & 0x3F;
161                         switch (index) {
162                             case 0:
163                                 state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
164                                 break;
165                             case 0x3F:
166                                 state = READ_INDEXED_HEADER_NAME;
167                                 break;
168                             default:
169                                 // Index was stored as the prefix
170                                 name = readName(index);
171                                 headerType = validate(name, headerType, validateHeaders);
172                                 nameLength = name.length();
173                                 state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
174                         }
175                     } else if ((b & 0x20) == 0x20) {
176                         // Dynamic Table Size Update
177                         index = b & 0x1F;
178                         if (index == 0x1F) {
179                             state = READ_MAX_DYNAMIC_TABLE_SIZE;
180                         } else {
181                             setDynamicTableSize(index);
182                             state = READ_HEADER_REPRESENTATION;
183                         }
184                     } else {
185                         // Literal Header Field without Indexing / never Indexed
186                         indexType = ((b & 0x10) == 0x10) ? IndexType.NEVER : IndexType.NONE;
187                         index = b & 0x0F;
188                         switch (index) {
189                             case 0:
190                                 state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
191                                 break;
192                             case 0x0F:
193                                 state = READ_INDEXED_HEADER_NAME;
194                                 break;
195                             default:
196                             // Index was stored as the prefix
197                             name = readName(index);
198                                 headerType = validate(name, headerType, validateHeaders);
199                             nameLength = name.length();
200                             state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
201                         }
202                     }
203                     break;
204 
205                 case READ_MAX_DYNAMIC_TABLE_SIZE:
206                     setDynamicTableSize(decodeULE128(in, (long) index));
207                     state = READ_HEADER_REPRESENTATION;
208                     break;
209 
210                 case READ_INDEXED_HEADER:
211                     HpackHeaderField indexedHeader = getIndexedHeader(decodeULE128(in, index));
212                     headerType = validate(indexedHeader.name, headerType, validateHeaders);
213                     headersLength = addHeader(headers, indexedHeader.name, indexedHeader.value, headersLength);
214                     state = READ_HEADER_REPRESENTATION;
215                     break;
216 
217                 case READ_INDEXED_HEADER_NAME:
218                     // Header Name matches an entry in the Header Table
219                     name = readName(decodeULE128(in, index));
220                     headerType = validate(name, headerType, validateHeaders);
221                     nameLength = name.length();
222                     state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
223                     break;
224 
225                 case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:
226                     b = in.readByte();
227                     huffmanEncoded = (b & 0x80) == 0x80;
228                     index = b & 0x7F;
229                     if (index == 0x7f) {
230                         state = READ_LITERAL_HEADER_NAME_LENGTH;
231                     } else {
232                         if (index > maxHeaderListSizeGoAway - headersLength) {
233                             headerListSizeExceeded(maxHeaderListSizeGoAway);
234                         }
235                         nameLength = index;
236                         state = READ_LITERAL_HEADER_NAME;
237                     }
238                     break;
239 
240                 case READ_LITERAL_HEADER_NAME_LENGTH:
241                     // Header Name is a Literal String
242                     nameLength = decodeULE128(in, index);
243 
244                     if (nameLength > maxHeaderListSizeGoAway - headersLength) {
245                         headerListSizeExceeded(maxHeaderListSizeGoAway);
246                     }
247                     state = READ_LITERAL_HEADER_NAME;
248                     break;
249 
250                 case READ_LITERAL_HEADER_NAME:
251                     // Wait until entire name is readable
252                     if (in.readableBytes() < nameLength) {
253                         throw notEnoughDataException(in);
254                     }
255 
256                     name = readStringLiteral(in, nameLength, huffmanEncoded);
257                     headerType = validate(name, headerType, validateHeaders);
258 
259                     state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
260                     break;
261 
262                 case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
263                     b = in.readByte();
264                     huffmanEncoded = (b & 0x80) == 0x80;
265                     index = b & 0x7F;
266                     switch (index) {
267                         case 0x7f:
268                             state = READ_LITERAL_HEADER_VALUE_LENGTH;
269                             break;
270                         case 0:
271                             headerType = validate(name, headerType, validateHeaders);
272                             headersLength = insertHeader(headers, name, EMPTY_STRING, indexType, headersLength);
273                             state = READ_HEADER_REPRESENTATION;
274                             break;
275                         default:
276                             // Check new header size against max header size
277                             if ((long) index + nameLength > maxHeaderListSizeGoAway - headersLength) {
278                                 headerListSizeExceeded(maxHeaderListSizeGoAway);
279                             }
280                             valueLength = index;
281                             state = READ_LITERAL_HEADER_VALUE;
282                     }
283 
284                     break;
285 
286                 case READ_LITERAL_HEADER_VALUE_LENGTH:
287                     // Header Value is a Literal String
288                     valueLength = decodeULE128(in, index);
289 
290                     // Check new header size against max header size
291                     if ((long) valueLength + nameLength > maxHeaderListSizeGoAway - headersLength) {
292                         headerListSizeExceeded(maxHeaderListSizeGoAway);
293                     }
294                     state = READ_LITERAL_HEADER_VALUE;
295                     break;
296 
297                 case READ_LITERAL_HEADER_VALUE:
298                     // Wait until entire value is readable
299                     if (in.readableBytes() < valueLength) {
300                         throw notEnoughDataException(in);
301                     }
302 
303                     CharSequence value = readStringLiteral(in, valueLength, huffmanEncoded);
304                     headerType = validate(name, headerType, validateHeaders);
305                     headersLength = insertHeader(headers, name, value, indexType, headersLength);
306                     state = READ_HEADER_REPRESENTATION;
307                     break;
308 
309                 default:
310                     throw new Error("should not reach here state: " + state);
311             }
312         }
313 
314         // we have read all of our headers, and not exceeded maxHeaderListSizeGoAway see if we have
315         // exceeded our actual maxHeaderListSize. This must be done here to prevent dynamic table
316         // corruption
317         if (headersLength > maxHeaderListSize) {
318             headerListSizeExceeded(streamId, maxHeaderListSize, true);
319         }
320 
321         if (state != READ_HEADER_REPRESENTATION) {
322             throw connectionError(COMPRESSION_ERROR, "Incomplete header block fragment.");
323         }
324     }
325 
326     /**
327      * Set the maximum table size. If this is below the maximum size of the dynamic table used by
328      * the encoder, the beginning of the next header block MUST signal this change.
329      */
330     public void setMaxHeaderTableSize(long maxHeaderTableSize) throws Http2Exception {
331         if (maxHeaderTableSize < MIN_HEADER_TABLE_SIZE || maxHeaderTableSize > MAX_HEADER_TABLE_SIZE) {
332             throw connectionError(PROTOCOL_ERROR, "Header Table Size must be >= %d and <= %d but was %d",
333                     MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, maxHeaderTableSize);
334         }
335         maxDynamicTableSize = maxHeaderTableSize;
336         if (maxDynamicTableSize < encoderMaxDynamicTableSize) {
337             // decoder requires less space than encoder
338             // encoder MUST signal this change
339             maxDynamicTableSizeChangeRequired = true;
340             hpackDynamicTable.setCapacity(maxDynamicTableSize);
341         }
342     }
343 
344     public void setMaxHeaderListSize(long maxHeaderListSize, long maxHeaderListSizeGoAway) throws Http2Exception {
345         if (maxHeaderListSizeGoAway < maxHeaderListSize || maxHeaderListSizeGoAway < 0) {
346             throw connectionError(INTERNAL_ERROR, "Header List Size GO_AWAY %d must be positive and >= %d",
347                     maxHeaderListSizeGoAway, maxHeaderListSize);
348         }
349         if (maxHeaderListSize < MIN_HEADER_LIST_SIZE || maxHeaderListSize > MAX_HEADER_LIST_SIZE) {
350             throw connectionError(PROTOCOL_ERROR, "Header List Size must be >= %d and <= %d but was %d",
351                     MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, maxHeaderListSize);
352         }
353         this.maxHeaderListSize = maxHeaderListSize;
354         this.maxHeaderListSizeGoAway = maxHeaderListSizeGoAway;
355     }
356 
357     public long getMaxHeaderListSize() {
358         return maxHeaderListSize;
359     }
360 
361     public long getMaxHeaderListSizeGoAway() {
362         return maxHeaderListSizeGoAway;
363     }
364 
365     /**
366      * Return the maximum table size. This is the maximum size allowed by both the encoder and the
367      * decoder.
368      */
369     public long getMaxHeaderTableSize() {
370         return hpackDynamicTable.capacity();
371     }
372 
373     /**
374      * Return the number of header fields in the dynamic table. Exposed for testing.
375      */
376     int length() {
377         return hpackDynamicTable.length();
378     }
379 
380     /**
381      * Return the size of the dynamic table. Exposed for testing.
382      */
383     long size() {
384         return hpackDynamicTable.size();
385     }
386 
387     /**
388      * Return the header field at the given index. Exposed for testing.
389      */
390     HpackHeaderField getHeaderField(int index) {
391         return hpackDynamicTable.getEntry(index + 1);
392     }
393 
394     private void setDynamicTableSize(long dynamicTableSize) throws Http2Exception {
395         if (dynamicTableSize > maxDynamicTableSize) {
396             throw INVALID_MAX_DYNAMIC_TABLE_SIZE;
397         }
398         encoderMaxDynamicTableSize = dynamicTableSize;
399         maxDynamicTableSizeChangeRequired = false;
400         hpackDynamicTable.setCapacity(dynamicTableSize);
401     }
402 
403     private HeaderType validate(CharSequence name, HeaderType previousHeaderType,
404                                 final boolean validateHeaders) throws Http2Exception {
405         if (!validateHeaders) {
406             return null;
407         }
408 
409         if (hasPseudoHeaderFormat(name)) {
410             if (previousHeaderType == HeaderType.REGULAR_HEADER) {
411                 throw connectionError(PROTOCOL_ERROR, "Pseudo-header field '%s' found after regular header.", name);
412             }
413 
414             final Http2Headers.PseudoHeaderName pseudoHeader = getPseudoHeader(name);
415             if (pseudoHeader == null) {
416                 throw connectionError(PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name);
417             }
418 
419             final HeaderType currentHeaderType = pseudoHeader.isRequestOnly() ?
420                     HeaderType.REQUEST_PSEUDO_HEADER : HeaderType.RESPONSE_PSEUDO_HEADER;
421             if (previousHeaderType != null && currentHeaderType != previousHeaderType) {
422                 throw connectionError(PROTOCOL_ERROR, "Mix of request and response pseudo-headers.");
423             }
424 
425             return currentHeaderType;
426         }
427 
428         return HeaderType.REGULAR_HEADER;
429     }
430 
431     private CharSequence readName(int index) throws Http2Exception {
432         if (index <= HpackStaticTable.length) {
433             HpackHeaderField hpackHeaderField = HpackStaticTable.getEntry(index);
434             return hpackHeaderField.name;
435         }
436         if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
437             HpackHeaderField hpackHeaderField = hpackDynamicTable.getEntry(index - HpackStaticTable.length);
438             return hpackHeaderField.name;
439         }
440         throw READ_NAME_ILLEGAL_INDEX_VALUE;
441     }
442 
443     private HpackHeaderField getIndexedHeader(int index) throws Http2Exception {
444         if (index <= HpackStaticTable.length) {
445             return HpackStaticTable.getEntry(index);
446         }
447         if (index - HpackStaticTable.length <= hpackDynamicTable.length()) {
448             return hpackDynamicTable.getEntry(index - HpackStaticTable.length);
449         }
450         throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
451     }
452 
453     private long insertHeader(Http2Headers headers, CharSequence name, CharSequence value,
454                               IndexType indexType, long headerSize) throws Http2Exception {
455         headerSize = addHeader(headers, name, value, headerSize);
456 
457         switch (indexType) {
458             case NONE:
459             case NEVER:
460                 break;
461 
462             case INCREMENTAL:
463                 hpackDynamicTable.add(new HpackHeaderField(name, value));
464                 break;
465 
466             default:
467                 throw new Error("should not reach here");
468         }
469 
470         return headerSize;
471     }
472 
473     private long addHeader(Http2Headers headers, CharSequence name, CharSequence value, long headersLength)
474             throws Http2Exception {
475         headersLength += HpackHeaderField.sizeOf(name, value);
476         if (headersLength > maxHeaderListSizeGoAway) {
477             headerListSizeExceeded(maxHeaderListSizeGoAway);
478         }
479         headers.add(name, value);
480         return headersLength;
481     }
482 
483     private CharSequence readStringLiteral(ByteBuf in, int length, boolean huffmanEncoded) throws Http2Exception {
484         if (huffmanEncoded) {
485             return hpackHuffmanDecoder.decode(in, length);
486         }
487         byte[] buf = new byte[length];
488         in.readBytes(buf);
489         return new AsciiString(buf, false);
490     }
491 
492     private static IllegalArgumentException notEnoughDataException(ByteBuf in) {
493         return new IllegalArgumentException("decode only works with an entire header block! " + in);
494     }
495 
496     /**
497      * Unsigned Little Endian Base 128 Variable-Length Integer Encoding
498      * <p>
499      * Visible for testing only!
500      */
501     static int decodeULE128(ByteBuf in, int result) throws Http2Exception {
502         final int readerIndex = in.readerIndex();
503         final long v = decodeULE128(in, (long) result);
504         if (v > Integer.MAX_VALUE) {
505             // the maximum value that can be represented by a signed 32 bit number is:
506             // [0x1,0x7f] + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x6 << 28)
507             // OR
508             // 0x0 + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x7 << 28)
509             // we should reset the readerIndex if we overflowed the int type.
510             in.readerIndex(readerIndex);
511             throw DECODE_ULE_128_TO_INT_DECOMPRESSION_EXCEPTION;
512         }
513         return (int) v;
514     }
515 
516     /**
517      * Unsigned Little Endian Base 128 Variable-Length Integer Encoding
518      * <p>
519      * Visible for testing only!
520      */
521     static long decodeULE128(ByteBuf in, long result) throws Http2Exception {
522         assert result <= 0x7f && result >= 0;
523         final boolean resultStartedAtZero = result == 0;
524         final int writerIndex = in.writerIndex();
525         for (int readerIndex = in.readerIndex(), shift = 0; readerIndex < writerIndex; ++readerIndex, shift += 7) {
526             byte b = in.getByte(readerIndex);
527             if (shift == 56 && ((b & 0x80) != 0 || b == 0x7F && !resultStartedAtZero)) {
528                 // the maximum value that can be represented by a signed 64 bit number is:
529                 // [0x01L, 0x7fL] + 0x7fL + (0x7fL << 7) + (0x7fL << 14) + (0x7fL << 21) + (0x7fL << 28) + (0x7fL << 35)
530                 // + (0x7fL << 42) + (0x7fL << 49) + (0x7eL << 56)
531                 // OR
532                 // 0x0L + 0x7fL + (0x7fL << 7) + (0x7fL << 14) + (0x7fL << 21) + (0x7fL << 28) + (0x7fL << 35) +
533                 // (0x7fL << 42) + (0x7fL << 49) + (0x7fL << 56)
534                 // this means any more shifts will result in overflow so we should break out and throw an error.
535                 throw DECODE_ULE_128_TO_LONG_DECOMPRESSION_EXCEPTION;
536             }
537 
538             if ((b & 0x80) == 0) {
539                 in.readerIndex(readerIndex + 1);
540                 return result + ((b & 0x7FL) << shift);
541             }
542             result += (b & 0x7FL) << shift;
543         }
544 
545         throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
546     }
547 
548     /**
549      * HTTP/2 header types.
550      */
551     private enum HeaderType {
552         REGULAR_HEADER,
553         REQUEST_PSEUDO_HEADER,
554         RESPONSE_PSEUDO_HEADER
555     }
556 }