[#5762] HTTP/2: SETTINGS_HEADER_TABLE_SIZE should be an unsigned int

Motivation:

he HTTP/2 spec demands that the max value for SETTINGS_HEADER_TABLE_SIZE should be an unsigned 32-bit integer.

Modifications:

Change the limit to unsigned 32-bit integer and add tests.

Result:

Complient to rfc.
This commit is contained in:
Norman Maurer 2016-09-05 14:58:08 +02:00
parent 67d3a78123
commit d2389a9339
9 changed files with 67 additions and 39 deletions

View File

@ -23,6 +23,8 @@ import java.io.IOException;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
@ -99,9 +101,10 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
*/
private final class Http2HeaderTableDecoder extends DefaultHttp2HeaderTableListSize implements Http2HeaderTable {
@Override
public void maxHeaderTableSize(int max) throws Http2Exception {
if (max < 0) {
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be non-negative but was %d", max);
public void maxHeaderTableSize(long max) throws Http2Exception {
if (max < MIN_HEADER_TABLE_SIZE || max > MAX_HEADER_TABLE_SIZE) {
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be >= %d and <= %d but was %d",
MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, max);
}
try {
decoder.setMaxHeaderTableSize(max);
@ -111,7 +114,7 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
}
@Override
public int maxHeaderTableSize() {
public long maxHeaderTableSize() {
return decoder.getMaxHeaderTableSize();
}
}

View File

@ -23,6 +23,8 @@ import io.netty.util.internal.UnstableApi;
import java.util.Map.Entry;
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
@ -89,9 +91,10 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
*/
private final class Http2HeaderTableEncoder extends DefaultHttp2HeaderTableListSize implements Http2HeaderTable {
@Override
public void maxHeaderTableSize(int max) throws Http2Exception {
if (max < 0) {
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be non-negative but was %d", max);
public void maxHeaderTableSize(long max) throws Http2Exception {
if (max < MIN_HEADER_TABLE_SIZE || max > MAX_HEADER_TABLE_SIZE) {
throw connectionError(PROTOCOL_ERROR, "Header Table Size must be >= %d and <= %d but was %d",
MIN_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE, max);
}
try {
// No headers should be emitted. If they are, we throw.
@ -102,7 +105,7 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
}
@Override
public int maxHeaderTableSize() {
public long maxHeaderTableSize() {
return encoder.getMaxHeaderTableSize();
}
}

View File

@ -87,7 +87,7 @@ public final class Http2CodecUtil {
public static final char SETTINGS_MAX_HEADER_LIST_SIZE = 6;
public static final int NUM_STANDARD_SETTINGS = 6;
public static final int MAX_HEADER_TABLE_SIZE = Integer.MAX_VALUE; // Size limited by HPACK library
public static final long MAX_HEADER_TABLE_SIZE = MAX_UNSIGNED_INT;
public static final long MAX_CONCURRENT_STREAMS = MAX_UNSIGNED_INT;
public static final int MAX_INITIAL_WINDOW_SIZE = Integer.MAX_VALUE;
public static final int MAX_FRAME_SIZE_LOWER_BOUND = 0x4000;

View File

@ -24,12 +24,12 @@ public interface Http2HeaderTable {
/**
* Sets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/
void maxHeaderTableSize(int max) throws Http2Exception;
void maxHeaderTableSize(long max) throws Http2Exception;
/**
* Gets the maximum size of the HPACK header table used for decoding HTTP/2 headers.
*/
int maxHeaderTableSize();
long maxHeaderTableSize();
/**
* Sets the maximum allowed header elements.

View File

@ -87,7 +87,7 @@ public final class Http2Settings extends CharObjectHashMap<Long> {
*
* @throws IllegalArgumentException if verification of the setting fails.
*/
public Http2Settings headerTableSize(int value) {
public Http2Settings headerTableSize(long value) {
put(SETTINGS_HEADER_TABLE_SIZE, Long.valueOf(value));
return this;
}

View File

@ -74,7 +74,7 @@ public final class Decoder {
private final DynamicTable dynamicTable;
private final HuffmanDecoder huffmanDecoder;
private final int maxHeadersLength;
private int maxDynamicTableSize;
private long maxDynamicTableSize;
private int encoderMaxDynamicTableSize;
private boolean maxDynamicTableSizeChangeRequired;
@ -305,7 +305,7 @@ public final class Decoder {
* Set the maximum table size. If this is below the maximum size of the dynamic table used by
* the encoder, the beginning of the next header block MUST signal this change.
*/
public void setMaxHeaderTableSize(int maxHeaderTableSize) {
public void setMaxHeaderTableSize(long maxHeaderTableSize) {
maxDynamicTableSize = maxHeaderTableSize;
if (maxDynamicTableSize < encoderMaxDynamicTableSize) {
// decoder requires less space than encoder
@ -319,7 +319,7 @@ public final class Decoder {
* Return the maximum table size. This is the maximum size allowed by both the encoder and the
* decoder.
*/
public int getMaxHeaderTableSize() {
public long getMaxHeaderTableSize() {
return dynamicTable.capacity();
}
@ -333,7 +333,7 @@ public final class Decoder {
/**
* Return the size of the dynamic table. Exposed for testing.
*/
int size() {
long size() {
return dynamicTable.size();
}

View File

@ -31,6 +31,8 @@
*/
package io.netty.handler.codec.http2.internal.hpack;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.internal.hpack.HeaderField.HEADER_ENTRY_OVERHEAD;
final class DynamicTable {
@ -40,7 +42,7 @@ final class DynamicTable {
int head;
int tail;
private int size;
private int capacity = -1; // ensure setCapacity creates the array
private long capacity = -1; // ensure setCapacity creates the array
/**
* Creates a new dynamic table with the specified initial capacity.
@ -65,14 +67,14 @@ final class DynamicTable {
/**
* Return the current size of the dynamic table. This is the sum of the size of the entries.
*/
public int size() {
public long size() {
return size;
}
/**
* Return the maximum allowable size of the dynamic table.
*/
public int capacity() {
public long capacity() {
return capacity;
}
@ -149,11 +151,10 @@ final class DynamicTable {
* Set the maximum size of the dynamic table. Entries are evicted from the dynamic table until
* the size of the table is less than or equal to the maximum size.
*/
public void setCapacity(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + capacity);
public void setCapacity(long capacity) {
if (capacity < MIN_HEADER_TABLE_SIZE || capacity > MAX_HEADER_TABLE_SIZE) {
throw new IllegalArgumentException("capacity is invalid: " + capacity);
}
// initially capacity will be -1 so init won't return here
if (this.capacity == capacity) {
return;
@ -169,7 +170,7 @@ final class DynamicTable {
}
}
int maxEntries = capacity / HEADER_ENTRY_OVERHEAD;
int maxEntries = (int) (capacity / HEADER_ENTRY_OVERHEAD);
if (capacity % HEADER_ENTRY_OVERHEAD != 0) {
maxEntries++;
}
@ -192,8 +193,8 @@ final class DynamicTable {
}
}
this.tail = 0;
this.head = tail + len;
this.headerFields = tmp;
tail = 0;
head = tail + len;
headerFields = tmp;
}
}

View File

@ -37,6 +37,8 @@ import io.netty.util.CharsetUtil;
import java.util.Arrays;
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.Http2CodecUtil.MIN_HEADER_TABLE_SIZE;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.INCREMENTAL;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NEVER;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NONE;
@ -52,22 +54,22 @@ public final class Encoder {
AsciiString.EMPTY_STRING, Integer.MAX_VALUE, null);
private final HuffmanEncoder huffmanEncoder = new HuffmanEncoder();
private final byte hashMask;
private int size;
private int capacity;
private long size;
private long capacity;
/**
* Creates a new encoder.
*/
public Encoder(int maxHeaderTableSize) {
public Encoder(long maxHeaderTableSize) {
this(maxHeaderTableSize, 16);
}
/**
* Creates a new encoder.
*/
public Encoder(int maxHeaderTableSize, int arraySizeHint) {
if (maxHeaderTableSize < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + maxHeaderTableSize);
public Encoder(long maxHeaderTableSize, int arraySizeHint) {
if (maxHeaderTableSize < MIN_HEADER_TABLE_SIZE || maxHeaderTableSize > MAX_HEADER_TABLE_SIZE) {
throw new IllegalArgumentException("maxHeaderTableSize is invalid: " + maxHeaderTableSize);
}
capacity = maxHeaderTableSize;
// Enforce a bound of [2, 128] because hashMask is a byte. The max possible value of hashMask is one less
@ -133,22 +135,23 @@ public final class Encoder {
/**
* Set the maximum table size.
*/
public void setMaxHeaderTableSize(ByteBuf out, int maxHeaderTableSize) {
if (maxHeaderTableSize < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + maxHeaderTableSize);
public void setMaxHeaderTableSize(ByteBuf out, long maxHeaderTableSize) {
if (maxHeaderTableSize < MIN_HEADER_TABLE_SIZE || maxHeaderTableSize > MAX_HEADER_TABLE_SIZE) {
throw new IllegalArgumentException("maxHeaderTableSize is invalid: " + maxHeaderTableSize);
}
if (capacity == maxHeaderTableSize) {
return;
}
capacity = maxHeaderTableSize;
ensureCapacity(0);
encodeInteger(out, 0x20, 5, maxHeaderTableSize);
// Casting to integer is safe as we verified the maxHeaderTableSize is a valid unsigned int.
encodeInteger(out, 0x20, 5, (int) maxHeaderTableSize);
}
/**
* Return the maximum table size.
*/
public int getMaxHeaderTableSize() {
public long getMaxHeaderTableSize() {
return capacity;
}
@ -252,7 +255,7 @@ public final class Encoder {
/**
* Return the size of the dynamic table. Exposed for testing.
*/
int size() {
long size() {
return size;
}

View File

@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import io.netty.handler.codec.http.HttpUtil;
import org.junit.Before;
import org.junit.Test;
@ -86,4 +87,21 @@ public class Http2SettingsTest {
settings.maxHeaderListSize((int) settingsValueUpperBound);
assertEquals(Integer.MAX_VALUE, (long) settings.maxHeaderListSize());
}
@Test
public void headerTableSizeUnsignedInt() {
final long value = 1L << 31;
settings.put(Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE, (Long) value);
assertEquals(value, (long) settings.get(Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE));
}
@Test(expected = IllegalArgumentException.class)
public void headerTableSizeBoundCheck() {
settings.put(Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE, (Long) Long.MAX_VALUE);
}
@Test(expected = IllegalArgumentException.class)
public void headerTableSizeBoundCheck2() {
settings.put(Http2CodecUtil.SETTINGS_HEADER_TABLE_SIZE, Long.valueOf(-1L));
}
}