HpackDecoder { public void decode(int streamId, ByteBuf in, Http2Headers headers) throws Http2Exception { int index = 0; long headersLength = 0; int nameLength = 0; int valueLength = 0; byte state = READ_HEADER_REPRESENTATION; boolean huffmanEncoded = false; CharSequence name = null; IndexType indexType = IndexType.NONE; while (in.isReadable()) { switch (state) { case READ_HEADER_REPRESENTATION: byte b = in.readByte(); if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) { throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED; } if (b < 0) { index = b & 0x7F; switch (index) { case 0: throw DECODE_ILLEGAL_INDEX_VALUE; case 0x7F: state = READ_INDEXED_HEADER; break; default: headersLength = indexHeader(streamId, index, headers, headersLength); } } else if ((b & 0x40) == 0x40) { indexType = IndexType.INCREMENTAL; index = b & 0x3F; switch (index) { case 0: state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX; break; case 0x3F: state = READ_INDEXED_HEADER_NAME; break; default: name = readName(index); state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } } else if ((b & 0x20) == 0x20) { index = b & 0x1F; if (index == 0x1F) { state = READ_MAX_DYNAMIC_TABLE_SIZE; } else { setDynamicTableSize(index); state = READ_HEADER_REPRESENTATION; } } else { indexType = ((b & 0x10) == 0x10) ? IndexType.NEVER : IndexType.NONE; index = b & 0x0F; switch (index) { case 0: state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX; break; case 0x0F: state = READ_INDEXED_HEADER_NAME; break; default: name = readName(index); state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } } break; case READ_MAX_DYNAMIC_TABLE_SIZE: setDynamicTableSize(decodeULE128(in, (long) index)); state = READ_HEADER_REPRESENTATION; break; case READ_INDEXED_HEADER: headersLength = indexHeader(streamId, decodeULE128(in, index), headers, headersLength); state = READ_HEADER_REPRESENTATION; break; case READ_INDEXED_HEADER_NAME: name = readName(decodeULE128(in, index)); state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; break; case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX: b = in.readByte(); huffmanEncoded = (b & 0x80) == 0x80; index = b & 0x7F; if (index == 0x7f) { state = READ_LITERAL_HEADER_NAME_LENGTH; } else { if (index > maxHeaderListSizeGoAway - headersLength) { headerListSizeExceeded(maxHeaderListSizeGoAway); } nameLength = index; state = READ_LITERAL_HEADER_NAME; } break; case READ_LITERAL_HEADER_NAME_LENGTH: nameLength = decodeULE128(in, index); if (nameLength > maxHeaderListSizeGoAway - headersLength) { headerListSizeExceeded(maxHeaderListSizeGoAway); } state = READ_LITERAL_HEADER_NAME; break; case READ_LITERAL_HEADER_NAME: if (in.readableBytes() < nameLength) { throw notEnoughDataException(in); } name = readStringLiteral(in, nameLength, huffmanEncoded); state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; break; case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX: b = in.readByte(); huffmanEncoded = (b & 0x80) == 0x80; index = b & 0x7F; switch (index) { case 0x7f: state = READ_LITERAL_HEADER_VALUE_LENGTH; break; case 0: headersLength = insertHeader(streamId, headers, name, EMPTY_STRING, indexType, headersLength); state = READ_HEADER_REPRESENTATION; break; default: if ((long) index + nameLength > maxHeaderListSizeGoAway - headersLength) { headerListSizeExceeded(maxHeaderListSizeGoAway); } valueLength = index; state = READ_LITERAL_HEADER_VALUE; } break; case READ_LITERAL_HEADER_VALUE_LENGTH: valueLength = decodeULE128(in, index); if ((long) valueLength + nameLength > maxHeaderListSizeGoAway - headersLength) { headerListSizeExceeded(maxHeaderListSizeGoAway); } state = READ_LITERAL_HEADER_VALUE; break; case READ_LITERAL_HEADER_VALUE: if (in.readableBytes() < valueLength) { throw notEnoughDataException(in); } CharSequence value = readStringLiteral(in, valueLength, huffmanEncoded); headersLength = insertHeader(streamId, headers, name, value, indexType, headersLength); state = READ_HEADER_REPRESENTATION; break; default: throw new Error("should not reach here state: " + state); } } if (headersLength > maxHeaderListSize) { headerListSizeExceeded(streamId, maxHeaderListSize, true); } } HpackDecoder(long maxHeaderListSize, int initialHuffmanDecodeCapacity);   HpackDecoder(long maxHeaderListSize, int initialHuffmanDecodeCapacity, int maxHeaderTableSize); void decode(int streamId, ByteBuf in, Http2Headers headers); void setMaxHeaderTableSize(long maxHeaderTableSize); void setMaxHeaderListSize(long maxHeaderListSize, long maxHeaderListSizeGoAway); long getMaxHeaderListSize(); long getMaxHeaderListSizeGoAway(); long getMaxHeaderTableSize();  }
@Test public void testLiteralHuffmanEncodedWithEmptyNameAndValue() throws Http2Exception { byte[] input = {0, (byte) 0x80, 0}; ByteBuf in = Unpooled.wrappedBuffer(input); try { hpackDecoder.decode(0, in, mockHeaders); verify(mockHeaders, times(1)).add(EMPTY_STRING, EMPTY_STRING); } finally { in.release(); } }
@Test(expected = Http2Exception.class) public void testLiteralHuffmanEncodedWithPaddingGreaterThan7Throws() throws Http2Exception { byte[] input = {0, (byte) 0x81, -1}; ByteBuf in = Unpooled.wrappedBuffer(input); try { hpackDecoder.decode(0, in, mockHeaders); } finally { in.release(); } }
@Test(expected = Http2Exception.class) public void testLiteralHuffmanEncodedWithDecodingEOSThrows() throws Http2Exception { byte[] input = {0, (byte) 0x84, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; ByteBuf in = Unpooled.wrappedBuffer(input); try { hpackDecoder.decode(0, in, mockHeaders); } finally { in.release(); } }
@Test(expected = Http2Exception.class) public void testLiteralHuffmanEncodedWithPaddingNotCorrespondingToMSBThrows() throws Http2Exception { byte[] input = {0, (byte) 0x81, 0}; ByteBuf in = Unpooled.wrappedBuffer(input); try { hpackDecoder.decode(0, in, mockHeaders); } finally { in.release(); } }
@Test(expected = Http2Exception.class) public void testIncompleteIndex() throws Http2Exception { byte[] compressed = StringUtil.decodeHexDump("FFF0"); ByteBuf in = Unpooled.wrappedBuffer(compressed); try { hpackDecoder.decode(0, in, mockHeaders); assertEquals(1, in.readableBytes()); hpackDecoder.decode(0, in, mockHeaders); } finally { in.release(); } }
@Test(expected = Http2Exception.class) public void testUnusedIndex() throws Http2Exception { decode("80"); }
@Test(expected = Http2Exception.class) public void testIllegalIndex() throws Http2Exception { decode("FF00"); }
@Test(expected = Http2Exception.class) public void testInsidiousIndex() throws Http2Exception { decode("FF8080808007"); }
@Test(expected = Http2Exception.class) public void testIllegalDynamicTableSizeUpdate() throws Http2Exception { decode("3FE21F"); }
@Test public void testLiteralWithIncrementalIndexingWithEmptyName() throws Http2Exception { decode("400005" + hex("value")); verify(mockHeaders, times(1)).add(EMPTY_STRING, of("value")); }
@Test public void testLiteralWithIncrementalIndexingCompleteEviction() throws Http2Exception { decode("4004" + hex("name") + "05" + hex("value")); verify(mockHeaders).add(of("name"), of("value")); verifyNoMoreInteractions(mockHeaders); reset(mockHeaders); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 4096; i++) { sb.append('a'); } String value = sb.toString(); sb = new StringBuilder(); sb.append("417F811F"); for (int i = 0; i < 4096; i++) { sb.append("61"); } decode(sb.toString()); verify(mockHeaders).add(of(":authority"), of(value)); verifyNoMoreInteractions(mockHeaders); reset(mockHeaders); decode("4004" + hex("name") + "05" + hex("value") + "BE"); verify(mockHeaders, times(2)).add(of("name"), of("value")); verifyNoMoreInteractions(mockHeaders); }
@Test(expected = Http2Exception.class) public void testLiteralWithIncrementalIndexingWithLargeValue() throws Http2Exception { StringBuilder sb = new StringBuilder(); sb.append("4004"); sb.append(hex("name")); sb.append("7F813F"); for (int i = 0; i < 8192; i++) { sb.append("61"); } decode(sb.toString()); }
@Test public void testLiteralWithoutIndexingWithEmptyName() throws Http2Exception { decode("000005" + hex("value")); verify(mockHeaders, times(1)).add(EMPTY_STRING, of("value")); }
@Test(expected = Http2Exception.class) public void testLiteralWithoutIndexingWithLargeName() throws Http2Exception { StringBuilder sb = new StringBuilder(); sb.append("007F817F"); for (int i = 0; i < 16384; i++) { sb.append("61"); } sb.append("00"); decode(sb.toString()); }
@Test(expected = Http2Exception.class) public void testLiteralWithoutIndexingWithLargeValue() throws Http2Exception { StringBuilder sb = new StringBuilder(); sb.append("0004"); sb.append(hex("name")); sb.append("7F813F"); for (int i = 0; i < 8192; i++) { sb.append("61"); } decode(sb.toString()); }
@Test public void testLiteralNeverIndexedWithEmptyName() throws Http2Exception { decode("100005" + hex("value")); verify(mockHeaders, times(1)).add(EMPTY_STRING, of("value")); }
@Test(expected = Http2Exception.class) public void testLiteralNeverIndexedWithLargeName() throws Http2Exception { StringBuilder sb = new StringBuilder(); sb.append("107F817F"); for (int i = 0; i < 16384; i++) { sb.append("61"); } sb.append("00"); decode(sb.toString()); }
@Test(expected = Http2Exception.class) public void testLiteralNeverIndexedWithLargeValue() throws Http2Exception { StringBuilder sb = new StringBuilder(); sb.append("1004"); sb.append(hex("name")); sb.append("7F813F"); for (int i = 0; i < 8192; i++) { sb.append("61"); } decode(sb.toString()); }