HTTP/2 HPACK Decoder VarInt Improvement
Motivation: HTTP/2 Decoder#decodeULE128 current will tolerate more bytes than necessary when attempted to detect overflow. The usage of this method also currently requires an additional overflow conditional. Modifications: - Integrate the first byte into Decoder#decodeULE128 which allows us to detect overflow reliably and avoid overflow checks outside of this method. Result: Less conditionals and earlier overflow detection in Decoder#decodeULE128
This commit is contained in:
parent
9b6cbf8ab1
commit
4145fae919
@ -39,6 +39,7 @@ import io.netty.util.AsciiString;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
|
||||||
import static io.netty.handler.codec.http2.Http2Error.ENHANCE_YOUR_CALM;
|
import static io.netty.handler.codec.http2.Http2Error.ENHANCE_YOUR_CALM;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Error.valueOf;
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||||
import static io.netty.util.AsciiString.EMPTY_STRING;
|
import static io.netty.util.AsciiString.EMPTY_STRING;
|
||||||
import static io.netty.util.internal.ThrowableUtil.unknownStackTrace;
|
import static io.netty.util.internal.ThrowableUtil.unknownStackTrace;
|
||||||
@ -173,36 +174,18 @@ public final class Decoder {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case READ_MAX_DYNAMIC_TABLE_SIZE:
|
case READ_MAX_DYNAMIC_TABLE_SIZE:
|
||||||
int maxSize = decodeULE128(in) + index;
|
setDynamicTableSize(decodeULE128(in, index));
|
||||||
|
|
||||||
if (maxSize < 0) { // Check for numerical overflow
|
|
||||||
throw DECODE_DECOMPRESSION_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDynamicTableSize(maxSize);
|
|
||||||
state = READ_HEADER_REPRESENTATION;
|
state = READ_HEADER_REPRESENTATION;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case READ_INDEXED_HEADER:
|
case READ_INDEXED_HEADER:
|
||||||
int headerIndex = decodeULE128(in) + index;
|
headersLength = indexHeader(decodeULE128(in, index), headers, headersLength);
|
||||||
|
|
||||||
if (headerIndex < 0) { // Check for numerical overflow
|
|
||||||
throw DECODE_DECOMPRESSION_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
headersLength = indexHeader(headerIndex, headers, headersLength);
|
|
||||||
state = READ_HEADER_REPRESENTATION;
|
state = READ_HEADER_REPRESENTATION;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case READ_INDEXED_HEADER_NAME:
|
case READ_INDEXED_HEADER_NAME:
|
||||||
// Header Name matches an entry in the Header Table
|
// Header Name matches an entry in the Header Table
|
||||||
int nameIndex = decodeULE128(in) + index;
|
name = readName(decodeULE128(in, index));
|
||||||
|
|
||||||
if (nameIndex < 0) { // Check for numerical overflow
|
|
||||||
throw DECODE_DECOMPRESSION_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
name = readName(nameIndex);
|
|
||||||
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
|
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -223,11 +206,7 @@ public final class Decoder {
|
|||||||
|
|
||||||
case READ_LITERAL_HEADER_NAME_LENGTH:
|
case READ_LITERAL_HEADER_NAME_LENGTH:
|
||||||
// Header Name is a Literal String
|
// Header Name is a Literal String
|
||||||
nameLength = decodeULE128(in) + index;
|
nameLength = decodeULE128(in, index);
|
||||||
|
|
||||||
if (nameLength < 0) { // Check for numerical overflow
|
|
||||||
throw DECODE_DECOMPRESSION_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameLength > maxHeadersLength - headersLength) {
|
if (nameLength > maxHeadersLength - headersLength) {
|
||||||
maxHeaderSizeExceeded();
|
maxHeaderSizeExceeded();
|
||||||
@ -271,11 +250,7 @@ public final class Decoder {
|
|||||||
|
|
||||||
case READ_LITERAL_HEADER_VALUE_LENGTH:
|
case READ_LITERAL_HEADER_VALUE_LENGTH:
|
||||||
// Header Value is a Literal String
|
// Header Value is a Literal String
|
||||||
valueLength = decodeULE128(in) + index;
|
valueLength = decodeULE128(in, index);
|
||||||
|
|
||||||
if (valueLength < 0) { // Check for numerical overflow
|
|
||||||
throw DECODE_DECOMPRESSION_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check new header size against max header size
|
// Check new header size against max header size
|
||||||
if ((long) valueLength + nameLength > maxHeadersLength - headersLength) {
|
if ((long) valueLength + nameLength > maxHeadersLength - headersLength) {
|
||||||
@ -429,21 +404,25 @@ public final class Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unsigned Little Endian Base 128 Variable-Length Integer Encoding
|
// Unsigned Little Endian Base 128 Variable-Length Integer Encoding
|
||||||
private static int decodeULE128(ByteBuf in) throws Http2Exception {
|
private static int decodeULE128(ByteBuf in, int result) throws Http2Exception {
|
||||||
|
assert result <= 0x7f && result >= 0;
|
||||||
final int writerIndex = in.writerIndex();
|
final int writerIndex = in.writerIndex();
|
||||||
for (int readerIndex = in.readerIndex(), shift = 0, result = 0;
|
for (int readerIndex = in.readerIndex(), shift = 0;
|
||||||
readerIndex < writerIndex; ++readerIndex, shift += 7) {
|
readerIndex < writerIndex; ++readerIndex, shift += 7) {
|
||||||
byte b = in.getByte(readerIndex);
|
byte b = in.getByte(readerIndex);
|
||||||
if (shift == 28 && (b & 0xF8) != 0) {
|
if (shift == 28 && ((b & 0x80) != 0 || b > 6)) {
|
||||||
|
// the maximum value that can be represented by a signed 32 bit number is:
|
||||||
|
// 0x7f + 0x7f + (0x7f << 7) + (0x7f << 14) + (0x7f << 21) + (0x6 << 28)
|
||||||
|
// this means any more shifts will result in overflow so we should break out and throw an error.
|
||||||
in.readerIndex(readerIndex + 1);
|
in.readerIndex(readerIndex + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((b & 0x80) == 0) {
|
if ((b & 0x80) == 0) {
|
||||||
in.readerIndex(readerIndex + 1);
|
in.readerIndex(readerIndex + 1);
|
||||||
return result | ((b & 0x7F) << shift);
|
return result + ((b & 0x7F) << shift);
|
||||||
}
|
}
|
||||||
result |= (b & 0x7F) << shift;
|
result += (b & 0x7F) << shift;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
|
throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
|
||||||
|
@ -40,6 +40,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import static io.netty.util.AsciiString.EMPTY_STRING;
|
import static io.netty.util.AsciiString.EMPTY_STRING;
|
||||||
import static io.netty.util.AsciiString.of;
|
import static io.netty.util.AsciiString.of;
|
||||||
|
import static java.lang.Integer.MAX_VALUE;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
@ -148,7 +149,7 @@ public class DecoderTest {
|
|||||||
@Test(expected = Http2Exception.class)
|
@Test(expected = Http2Exception.class)
|
||||||
public void testInsidiousIndex() throws Http2Exception {
|
public void testInsidiousIndex() throws Http2Exception {
|
||||||
// Insidious index so the last shift causes sign overflow
|
// Insidious index so the last shift causes sign overflow
|
||||||
decode("FF8080808008");
|
decode("FF8080808007");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -174,10 +175,20 @@ public class DecoderTest {
|
|||||||
|
|
||||||
@Test(expected = Http2Exception.class)
|
@Test(expected = Http2Exception.class)
|
||||||
public void testInsidiousMaxDynamicTableSize() throws Http2Exception {
|
public void testInsidiousMaxDynamicTableSize() throws Http2Exception {
|
||||||
|
decoder.setMaxHeaderTableSize(MAX_VALUE);
|
||||||
// max header table size sign overflow
|
// max header table size sign overflow
|
||||||
decode("3FE1FFFFFF07");
|
decode("3FE1FFFFFF07");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaxValidDynamicTableSize() throws Http2Exception {
|
||||||
|
decoder.setMaxHeaderTableSize(MAX_VALUE);
|
||||||
|
String baseValue = "3FE1FFFFFF0";
|
||||||
|
for (int i = 0; i < 7; ++i) {
|
||||||
|
decode(baseValue + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReduceMaxDynamicTableSize() throws Http2Exception {
|
public void testReduceMaxDynamicTableSize() throws Http2Exception {
|
||||||
decoder.setMaxHeaderTableSize(0);
|
decoder.setMaxHeaderTableSize(0);
|
||||||
|
Loading…
Reference in New Issue
Block a user