Optimize HPACK usage to align more with Netty types and remove heavy object creations. Related to [#3597]
Motivations: The HPACK code was not really optimized and written with Netty types in mind. Because of this a lot of garbage was created due heavy object creation. This was first reported in [#3597] and https://github.com/grpc/grpc-java/issues/1872 . Modifications: - Directly use ByteBuf as input and output - Make use of ByteProcessor where possible - Use AsciiString as this is the only thing we need for our http2 usage Result: Less garbage and better usage of Netty apis.
This commit is contained in:
parent
a725b97092
commit
b4d4c0034d
|
@ -18,11 +18,16 @@ package io.netty.handler.codec.http2;
|
||||||
import io.netty.handler.codec.DefaultHeaders;
|
import io.netty.handler.codec.DefaultHeaders;
|
||||||
import io.netty.handler.codec.UnsupportedValueConverter;
|
import io.netty.handler.codec.UnsupportedValueConverter;
|
||||||
import io.netty.handler.codec.ValueConverter;
|
import io.netty.handler.codec.ValueConverter;
|
||||||
|
import io.netty.util.internal.UnstableApi;
|
||||||
|
|
||||||
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
||||||
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
|
import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER;
|
||||||
|
|
||||||
final class CharSequenceMap<V> extends DefaultHeaders<CharSequence, V, CharSequenceMap<V>> {
|
/**
|
||||||
|
* Internal use only!
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public final class CharSequenceMap<V> extends DefaultHeaders<CharSequence, V, CharSequenceMap<V>> {
|
||||||
public CharSequenceMap() {
|
public CharSequenceMap() {
|
||||||
this(true);
|
this(true);
|
||||||
}
|
}
|
||||||
|
@ -34,4 +39,10 @@ final class CharSequenceMap<V> extends DefaultHeaders<CharSequence, V, CharSeque
|
||||||
public CharSequenceMap(boolean caseSensitive, ValueConverter<V> valueConverter) {
|
public CharSequenceMap(boolean caseSensitive, ValueConverter<V> valueConverter) {
|
||||||
super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter);
|
super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public CharSequenceMap(boolean caseSensitive, ValueConverter<V> valueConverter, int arraySizeHint) {
|
||||||
|
super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter,
|
||||||
|
NameValidator.NOT_NULL, arraySizeHint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,15 @@
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufInputStream;
|
|
||||||
import io.netty.handler.codec.http2.internal.hpack.Decoder;
|
import io.netty.handler.codec.http2.internal.hpack.Decoder;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.HeaderListener;
|
|
||||||
import io.netty.util.AsciiString;
|
|
||||||
import io.netty.util.internal.UnstableApi;
|
import io.netty.util.internal.UnstableApi;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
|
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.DEFAULT_MAX_HEADER_SIZE;
|
||||||
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.INTERNAL_ERROR;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||||
|
|
||||||
|
@ -53,14 +48,15 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2HeadersDecoder(boolean validateHeaders) {
|
public DefaultHttp2HeadersDecoder(boolean validateHeaders) {
|
||||||
this(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE, validateHeaders);
|
this(DEFAULT_MAX_HEADER_SIZE, DEFAULT_HEADER_TABLE_SIZE, validateHeaders, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2HeadersDecoder(int maxHeaderSize, int maxHeaderTableSize, boolean validateHeaders) {
|
public DefaultHttp2HeadersDecoder(int maxHeaderSize, int maxHeaderTableSize, boolean validateHeaders,
|
||||||
|
int initialHuffmanDecodeCapacity) {
|
||||||
if (maxHeaderSize <= 0) {
|
if (maxHeaderSize <= 0) {
|
||||||
throw new IllegalArgumentException("maxHeaderSize must be positive: " + maxHeaderSize);
|
throw new IllegalArgumentException("maxHeaderSize must be positive: " + maxHeaderSize);
|
||||||
}
|
}
|
||||||
decoder = new Decoder(maxHeaderSize, maxHeaderTableSize);
|
decoder = new Decoder(maxHeaderSize, maxHeaderTableSize, initialHuffmanDecodeCapacity);
|
||||||
headerTable = new Http2HeaderTableDecoder();
|
headerTable = new Http2HeaderTableDecoder();
|
||||||
this.maxHeaderSize = maxHeaderSize;
|
this.maxHeaderSize = maxHeaderSize;
|
||||||
this.validateHeaders = validateHeaders;
|
this.validateHeaders = validateHeaders;
|
||||||
|
@ -91,17 +87,9 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
|
public Http2Headers decodeHeaders(ByteBuf headerBlock) throws Http2Exception {
|
||||||
InputStream in = new ByteBufInputStream(headerBlock);
|
|
||||||
try {
|
try {
|
||||||
final Http2Headers headers = new DefaultHttp2Headers(validateHeaders, (int) headerArraySizeAccumulator);
|
final Http2Headers headers = new DefaultHttp2Headers(validateHeaders, (int) headerArraySizeAccumulator);
|
||||||
HeaderListener listener = new HeaderListener() {
|
decoder.decode(headerBlock, headers);
|
||||||
@Override
|
|
||||||
public void addHeader(byte[] key, byte[] value, boolean sensitive) {
|
|
||||||
headers.add(new AsciiString(key, false), new AsciiString(value, false));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
decoder.decode(in, listener);
|
|
||||||
if (decoder.endHeaderBlock()) {
|
if (decoder.endHeaderBlock()) {
|
||||||
maxHeaderSizeExceeded();
|
maxHeaderSizeExceeded();
|
||||||
}
|
}
|
||||||
|
@ -123,12 +111,6 @@ public class DefaultHttp2HeadersDecoder implements Http2HeadersDecoder, Http2Hea
|
||||||
// the the Header builder throws IllegalArgumentException if the key or value was invalid
|
// the the Header builder throws IllegalArgumentException if the key or value was invalid
|
||||||
// for any reason (e.g. the key was an invalid pseudo-header).
|
// for any reason (e.g. the key was an invalid pseudo-header).
|
||||||
throw connectionError(COMPRESSION_ERROR, e, e.getMessage());
|
throw connectionError(COMPRESSION_ERROR, e, e.getMessage());
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
in.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw connectionError(INTERNAL_ERROR, e, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,19 +16,14 @@
|
||||||
package io.netty.handler.codec.http2;
|
package io.netty.handler.codec.http2;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufOutputStream;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.Encoder;
|
import io.netty.handler.codec.http2.internal.hpack.Encoder;
|
||||||
import io.netty.util.AsciiString;
|
|
||||||
import io.netty.util.internal.UnstableApi;
|
import io.netty.util.internal.UnstableApi;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Map.Entry;
|
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.DEFAULT_HEADER_TABLE_SIZE;
|
||||||
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.INTERNAL_ERROR;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
@ -36,9 +31,9 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2HeadersEncoder.Configuration {
|
public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2HeadersEncoder.Configuration {
|
||||||
private final Encoder encoder;
|
private final Encoder encoder;
|
||||||
private final ByteArrayOutputStream tableSizeChangeOutput = new ByteArrayOutputStream();
|
|
||||||
private final SensitivityDetector sensitivityDetector;
|
private final SensitivityDetector sensitivityDetector;
|
||||||
private final Http2HeaderTable headerTable;
|
private final Http2HeaderTable headerTable;
|
||||||
|
private final ByteBuf tableSizeChangeOutput = Unpooled.buffer();
|
||||||
|
|
||||||
public DefaultHttp2HeadersEncoder() {
|
public DefaultHttp2HeadersEncoder() {
|
||||||
this(DEFAULT_HEADER_TABLE_SIZE, NEVER_SENSITIVE);
|
this(DEFAULT_HEADER_TABLE_SIZE, NEVER_SENSITIVE);
|
||||||
|
@ -52,7 +47,6 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception {
|
public void encodeHeaders(Http2Headers headers, ByteBuf buffer) throws Http2Exception {
|
||||||
final OutputStream stream = new ByteBufOutputStream(buffer);
|
|
||||||
try {
|
try {
|
||||||
if (headers.size() > headerTable.maxHeaderListSize()) {
|
if (headers.size() > headerTable.maxHeaderListSize()) {
|
||||||
throw connectionError(PROTOCOL_ERROR, "Number of headers (%d) exceeds maxHeaderListSize (%d)",
|
throw connectionError(PROTOCOL_ERROR, "Number of headers (%d) exceeds maxHeaderListSize (%d)",
|
||||||
|
@ -61,24 +55,18 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
|
||||||
|
|
||||||
// If there was a change in the table size, serialize the output from the encoder
|
// If there was a change in the table size, serialize the output from the encoder
|
||||||
// resulting from that change.
|
// resulting from that change.
|
||||||
if (tableSizeChangeOutput.size() > 0) {
|
if (tableSizeChangeOutput.isReadable()) {
|
||||||
buffer.writeBytes(tableSizeChangeOutput.toByteArray());
|
buffer.writeBytes(tableSizeChangeOutput);
|
||||||
tableSizeChangeOutput.reset();
|
tableSizeChangeOutput.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Entry<CharSequence, CharSequence> header : headers) {
|
for (Entry<CharSequence, CharSequence> header : headers) {
|
||||||
encodeHeader(header.getKey(), header.getValue(), stream);
|
encodeHeader(buffer, header.getKey(), header.getValue());
|
||||||
}
|
}
|
||||||
} catch (Http2Exception e) {
|
} catch (Http2Exception e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw connectionError(COMPRESSION_ERROR, t, "Failed encoding headers block: %s", t.getMessage());
|
throw connectionError(COMPRESSION_ERROR, t, "Failed encoding headers block: %s", t.getMessage());
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
stream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw connectionError(INTERNAL_ERROR, e, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,13 +80,8 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] toBytes(CharSequence chars) {
|
private void encodeHeader(ByteBuf out, CharSequence key, CharSequence value) {
|
||||||
AsciiString aString = AsciiString.of(chars);
|
encoder.encodeHeader(out, key, value, sensitivityDetector.isSensitive(key, value));
|
||||||
return aString.isEntireArrayUsed() ? aString.array() : aString.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void encodeHeader(CharSequence key, CharSequence value, OutputStream stream) throws IOException {
|
|
||||||
encoder.encodeHeader(stream, toBytes(key), toBytes(value), sensitivityDetector.isSensitive(key, value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -113,8 +96,6 @@ public class DefaultHttp2HeadersEncoder implements Http2HeadersEncoder, Http2Hea
|
||||||
try {
|
try {
|
||||||
// No headers should be emitted. If they are, we throw.
|
// No headers should be emitted. If they are, we throw.
|
||||||
encoder.setMaxHeaderTableSize(tableSizeChangeOutput, max);
|
encoder.setMaxHeaderTableSize(tableSizeChangeOutput, max);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new Http2Exception(COMPRESSION_ERROR, e.getMessage(), e);
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new Http2Exception(PROTOCOL_ERROR, t.getMessage(), t);
|
throw new Http2Exception(PROTOCOL_ERROR, t.getMessage(), t);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,17 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType;
|
import io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType;
|
||||||
import io.netty.util.internal.ThrowableUtil;
|
import io.netty.util.internal.ThrowableUtil;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import static io.netty.util.internal.EmptyArrays.EMPTY_BYTES;
|
import static io.netty.util.AsciiString.EMPTY_STRING;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
public final class Decoder {
|
public final class Decoder {
|
||||||
|
|
||||||
|
@ -45,8 +49,6 @@ public final class Decoder {
|
||||||
new IOException("HPACK - decompression failure"), Decoder.class, "decode(...)");
|
new IOException("HPACK - decompression failure"), Decoder.class, "decode(...)");
|
||||||
private static final IOException DECODE_ULE_128_DECOMPRESSION_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
private static final IOException DECODE_ULE_128_DECOMPRESSION_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||||
new IOException("HPACK - decompression failure"), Decoder.class, "decodeULE128(...)");
|
new IOException("HPACK - decompression failure"), Decoder.class, "decodeULE128(...)");
|
||||||
private static final IOException READ_STRING_LITERAL_DECOMPRESSION_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
|
||||||
new IOException("HPACK - decompression failure"), Decoder.class, "readStringLiteral(...)");
|
|
||||||
private static final IOException DECODE_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
|
private static final IOException DECODE_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
|
||||||
new IOException("HPACK - illegal index value"), Decoder.class, "decode(...)");
|
new IOException("HPACK - illegal index value"), Decoder.class, "decode(...)");
|
||||||
private static final IOException INDEX_HEADER_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
|
private static final IOException INDEX_HEADER_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
|
||||||
|
@ -59,8 +61,8 @@ public final class Decoder {
|
||||||
new IOException("HPACK - max dynamic table size change required"), Decoder.class, "decode(...)");
|
new IOException("HPACK - max dynamic table size change required"), Decoder.class, "decode(...)");
|
||||||
|
|
||||||
private final DynamicTable dynamicTable;
|
private final DynamicTable dynamicTable;
|
||||||
|
private final HuffmanDecoder huffmanDecoder;
|
||||||
private int maxHeaderSize;
|
private final int maxHeaderSize;
|
||||||
private int maxDynamicTableSize;
|
private int maxDynamicTableSize;
|
||||||
private int encoderMaxDynamicTableSize;
|
private int encoderMaxDynamicTableSize;
|
||||||
private boolean maxDynamicTableSizeChangeRequired;
|
private boolean maxDynamicTableSizeChangeRequired;
|
||||||
|
@ -73,7 +75,7 @@ public final class Decoder {
|
||||||
private int skipLength;
|
private int skipLength;
|
||||||
private int nameLength;
|
private int nameLength;
|
||||||
private int valueLength;
|
private int valueLength;
|
||||||
private byte[] name;
|
private CharSequence name;
|
||||||
|
|
||||||
private enum State {
|
private enum State {
|
||||||
READ_HEADER_REPRESENTATION,
|
READ_HEADER_REPRESENTATION,
|
||||||
|
@ -93,12 +95,13 @@ public final class Decoder {
|
||||||
/**
|
/**
|
||||||
* Creates a new decoder.
|
* Creates a new decoder.
|
||||||
*/
|
*/
|
||||||
public Decoder(int maxHeaderSize, int maxHeaderTableSize) {
|
public Decoder(int maxHeaderSize, int maxHeaderTableSize, int initialHuffmanDecodeCapacity) {
|
||||||
dynamicTable = new DynamicTable(maxHeaderTableSize);
|
dynamicTable = new DynamicTable(maxHeaderTableSize);
|
||||||
this.maxHeaderSize = maxHeaderSize;
|
this.maxHeaderSize = maxHeaderSize;
|
||||||
maxDynamicTableSize = maxHeaderTableSize;
|
maxDynamicTableSize = maxHeaderTableSize;
|
||||||
encoderMaxDynamicTableSize = maxHeaderTableSize;
|
encoderMaxDynamicTableSize = maxHeaderTableSize;
|
||||||
maxDynamicTableSizeChangeRequired = false;
|
maxDynamicTableSizeChangeRequired = false;
|
||||||
|
huffmanDecoder = new HuffmanDecoder(initialHuffmanDecodeCapacity);
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,11 +114,11 @@ public final class Decoder {
|
||||||
/**
|
/**
|
||||||
* Decode the header block into header fields.
|
* Decode the header block into header fields.
|
||||||
*/
|
*/
|
||||||
public void decode(InputStream in, HeaderListener headerListener) throws IOException {
|
public void decode(ByteBuf in, Http2Headers headers) throws IOException {
|
||||||
while (in.available() > 0) {
|
while (in.isReadable()) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case READ_HEADER_REPRESENTATION:
|
case READ_HEADER_REPRESENTATION:
|
||||||
byte b = (byte) in.read();
|
byte b = in.readByte();
|
||||||
if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
|
if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
|
||||||
// Encoder MUST signal maximum dynamic table size change
|
// Encoder MUST signal maximum dynamic table size change
|
||||||
throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
|
throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
|
||||||
|
@ -128,7 +131,7 @@ public final class Decoder {
|
||||||
} else if (index == 0x7F) {
|
} else if (index == 0x7F) {
|
||||||
state = State.READ_INDEXED_HEADER;
|
state = State.READ_INDEXED_HEADER;
|
||||||
} else {
|
} else {
|
||||||
indexHeader(index, headerListener);
|
indexHeader(index, headers);
|
||||||
}
|
}
|
||||||
} else if ((b & 0x40) == 0x40) {
|
} else if ((b & 0x40) == 0x40) {
|
||||||
// Literal Header Field with Incremental Indexing
|
// Literal Header Field with Incremental Indexing
|
||||||
|
@ -194,7 +197,7 @@ public final class Decoder {
|
||||||
throw DECODE_DECOMPRESSION_EXCEPTION;
|
throw DECODE_DECOMPRESSION_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
indexHeader(index + headerIndex, headerListener);
|
indexHeader(index + headerIndex, headers);
|
||||||
state = State.READ_HEADER_REPRESENTATION;
|
state = State.READ_HEADER_REPRESENTATION;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -215,7 +218,7 @@ public final class Decoder {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:
|
case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:
|
||||||
b = (byte) in.read();
|
b = in.readByte();
|
||||||
huffmanEncoded = (b & 0x80) == 0x80;
|
huffmanEncoded = (b & 0x80) == 0x80;
|
||||||
index = b & 0x7F;
|
index = b & 0x7F;
|
||||||
if (index == 0x7f) {
|
if (index == 0x7f) {
|
||||||
|
@ -228,7 +231,7 @@ public final class Decoder {
|
||||||
|
|
||||||
if (indexType == IndexType.NONE) {
|
if (indexType == IndexType.NONE) {
|
||||||
// Name is unused so skip bytes
|
// Name is unused so skip bytes
|
||||||
name = EMPTY_BYTES;
|
name = EMPTY_STRING;
|
||||||
skipLength = nameLength;
|
skipLength = nameLength;
|
||||||
state = State.SKIP_LITERAL_HEADER_NAME;
|
state = State.SKIP_LITERAL_HEADER_NAME;
|
||||||
break;
|
break;
|
||||||
|
@ -237,7 +240,7 @@ public final class Decoder {
|
||||||
// Check name length against max dynamic table size
|
// Check name length against max dynamic table size
|
||||||
if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
|
if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
|
||||||
dynamicTable.clear();
|
dynamicTable.clear();
|
||||||
name = EMPTY_BYTES;
|
name = EMPTY_STRING;
|
||||||
skipLength = nameLength;
|
skipLength = nameLength;
|
||||||
state = State.SKIP_LITERAL_HEADER_NAME;
|
state = State.SKIP_LITERAL_HEADER_NAME;
|
||||||
break;
|
break;
|
||||||
|
@ -264,7 +267,7 @@ public final class Decoder {
|
||||||
if (exceedsMaxHeaderSize(nameLength)) {
|
if (exceedsMaxHeaderSize(nameLength)) {
|
||||||
if (indexType == IndexType.NONE) {
|
if (indexType == IndexType.NONE) {
|
||||||
// Name is unused so skip bytes
|
// Name is unused so skip bytes
|
||||||
name = EMPTY_BYTES;
|
name = EMPTY_STRING;
|
||||||
skipLength = nameLength;
|
skipLength = nameLength;
|
||||||
state = State.SKIP_LITERAL_HEADER_NAME;
|
state = State.SKIP_LITERAL_HEADER_NAME;
|
||||||
break;
|
break;
|
||||||
|
@ -273,7 +276,7 @@ public final class Decoder {
|
||||||
// Check name length against max dynamic table size
|
// Check name length against max dynamic table size
|
||||||
if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
|
if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
|
||||||
dynamicTable.clear();
|
dynamicTable.clear();
|
||||||
name = EMPTY_BYTES;
|
name = EMPTY_STRING;
|
||||||
skipLength = nameLength;
|
skipLength = nameLength;
|
||||||
state = State.SKIP_LITERAL_HEADER_NAME;
|
state = State.SKIP_LITERAL_HEADER_NAME;
|
||||||
break;
|
break;
|
||||||
|
@ -284,7 +287,7 @@ public final class Decoder {
|
||||||
|
|
||||||
case READ_LITERAL_HEADER_NAME:
|
case READ_LITERAL_HEADER_NAME:
|
||||||
// Wait until entire name is readable
|
// Wait until entire name is readable
|
||||||
if (in.available() < nameLength) {
|
if (in.readableBytes() < nameLength) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +297,9 @@ public final class Decoder {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SKIP_LITERAL_HEADER_NAME:
|
case SKIP_LITERAL_HEADER_NAME:
|
||||||
skipLength -= in.skip(skipLength);
|
int skip = min(in.readableBytes(), skipLength);
|
||||||
|
in.skipBytes(skip);
|
||||||
|
skipLength -= skip;
|
||||||
|
|
||||||
if (skipLength == 0) {
|
if (skipLength == 0) {
|
||||||
state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
|
state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
|
||||||
|
@ -302,7 +307,7 @@ public final class Decoder {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
|
case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
|
||||||
b = (byte) in.read();
|
b = in.readByte();
|
||||||
huffmanEncoded = (b & 0x80) == 0x80;
|
huffmanEncoded = (b & 0x80) == 0x80;
|
||||||
index = b & 0x7F;
|
index = b & 0x7F;
|
||||||
if (index == 0x7f) {
|
if (index == 0x7f) {
|
||||||
|
@ -331,7 +336,7 @@ public final class Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueLength == 0) {
|
if (valueLength == 0) {
|
||||||
insertHeader(headerListener, name, EMPTY_BYTES, indexType);
|
insertHeader(headers, name, EMPTY_STRING, indexType);
|
||||||
state = State.READ_HEADER_REPRESENTATION;
|
state = State.READ_HEADER_REPRESENTATION;
|
||||||
} else {
|
} else {
|
||||||
state = State.READ_LITERAL_HEADER_VALUE;
|
state = State.READ_LITERAL_HEADER_VALUE;
|
||||||
|
@ -377,18 +382,19 @@ public final class Decoder {
|
||||||
|
|
||||||
case READ_LITERAL_HEADER_VALUE:
|
case READ_LITERAL_HEADER_VALUE:
|
||||||
// Wait until entire value is readable
|
// Wait until entire value is readable
|
||||||
if (in.available() < valueLength) {
|
if (in.readableBytes() < valueLength) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] value = readStringLiteral(in, valueLength);
|
CharSequence value = readStringLiteral(in, valueLength);
|
||||||
insertHeader(headerListener, name, value, indexType);
|
insertHeader(headers, name, value, indexType);
|
||||||
state = State.READ_HEADER_REPRESENTATION;
|
state = State.READ_HEADER_REPRESENTATION;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SKIP_LITERAL_HEADER_VALUE:
|
case SKIP_LITERAL_HEADER_VALUE:
|
||||||
valueLength -= in.skip(valueLength);
|
int skipBytes = min(in.readableBytes(), valueLength);
|
||||||
|
in.skipBytes(skipBytes);
|
||||||
|
valueLength -= skipBytes;
|
||||||
if (valueLength == 0) {
|
if (valueLength == 0) {
|
||||||
state = State.READ_HEADER_REPRESENTATION;
|
state = State.READ_HEADER_REPRESENTATION;
|
||||||
}
|
}
|
||||||
|
@ -474,21 +480,21 @@ public final class Decoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void indexHeader(int index, HeaderListener headerListener) throws IOException {
|
private void indexHeader(int index, Http2Headers headers) throws IOException {
|
||||||
if (index <= StaticTable.length) {
|
if (index <= StaticTable.length) {
|
||||||
HeaderField headerField = StaticTable.getEntry(index);
|
HeaderField headerField = StaticTable.getEntry(index);
|
||||||
addHeader(headerListener, headerField.name, headerField.value, false);
|
addHeader(headers, headerField.name, headerField.value);
|
||||||
} else if (index - StaticTable.length <= dynamicTable.length()) {
|
} else if (index - StaticTable.length <= dynamicTable.length()) {
|
||||||
HeaderField headerField = dynamicTable.getEntry(index - StaticTable.length);
|
HeaderField headerField = dynamicTable.getEntry(index - StaticTable.length);
|
||||||
addHeader(headerListener, headerField.name, headerField.value, false);
|
addHeader(headers, headerField.name, headerField.value);
|
||||||
} else {
|
} else {
|
||||||
throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
|
throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertHeader(HeaderListener headerListener, byte[] name, byte[] value,
|
private void insertHeader(Http2Headers headers, CharSequence name, CharSequence value,
|
||||||
IndexType indexType) {
|
IndexType indexType) {
|
||||||
addHeader(headerListener, name, value, indexType == IndexType.NEVER);
|
addHeader(headers, name, value);
|
||||||
|
|
||||||
switch (indexType) {
|
switch (indexType) {
|
||||||
case NONE:
|
case NONE:
|
||||||
|
@ -504,11 +510,10 @@ public final class Decoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addHeader(HeaderListener headerListener, byte[] name, byte[] value,
|
private void addHeader(Http2Headers headers, CharSequence name, CharSequence value) {
|
||||||
boolean sensitive) {
|
long newSize = headerSize + name.length() + value.length();
|
||||||
long newSize = headerSize + name.length + value.length;
|
|
||||||
if (newSize <= maxHeaderSize) {
|
if (newSize <= maxHeaderSize) {
|
||||||
headerListener.addHeader(name, value, sensitive);
|
headers.add(name, value);
|
||||||
headerSize = (int) newSize;
|
headerSize = (int) newSize;
|
||||||
} else {
|
} else {
|
||||||
// truncation will be reported during endHeaderBlock
|
// truncation will be reported during endHeaderBlock
|
||||||
|
@ -527,32 +532,29 @@ public final class Decoder {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] readStringLiteral(InputStream in, int length) throws IOException {
|
private CharSequence readStringLiteral(ByteBuf in, int length) throws IOException {
|
||||||
byte[] buf = new byte[length];
|
|
||||||
if (in.read(buf) != length) {
|
|
||||||
throw READ_STRING_LITERAL_DECOMPRESSION_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (huffmanEncoded) {
|
if (huffmanEncoded) {
|
||||||
return Huffman.DECODER.decode(buf);
|
return huffmanDecoder.decode(in, length);
|
||||||
} else {
|
} else {
|
||||||
return buf;
|
byte[] buf = new byte[length];
|
||||||
|
in.readBytes(buf);
|
||||||
|
return new AsciiString(buf, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsigned Little Endian Base 128 Variable-Length Integer Encoding
|
// Unsigned Little Endian Base 128 Variable-Length Integer Encoding
|
||||||
private static int decodeULE128(InputStream in) throws IOException {
|
private static int decodeULE128(ByteBuf in) throws IOException {
|
||||||
in.mark(5);
|
in.markReaderIndex();
|
||||||
int result = 0;
|
int result = 0;
|
||||||
int shift = 0;
|
int shift = 0;
|
||||||
while (shift < 32) {
|
while (shift < 32) {
|
||||||
if (in.available() == 0) {
|
if (!in.isReadable()) {
|
||||||
// Buffer does not contain entire integer,
|
// Buffer does not contain entire integer,
|
||||||
// reset reader index and return -1.
|
// reset reader index and return -1.
|
||||||
in.reset();
|
in.resetReaderIndex();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
byte b = (byte) in.read();
|
byte b = in.readByte();
|
||||||
if (shift == 28 && (b & 0xF8) != 0) {
|
if (shift == 28 && (b & 0xF8) != 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -563,7 +565,7 @@ public final class Decoder {
|
||||||
shift += 7;
|
shift += 7;
|
||||||
}
|
}
|
||||||
// Value exceeds Integer.MAX_VALUE
|
// Value exceeds Integer.MAX_VALUE
|
||||||
in.reset();
|
in.resetReaderIndex();
|
||||||
throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
|
throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,10 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import java.io.IOException;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.io.OutputStream;
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.INCREMENTAL;
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.INCREMENTAL;
|
||||||
|
@ -42,16 +44,17 @@ import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NO
|
||||||
public final class Encoder {
|
public final class Encoder {
|
||||||
|
|
||||||
private static final int BUCKET_SIZE = 17;
|
private static final int BUCKET_SIZE = 17;
|
||||||
private static final byte[] EMPTY = {};
|
|
||||||
|
|
||||||
// for testing
|
// for testing
|
||||||
private final boolean useIndexing;
|
private final boolean useIndexing;
|
||||||
private final boolean forceHuffmanOn;
|
private final boolean forceHuffmanOn;
|
||||||
private final boolean forceHuffmanOff;
|
private final boolean forceHuffmanOff;
|
||||||
|
private final HuffmanEncoder huffmanEncoder = new HuffmanEncoder();
|
||||||
|
|
||||||
// a linked hash map of header fields
|
// a linked hash map of header fields
|
||||||
private final HeaderEntry[] headerFields = new HeaderEntry[BUCKET_SIZE];
|
private final HeaderEntry[] headerFields = new HeaderEntry[BUCKET_SIZE];
|
||||||
private final HeaderEntry head = new HeaderEntry(-1, EMPTY, EMPTY, Integer.MAX_VALUE, null);
|
private final HeaderEntry head = new HeaderEntry(-1, AsciiString.EMPTY_STRING,
|
||||||
|
AsciiString.EMPTY_STRING, Integer.MAX_VALUE, null);
|
||||||
private int size;
|
private int size;
|
||||||
private int capacity;
|
private int capacity;
|
||||||
|
|
||||||
|
@ -77,15 +80,16 @@ public final class Encoder {
|
||||||
this.useIndexing = useIndexing;
|
this.useIndexing = useIndexing;
|
||||||
this.forceHuffmanOn = forceHuffmanOn;
|
this.forceHuffmanOn = forceHuffmanOn;
|
||||||
this.forceHuffmanOff = forceHuffmanOff;
|
this.forceHuffmanOff = forceHuffmanOff;
|
||||||
this.capacity = maxHeaderTableSize;
|
capacity = maxHeaderTableSize;
|
||||||
head.before = head.after = head;
|
head.before = head.after = head;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode the header field into the header block.
|
* Encode the header field into the header block.
|
||||||
|
*
|
||||||
|
* <strong>The given {@link CharSequence}s must be immutable!</strong>
|
||||||
*/
|
*/
|
||||||
public void encodeHeader(OutputStream out, byte[] name, byte[] value, boolean sensitive)
|
public void encodeHeader(ByteBuf out, CharSequence name, CharSequence value, boolean sensitive) {
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
// If the header value is sensitive then it must never be indexed
|
// If the header value is sensitive then it must never be indexed
|
||||||
if (sensitive) {
|
if (sensitive) {
|
||||||
|
@ -143,7 +147,7 @@ public final class Encoder {
|
||||||
/**
|
/**
|
||||||
* Set the maximum table size.
|
* Set the maximum table size.
|
||||||
*/
|
*/
|
||||||
public void setMaxHeaderTableSize(OutputStream out, int maxHeaderTableSize) throws IOException {
|
public void setMaxHeaderTableSize(ByteBuf out, int maxHeaderTableSize) {
|
||||||
if (maxHeaderTableSize < 0) {
|
if (maxHeaderTableSize < 0) {
|
||||||
throw new IllegalArgumentException("Illegal Capacity: " + maxHeaderTableSize);
|
throw new IllegalArgumentException("Illegal Capacity: " + maxHeaderTableSize);
|
||||||
}
|
}
|
||||||
|
@ -165,22 +169,22 @@ public final class Encoder {
|
||||||
/**
|
/**
|
||||||
* Encode integer according to Section 5.1.
|
* Encode integer according to Section 5.1.
|
||||||
*/
|
*/
|
||||||
private static void encodeInteger(OutputStream out, int mask, int n, int i) throws IOException {
|
private static void encodeInteger(ByteBuf out, int mask, int n, int i) {
|
||||||
if (n < 0 || n > 8) {
|
if (n < 0 || n > 8) {
|
||||||
throw new IllegalArgumentException("N: " + n);
|
throw new IllegalArgumentException("N: " + n);
|
||||||
}
|
}
|
||||||
int nbits = 0xFF >>> (8 - n);
|
int nbits = 0xFF >>> (8 - n);
|
||||||
if (i < nbits) {
|
if (i < nbits) {
|
||||||
out.write(mask | i);
|
out.writeByte(mask | i);
|
||||||
} else {
|
} else {
|
||||||
out.write(mask | nbits);
|
out.writeByte(mask | nbits);
|
||||||
int length = i - nbits;
|
int length = i - nbits;
|
||||||
while (true) {
|
for (;;) {
|
||||||
if ((length & ~0x7F) == 0) {
|
if ((length & ~0x7F) == 0) {
|
||||||
out.write(length);
|
out.writeByte(length);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
out.write((length & 0x7F) | 0x80);
|
out.writeByte((length & 0x7F) | 0x80);
|
||||||
length >>>= 7;
|
length >>>= 7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,23 +194,30 @@ public final class Encoder {
|
||||||
/**
|
/**
|
||||||
* Encode string literal according to Section 5.2.
|
* Encode string literal according to Section 5.2.
|
||||||
*/
|
*/
|
||||||
private void encodeStringLiteral(OutputStream out, byte[] string) throws IOException {
|
private void encodeStringLiteral(ByteBuf out, CharSequence string) {
|
||||||
int huffmanLength = Huffman.ENCODER.getEncodedLength(string);
|
int huffmanLength = huffmanEncoder.getEncodedLength(string);
|
||||||
if ((huffmanLength < string.length && !forceHuffmanOff) || forceHuffmanOn) {
|
if ((huffmanLength < string.length() && !forceHuffmanOff) || forceHuffmanOn) {
|
||||||
encodeInteger(out, 0x80, 7, huffmanLength);
|
encodeInteger(out, 0x80, 7, huffmanLength);
|
||||||
Huffman.ENCODER.encode(out, string);
|
huffmanEncoder.encode(out, string);
|
||||||
} else {
|
} else {
|
||||||
encodeInteger(out, 0x00, 7, string.length);
|
encodeInteger(out, 0x00, 7, string.length());
|
||||||
out.write(string, 0, string.length);
|
if (string instanceof AsciiString) {
|
||||||
|
// Fast-path
|
||||||
|
AsciiString asciiString = (AsciiString) string;
|
||||||
|
out.writeBytes(asciiString.array(), asciiString.arrayOffset(), asciiString.length());
|
||||||
|
} else {
|
||||||
|
// Only ASCII is allowed in http2 headers, so its fine to use this.
|
||||||
|
// https://tools.ietf.org/html/rfc7540#section-8.1.2
|
||||||
|
out.writeCharSequence(string, CharsetUtil.ISO_8859_1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode literal header field according to Section 6.2.
|
* Encode literal header field according to Section 6.2.
|
||||||
*/
|
*/
|
||||||
private void encodeLiteral(OutputStream out, byte[] name, byte[] value, HpackUtil.IndexType indexType,
|
private void encodeLiteral(ByteBuf out, CharSequence name, CharSequence value, HpackUtil.IndexType indexType,
|
||||||
int nameIndex)
|
int nameIndex) {
|
||||||
throws IOException {
|
|
||||||
int mask;
|
int mask;
|
||||||
int prefixBits;
|
int prefixBits;
|
||||||
switch (indexType) {
|
switch (indexType) {
|
||||||
|
@ -232,7 +243,7 @@ public final class Encoder {
|
||||||
encodeStringLiteral(out, value);
|
encodeStringLiteral(out, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getNameIndex(byte[] name) {
|
private int getNameIndex(CharSequence name) {
|
||||||
int index = StaticTable.getIndex(name);
|
int index = StaticTable.getIndex(name);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
index = getIndex(name);
|
index = getIndex(name);
|
||||||
|
@ -247,7 +258,7 @@ public final class Encoder {
|
||||||
* Ensure that the dynamic table has enough room to hold 'headerSize' more bytes. Removes the
|
* Ensure that the dynamic table has enough room to hold 'headerSize' more bytes. Removes the
|
||||||
* oldest entry from the dynamic table until sufficient space is available.
|
* oldest entry from the dynamic table until sufficient space is available.
|
||||||
*/
|
*/
|
||||||
private void ensureCapacity(int headerSize) throws IOException {
|
private void ensureCapacity(int headerSize) {
|
||||||
while (size + headerSize > capacity) {
|
while (size + headerSize > capacity) {
|
||||||
int index = length();
|
int index = length();
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
|
@ -286,7 +297,7 @@ public final class Encoder {
|
||||||
* Returns the header entry with the lowest index value for the header field. Returns null if
|
* Returns the header entry with the lowest index value for the header field. Returns null if
|
||||||
* header field is not in the dynamic table.
|
* header field is not in the dynamic table.
|
||||||
*/
|
*/
|
||||||
private HeaderEntry getEntry(byte[] name, byte[] value) {
|
private HeaderEntry getEntry(CharSequence name, CharSequence value) {
|
||||||
if (length() == 0 || name == null || value == null) {
|
if (length() == 0 || name == null || value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -306,7 +317,7 @@ public final class Encoder {
|
||||||
* Returns the lowest index value for the header field name in the dynamic table. Returns -1 if
|
* Returns the lowest index value for the header field name in the dynamic table. Returns -1 if
|
||||||
* the header field name is not in the dynamic table.
|
* the header field name is not in the dynamic table.
|
||||||
*/
|
*/
|
||||||
private int getIndex(byte[] name) {
|
private int getIndex(CharSequence name) {
|
||||||
if (length() == 0 || name == null) {
|
if (length() == 0 || name == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -327,7 +338,7 @@ public final class Encoder {
|
||||||
*/
|
*/
|
||||||
private int getIndex(int index) {
|
private int getIndex(int index) {
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
return index;
|
return -1;
|
||||||
}
|
}
|
||||||
return index - head.before.index + 1;
|
return index - head.before.index + 1;
|
||||||
}
|
}
|
||||||
|
@ -337,7 +348,7 @@ public final class Encoder {
|
||||||
* the size of the table and the new header field is less than the table's capacity. If the size
|
* the size of the table and the new header field is less than the table's capacity. If the size
|
||||||
* of the new entry is larger than the table's capacity, the dynamic table will be cleared.
|
* of the new entry is larger than the table's capacity, the dynamic table will be cleared.
|
||||||
*/
|
*/
|
||||||
private void add(byte[] name, byte[] value) {
|
private void add(CharSequence name, CharSequence value) {
|
||||||
int headerSize = HeaderField.sizeOf(name, value);
|
int headerSize = HeaderField.sizeOf(name, value);
|
||||||
|
|
||||||
// Clear the table if the header field size is larger than the capacity.
|
// Clear the table if the header field size is larger than the capacity.
|
||||||
|
@ -351,10 +362,6 @@ public final class Encoder {
|
||||||
remove();
|
remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy name and value that modifications of original do not affect the dynamic table.
|
|
||||||
name = Arrays.copyOf(name, name.length);
|
|
||||||
value = Arrays.copyOf(value, value.length);
|
|
||||||
|
|
||||||
int h = hash(name);
|
int h = hash(name);
|
||||||
int i = index(h);
|
int i = index(h);
|
||||||
HeaderEntry old = headerFields[i];
|
HeaderEntry old = headerFields[i];
|
||||||
|
@ -400,16 +407,16 @@ public final class Encoder {
|
||||||
private void clear() {
|
private void clear() {
|
||||||
Arrays.fill(headerFields, null);
|
Arrays.fill(headerFields, null);
|
||||||
head.before = head.after = head;
|
head.before = head.after = head;
|
||||||
this.size = 0;
|
size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hash code for the given header field name.
|
* Returns the hash code for the given header field name.
|
||||||
*/
|
*/
|
||||||
private static int hash(byte[] name) {
|
private static int hash(CharSequence name) {
|
||||||
int h = 0;
|
int h = 0;
|
||||||
for (int i = 0; i < name.length; i++) {
|
for (int i = 0; i < name.length(); i++) {
|
||||||
h = 31 * h + name[i];
|
h = 31 * h + name.charAt(i);
|
||||||
}
|
}
|
||||||
if (h > 0) {
|
if (h > 0) {
|
||||||
return h;
|
return h;
|
||||||
|
@ -444,7 +451,7 @@ public final class Encoder {
|
||||||
/**
|
/**
|
||||||
* Creates new entry.
|
* Creates new entry.
|
||||||
*/
|
*/
|
||||||
HeaderEntry(int hash, byte[] name, byte[] value, int index, HeaderEntry next) {
|
HeaderEntry(int hash, CharSequence name, CharSequence value, int index, HeaderEntry next) {
|
||||||
super(name, value);
|
super(name, value);
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
|
|
|
@ -31,35 +31,30 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import static io.netty.util.CharsetUtil.ISO_8859_1;
|
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
class HeaderField implements Comparable<HeaderField> {
|
class HeaderField {
|
||||||
|
|
||||||
// Section 4.1. Calculating Table Size
|
// Section 4.1. Calculating Table Size
|
||||||
// The additional 32 octets account for an estimated
|
// The additional 32 octets account for an estimated
|
||||||
// overhead associated with the structure.
|
// overhead associated with the structure.
|
||||||
static final int HEADER_ENTRY_OVERHEAD = 32;
|
static final int HEADER_ENTRY_OVERHEAD = 32;
|
||||||
|
|
||||||
static int sizeOf(byte[] name, byte[] value) {
|
static int sizeOf(CharSequence name, CharSequence value) {
|
||||||
return name.length + value.length + HEADER_ENTRY_OVERHEAD;
|
return name.length() + value.length() + HEADER_ENTRY_OVERHEAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
final byte[] name;
|
final CharSequence name;
|
||||||
final byte[] value;
|
final CharSequence value;
|
||||||
|
|
||||||
// This constructor can only be used if name and value are ISO-8859-1 encoded.
|
// This constructor can only be used if name and value are ISO-8859-1 encoded.
|
||||||
HeaderField(String name, String value) {
|
HeaderField(CharSequence name, CharSequence value) {
|
||||||
this(name.getBytes(ISO_8859_1), value.getBytes(ISO_8859_1));
|
|
||||||
}
|
|
||||||
|
|
||||||
HeaderField(byte[] name, byte[] value) {
|
|
||||||
this.name = checkNotNull(name, "name");
|
this.name = checkNotNull(name, "name");
|
||||||
this.value = checkNotNull(value, "value");
|
this.value = checkNotNull(value, "value");
|
||||||
}
|
}
|
||||||
|
|
||||||
int size() {
|
int size() {
|
||||||
return name.length + value.length + HEADER_ENTRY_OVERHEAD;
|
return name.length() + value.length() + HEADER_ENTRY_OVERHEAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,32 +63,6 @@ class HeaderField implements Comparable<HeaderField> {
|
||||||
return super.hashCode();
|
return super.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(HeaderField anotherHeaderField) {
|
|
||||||
int ret = compareTo(name, anotherHeaderField.name);
|
|
||||||
if (ret == 0) {
|
|
||||||
ret = compareTo(value, anotherHeaderField.value);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int compareTo(byte[] s1, byte[] s2) {
|
|
||||||
int len1 = s1.length;
|
|
||||||
int len2 = s2.length;
|
|
||||||
int lim = Math.min(len1, len2);
|
|
||||||
|
|
||||||
int k = 0;
|
|
||||||
while (k < lim) {
|
|
||||||
byte b1 = s1[k];
|
|
||||||
byte b2 = s2[k];
|
|
||||||
if (b1 != b2) {
|
|
||||||
return b1 - b2;
|
|
||||||
}
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
return len1 - len2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == this) {
|
if (obj == this) {
|
||||||
|
@ -110,8 +79,6 @@ class HeaderField implements Comparable<HeaderField> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String nameString = new String(name);
|
return name + ": " + value;
|
||||||
String valueString = new String(value);
|
|
||||||
return nameString + ": " + valueString;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 The Netty Project
|
|
||||||
*
|
|
||||||
* The Netty Project licenses this file to you under the Apache License,
|
|
||||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2014 Twitter, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
|
||||||
|
|
||||||
public interface HeaderListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* emitHeader is called by the decoder during header field emission.
|
|
||||||
* The name and value byte arrays must not be modified.
|
|
||||||
*/
|
|
||||||
void addHeader(byte[] name, byte[] value, boolean sensitive);
|
|
||||||
}
|
|
|
@ -31,19 +31,17 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
final class HpackUtil {
|
final class HpackUtil {
|
||||||
/**
|
/**
|
||||||
* A string compare that doesn't leak timing information.
|
* A string compare that doesn't leak timing information.
|
||||||
*/
|
*/
|
||||||
static boolean equals(byte[] s1, byte[] s2) {
|
static boolean equals(CharSequence s1, CharSequence s2) {
|
||||||
if (s1.length != s2.length) {
|
if (s1.length() != s2.length()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
char c = 0;
|
char c = 0;
|
||||||
for (int i = 0; i < s1.length; i++) {
|
for (int i = 0; i < s1.length(); i++) {
|
||||||
c |= s1[i] ^ s2[i];
|
c |= s1.charAt(i) ^ s2.charAt(i);
|
||||||
}
|
}
|
||||||
return c == 0;
|
return c == 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 The Netty Project
|
|
||||||
*
|
|
||||||
* The Netty Project licenses this file to you under the Apache License,
|
|
||||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright 2014 Twitter, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODES;
|
|
||||||
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODE_LENGTHS;
|
|
||||||
|
|
||||||
public final class Huffman {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Huffman Decoder
|
|
||||||
*/
|
|
||||||
public static final HuffmanDecoder DECODER =
|
|
||||||
new HuffmanDecoder(HUFFMAN_CODES, HUFFMAN_CODE_LENGTHS);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Huffman Encoder
|
|
||||||
*/
|
|
||||||
public static final HuffmanEncoder ENCODER =
|
|
||||||
new HuffmanEncoder(HUFFMAN_CODES, HUFFMAN_CODE_LENGTHS);
|
|
||||||
|
|
||||||
private Huffman() {
|
|
||||||
// utility class
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,10 +32,16 @@
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import io.netty.util.internal.ThrowableUtil;
|
import io.netty.util.internal.ThrowableUtil;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.ByteProcessor;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODES;
|
||||||
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODE_LENGTHS;
|
||||||
|
|
||||||
final class HuffmanDecoder {
|
final class HuffmanDecoder {
|
||||||
|
|
||||||
private static final IOException EOS_DECODED = ThrowableUtil.unknownStackTrace(
|
private static final IOException EOS_DECODED = ThrowableUtil.unknownStackTrace(
|
||||||
|
@ -43,19 +49,12 @@ final class HuffmanDecoder {
|
||||||
private static final IOException INVALID_PADDING = ThrowableUtil.unknownStackTrace(
|
private static final IOException INVALID_PADDING = ThrowableUtil.unknownStackTrace(
|
||||||
new IOException("HPACK - Invalid Padding"), HuffmanDecoder.class, "decode(...)");
|
new IOException("HPACK - Invalid Padding"), HuffmanDecoder.class, "decode(...)");
|
||||||
|
|
||||||
private final Node root;
|
private static final Node ROOT = buildTree(HUFFMAN_CODES, HUFFMAN_CODE_LENGTHS);
|
||||||
|
|
||||||
/**
|
private final DecoderProcessor processor;
|
||||||
* Creates a new Huffman decoder with the specified Huffman coding.
|
|
||||||
*
|
HuffmanDecoder(int initialCapacity) {
|
||||||
* @param codes the Huffman codes indexed by symbol
|
processor = new DecoderProcessor(initialCapacity);
|
||||||
* @param lengths the length of each Huffman code
|
|
||||||
*/
|
|
||||||
HuffmanDecoder(int[] codes, byte[] lengths) {
|
|
||||||
if (codes.length != 257 || codes.length != lengths.length) {
|
|
||||||
throw new IllegalArgumentException("invalid Huffman coding");
|
|
||||||
}
|
|
||||||
root = buildTree(codes, lengths);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,84 +65,11 @@ final class HuffmanDecoder {
|
||||||
* @throws IOException if an I/O error occurs. In particular, an <code>IOException</code> may be
|
* @throws IOException if an I/O error occurs. In particular, an <code>IOException</code> may be
|
||||||
* thrown if the output stream has been closed.
|
* thrown if the output stream has been closed.
|
||||||
*/
|
*/
|
||||||
public byte[] decode(byte[] buf) throws IOException {
|
public AsciiString decode(ByteBuf buf, int length) throws IOException {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
processor.reset();
|
||||||
|
buf.forEachByte(buf.readerIndex(), length, processor);
|
||||||
/*
|
buf.skipBytes(length);
|
||||||
* The idea here is to consume whole bytes at a time rather than individual bits. node
|
return processor.end();
|
||||||
* represents the Huffman tree, with all bit patterns denormalized as 256 children. Each
|
|
||||||
* child represents the last 8 bits of the huffman code. The parents of each child each
|
|
||||||
* represent the successive 8 bit chunks that lead up to the last most part. 8 bit bytes
|
|
||||||
* from buf are used to traverse these tree until a terminal node is found.
|
|
||||||
*
|
|
||||||
* current is a bit buffer. The low order bits represent how much of the huffman code has
|
|
||||||
* not been used to traverse the tree. Thus, the high order bits are just garbage.
|
|
||||||
* currentBits represents how many of the low order bits of current are actually valid.
|
|
||||||
* currentBits will vary between 0 and 15.
|
|
||||||
*
|
|
||||||
* symbolBits is the number of bits of the the symbol being decoded, *including* all those
|
|
||||||
* of the parent nodes. symbolBits tells how far down the tree we are. For example, when
|
|
||||||
* decoding the invalid sequence {0xff, 0xff}, currentBits will be 0, but symbolBits will be
|
|
||||||
* 16. This is used to know if buf ended early (before consuming a whole symbol) or if
|
|
||||||
* there is too much padding.
|
|
||||||
*/
|
|
||||||
Node node = root;
|
|
||||||
int current = 0;
|
|
||||||
int currentBits = 0;
|
|
||||||
int symbolBits = 0;
|
|
||||||
for (int i = 0; i < buf.length; i++) {
|
|
||||||
int b = buf[i] & 0xFF;
|
|
||||||
current = (current << 8) | b;
|
|
||||||
currentBits += 8;
|
|
||||||
symbolBits += 8;
|
|
||||||
// While there are unconsumed bits in current, keep consuming symbols.
|
|
||||||
while (currentBits >= 8) {
|
|
||||||
int c = (current >>> (currentBits - 8)) & 0xFF;
|
|
||||||
node = node.children[c];
|
|
||||||
currentBits -= node.bits;
|
|
||||||
if (node.isTerminal()) {
|
|
||||||
if (node.symbol == HpackUtil.HUFFMAN_EOS) {
|
|
||||||
throw EOS_DECODED;
|
|
||||||
}
|
|
||||||
baos.write(node.symbol);
|
|
||||||
node = root;
|
|
||||||
// Upon consuming a whole symbol, reset the symbol bits to the number of bits
|
|
||||||
// left over in the byte.
|
|
||||||
symbolBits = currentBits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We have consumed all the bytes in buf, but haven't consumed all the symbols. We may be on
|
|
||||||
* a partial symbol, so consume until there is nothing left. This will loop at most 2 times.
|
|
||||||
*/
|
|
||||||
while (currentBits > 0) {
|
|
||||||
int c = (current << (8 - currentBits)) & 0xFF;
|
|
||||||
node = node.children[c];
|
|
||||||
if (node.isTerminal() && node.bits <= currentBits) {
|
|
||||||
if (node.symbol == HpackUtil.HUFFMAN_EOS) {
|
|
||||||
throw EOS_DECODED;
|
|
||||||
}
|
|
||||||
currentBits -= node.bits;
|
|
||||||
baos.write(node.symbol);
|
|
||||||
node = root;
|
|
||||||
symbolBits = currentBits;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section 5.2. String Literal Representation
|
|
||||||
// A padding strictly longer than 7 bits MUST be treated as a decoding error.
|
|
||||||
// Padding not corresponding to the most significant bits of the code
|
|
||||||
// for the EOS symbol (0xFF) MUST be treated as a decoding error.
|
|
||||||
int mask = (1 << symbolBits) - 1;
|
|
||||||
if (symbolBits > 7 || (current & mask) != mask) {
|
|
||||||
throw INVALID_PADDING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Node {
|
private static final class Node {
|
||||||
|
@ -155,7 +81,7 @@ final class HuffmanDecoder {
|
||||||
/**
|
/**
|
||||||
* Construct an internal node
|
* Construct an internal node
|
||||||
*/
|
*/
|
||||||
private Node() {
|
Node() {
|
||||||
symbol = 0;
|
symbol = 0;
|
||||||
bits = 8;
|
bits = 8;
|
||||||
children = new Node[256];
|
children = new Node[256];
|
||||||
|
@ -167,7 +93,7 @@ final class HuffmanDecoder {
|
||||||
* @param symbol the symbol the node represents
|
* @param symbol the symbol the node represents
|
||||||
* @param bits the number of bits matched by this node
|
* @param bits the number of bits matched by this node
|
||||||
*/
|
*/
|
||||||
private Node(int symbol, int bits) {
|
Node(int symbol, int bits) {
|
||||||
assert bits > 0 && bits <= 8;
|
assert bits > 0 && bits <= 8;
|
||||||
this.symbol = symbol;
|
this.symbol = symbol;
|
||||||
this.bits = bits;
|
this.bits = bits;
|
||||||
|
@ -210,4 +136,113 @@ final class HuffmanDecoder {
|
||||||
current.children[i] = terminal;
|
current.children[i] = terminal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class DecoderProcessor implements ByteProcessor {
|
||||||
|
private final int initialCapacity;
|
||||||
|
private byte[] bytes;
|
||||||
|
private int index;
|
||||||
|
private Node node;
|
||||||
|
private int current;
|
||||||
|
private int currentBits;
|
||||||
|
private int symbolBits;
|
||||||
|
|
||||||
|
DecoderProcessor(int initialCapacity) {
|
||||||
|
this.initialCapacity = ObjectUtil.checkPositive(initialCapacity, "initialCapacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
node = ROOT;
|
||||||
|
current = 0;
|
||||||
|
currentBits = 0;
|
||||||
|
symbolBits = 0;
|
||||||
|
bytes = new byte[initialCapacity];
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The idea here is to consume whole bytes at a time rather than individual bits. node
|
||||||
|
* represents the Huffman tree, with all bit patterns denormalized as 256 children. Each
|
||||||
|
* child represents the last 8 bits of the huffman code. The parents of each child each
|
||||||
|
* represent the successive 8 bit chunks that lead up to the last most part. 8 bit bytes
|
||||||
|
* from buf are used to traverse these tree until a terminal node is found.
|
||||||
|
*
|
||||||
|
* current is a bit buffer. The low order bits represent how much of the huffman code has
|
||||||
|
* not been used to traverse the tree. Thus, the high order bits are just garbage.
|
||||||
|
* currentBits represents how many of the low order bits of current are actually valid.
|
||||||
|
* currentBits will vary between 0 and 15.
|
||||||
|
*
|
||||||
|
* symbolBits is the number of bits of the the symbol being decoded, *including* all those
|
||||||
|
* of the parent nodes. symbolBits tells how far down the tree we are. For example, when
|
||||||
|
* decoding the invalid sequence {0xff, 0xff}, currentBits will be 0, but symbolBits will be
|
||||||
|
* 16. This is used to know if buf ended early (before consuming a whole symbol) or if
|
||||||
|
* there is too much padding.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean process(byte value) throws IOException {
|
||||||
|
current = (current << 8) | (value & 0xFF);
|
||||||
|
currentBits += 8;
|
||||||
|
symbolBits += 8;
|
||||||
|
// While there are unconsumed bits in current, keep consuming symbols.
|
||||||
|
do {
|
||||||
|
node = node.children[(current >>> (currentBits - 8)) & 0xFF];
|
||||||
|
currentBits -= node.bits;
|
||||||
|
if (node.isTerminal()) {
|
||||||
|
if (node.symbol == HpackUtil.HUFFMAN_EOS) {
|
||||||
|
throw EOS_DECODED;
|
||||||
|
}
|
||||||
|
append(node.symbol);
|
||||||
|
node = ROOT;
|
||||||
|
// Upon consuming a whole symbol, reset the symbol bits to the number of bits
|
||||||
|
// left over in the byte.
|
||||||
|
symbolBits = currentBits;
|
||||||
|
}
|
||||||
|
} while (currentBits >= 8);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsciiString end() throws IOException {
|
||||||
|
/*
|
||||||
|
* We have consumed all the bytes in buf, but haven't consumed all the symbols. We may be on
|
||||||
|
* a partial symbol, so consume until there is nothing left. This will loop at most 2 times.
|
||||||
|
*/
|
||||||
|
while (currentBits > 0) {
|
||||||
|
node = node.children[(current << (8 - currentBits)) & 0xFF];
|
||||||
|
if (node.isTerminal() && node.bits <= currentBits) {
|
||||||
|
if (node.symbol == HpackUtil.HUFFMAN_EOS) {
|
||||||
|
throw EOS_DECODED;
|
||||||
|
}
|
||||||
|
currentBits -= node.bits;
|
||||||
|
append(node.symbol);
|
||||||
|
node = ROOT;
|
||||||
|
symbolBits = currentBits;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section 5.2. String Literal Representation
|
||||||
|
// A padding strictly longer than 7 bits MUST be treated as a decoding error.
|
||||||
|
// Padding not corresponding to the most significant bits of the code
|
||||||
|
// for the EOS symbol (0xFF) MUST be treated as a decoding error.
|
||||||
|
int mask = (1 << symbolBits) - 1;
|
||||||
|
if (symbolBits > 7 || (current & mask) != mask) {
|
||||||
|
throw INVALID_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AsciiString(bytes, 0, index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(int i) {
|
||||||
|
try {
|
||||||
|
bytes[index] = (byte) i;
|
||||||
|
} catch (IndexOutOfBoundsException ignore) {
|
||||||
|
// Always just expand by INITIAL_SIZE
|
||||||
|
byte[] newBytes = new byte[bytes.length + initialCapacity];
|
||||||
|
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
|
||||||
|
bytes = newBytes;
|
||||||
|
bytes[index] = (byte) i;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,26 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import java.io.IOException;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.io.OutputStream;
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.ByteProcessor;
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODES;
|
||||||
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODE_LENGTHS;
|
||||||
|
|
||||||
final class HuffmanEncoder {
|
final class HuffmanEncoder {
|
||||||
|
|
||||||
private final int[] codes;
|
private final int[] codes;
|
||||||
private final byte[] lengths;
|
private final byte[] lengths;
|
||||||
|
private final EncodedLengthProcessor encodedLengthProcessor = new EncodedLengthProcessor();
|
||||||
|
private final EncodeProcessor encodeProcessor = new EncodeProcessor();
|
||||||
|
|
||||||
|
HuffmanEncoder() {
|
||||||
|
this(HUFFMAN_CODES, HUFFMAN_CODE_LENGTHS);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Huffman encoder with the specified Huffman coding.
|
* Creates a new Huffman encoder with the specified Huffman coding.
|
||||||
|
@ -45,7 +58,7 @@ final class HuffmanEncoder {
|
||||||
* @param codes the Huffman codes indexed by symbol
|
* @param codes the Huffman codes indexed by symbol
|
||||||
* @param lengths the length of each Huffman code
|
* @param lengths the length of each Huffman code
|
||||||
*/
|
*/
|
||||||
HuffmanEncoder(int[] codes, byte[] lengths) {
|
private HuffmanEncoder(int[] codes, byte[] lengths) {
|
||||||
this.codes = codes;
|
this.codes = codes;
|
||||||
this.lengths = lengths;
|
this.lengths = lengths;
|
||||||
}
|
}
|
||||||
|
@ -55,40 +68,30 @@ final class HuffmanEncoder {
|
||||||
*
|
*
|
||||||
* @param out the output stream for the compressed data
|
* @param out the output stream for the compressed data
|
||||||
* @param data the string literal to be Huffman encoded
|
* @param data the string literal to be Huffman encoded
|
||||||
* @throws IOException if an I/O error occurs.
|
|
||||||
* @see HuffmanEncoder#encode(OutputStream, byte[], int, int)
|
|
||||||
*/
|
*/
|
||||||
public void encode(OutputStream out, byte[] data) throws IOException {
|
public void encode(ByteBuf out, CharSequence data) {
|
||||||
encode(out, data, 0, data.length);
|
ObjectUtil.checkNotNull(out, "out");
|
||||||
}
|
if (data instanceof AsciiString) {
|
||||||
|
AsciiString string = (AsciiString) data;
|
||||||
/**
|
try {
|
||||||
* Compresses the input string literal using the Huffman coding.
|
encodeProcessor.out = out;
|
||||||
*
|
string.forEachByte(encodeProcessor);
|
||||||
* @param out the output stream for the compressed data
|
} catch (Exception e) {
|
||||||
* @param data the string literal to be Huffman encoded
|
PlatformDependent.throwException(e);
|
||||||
* @param off the start offset in the data
|
} finally {
|
||||||
* @param len the number of bytes to encode
|
encodeProcessor.end();
|
||||||
* @throws IOException if an I/O error occurs. In particular, an <code>IOException</code> may be
|
}
|
||||||
* thrown if the output stream has been closed.
|
} else {
|
||||||
*/
|
encodeSlowPath(out, data);
|
||||||
public void encode(OutputStream out, byte[] data, int off, int len) throws IOException {
|
}
|
||||||
if (out == null) {
|
|
||||||
throw new NullPointerException("out");
|
|
||||||
} else if (data == null) {
|
|
||||||
throw new NullPointerException("data");
|
|
||||||
} else if (off < 0 || len < 0 || (off + len) < 0 || off > data.length ||
|
|
||||||
(off + len) > data.length) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
} else if (len == 0) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void encodeSlowPath(ByteBuf out, CharSequence data) {
|
||||||
long current = 0;
|
long current = 0;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < data.length(); i++) {
|
||||||
int b = data[off + i] & 0xFF;
|
int b = data.charAt(i) & 0xFF;
|
||||||
int code = codes[b];
|
int code = codes[b];
|
||||||
int nbits = lengths[b];
|
int nbits = lengths[b];
|
||||||
|
|
||||||
|
@ -98,14 +101,14 @@ final class HuffmanEncoder {
|
||||||
|
|
||||||
while (n >= 8) {
|
while (n >= 8) {
|
||||||
n -= 8;
|
n -= 8;
|
||||||
out.write((int) (current >> n));
|
out.writeByte((int) (current >> n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
current <<= 8 - n;
|
current <<= 8 - n;
|
||||||
current |= 0xFF >>> n; // this should be EOS symbol
|
current |= 0xFF >>> n; // this should be EOS symbol
|
||||||
out.write((int) current);
|
out.writeByte((int) current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,14 +118,81 @@ final class HuffmanEncoder {
|
||||||
* @param data the string literal to be Huffman encoded
|
* @param data the string literal to be Huffman encoded
|
||||||
* @return the number of bytes required to Huffman encode <code>data</code>
|
* @return the number of bytes required to Huffman encode <code>data</code>
|
||||||
*/
|
*/
|
||||||
public int getEncodedLength(byte[] data) {
|
public int getEncodedLength(CharSequence data) {
|
||||||
if (data == null) {
|
if (data instanceof AsciiString) {
|
||||||
throw new NullPointerException("data");
|
AsciiString string = (AsciiString) data;
|
||||||
|
try {
|
||||||
|
encodedLengthProcessor.reset();
|
||||||
|
string.forEachByte(encodedLengthProcessor);
|
||||||
|
return encodedLengthProcessor.length();
|
||||||
|
} catch (Exception e) {
|
||||||
|
PlatformDependent.throwException(e);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return getEncodedLengthSlowPath(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getEncodedLengthSlowPath(CharSequence data) {
|
||||||
long len = 0;
|
long len = 0;
|
||||||
for (byte b : data) {
|
for (int i = 0; i < data.length(); i++) {
|
||||||
len += lengths[b & 0xFF];
|
len += lengths[data.charAt(i) & 0xFF];
|
||||||
}
|
}
|
||||||
return (int) ((len + 7) >> 3);
|
return (int) ((len + 7) >> 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class EncodeProcessor implements ByteProcessor {
|
||||||
|
ByteBuf out;
|
||||||
|
private long current;
|
||||||
|
private int n;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(byte value) {
|
||||||
|
int b = value & 0xFF;
|
||||||
|
int nbits = lengths[b];
|
||||||
|
|
||||||
|
current <<= nbits;
|
||||||
|
current |= codes[b];
|
||||||
|
n += nbits;
|
||||||
|
|
||||||
|
while (n >= 8) {
|
||||||
|
n -= 8;
|
||||||
|
out.writeByte((int) (current >> n));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void end() {
|
||||||
|
try {
|
||||||
|
if (n > 0) {
|
||||||
|
current <<= 8 - n;
|
||||||
|
current |= 0xFF >>> n; // this should be EOS symbol
|
||||||
|
out.writeByte((int) current);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
out = null;
|
||||||
|
current = 0;
|
||||||
|
n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class EncodedLengthProcessor implements ByteProcessor {
|
||||||
|
private long len;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(byte value) {
|
||||||
|
len += lengths[value & 0xFF];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int length() {
|
||||||
|
return (int) ((len + 7) >> 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,21 +31,19 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import io.netty.handler.codec.UnsupportedValueConverter;
|
||||||
import java.util.HashMap;
|
import io.netty.handler.codec.http2.CharSequenceMap;
|
||||||
import java.util.List;
|
import io.netty.util.AsciiString;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static io.netty.util.CharsetUtil.ISO_8859_1;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
final class StaticTable {
|
final class StaticTable {
|
||||||
|
|
||||||
private static final String EMPTY = "";
|
|
||||||
|
|
||||||
// Appendix A: Static Table
|
// Appendix A: Static Table
|
||||||
// http://tools.ietf.org/html/rfc7541#appendix-A
|
// http://tools.ietf.org/html/rfc7541#appendix-A
|
||||||
private static final List<HeaderField> STATIC_TABLE = Arrays.asList(
|
private static final List<HeaderField> STATIC_TABLE = Arrays.asList(
|
||||||
/* 1 */ new HeaderField(":authority", EMPTY),
|
/* 1 */ newEmptyHeaderField(":authority"),
|
||||||
/* 2 */ newHeaderField(":method", "GET"),
|
/* 2 */ newHeaderField(":method", "GET"),
|
||||||
/* 3 */ newHeaderField(":method", "POST"),
|
/* 3 */ newHeaderField(":method", "POST"),
|
||||||
/* 4 */ newHeaderField(":path", "/"),
|
/* 4 */ newHeaderField(":path", "/"),
|
||||||
|
@ -59,56 +57,64 @@ final class StaticTable {
|
||||||
/* 12 */ newHeaderField(":status", "400"),
|
/* 12 */ newHeaderField(":status", "400"),
|
||||||
/* 13 */ newHeaderField(":status", "404"),
|
/* 13 */ newHeaderField(":status", "404"),
|
||||||
/* 14 */ newHeaderField(":status", "500"),
|
/* 14 */ newHeaderField(":status", "500"),
|
||||||
/* 15 */ new HeaderField("accept-charset", EMPTY),
|
/* 15 */ newEmptyHeaderField("accept-charset"),
|
||||||
/* 16 */ newHeaderField("accept-encoding", "gzip, deflate"),
|
/* 16 */ newHeaderField("accept-encoding", "gzip, deflate"),
|
||||||
/* 17 */ new HeaderField("accept-language", EMPTY),
|
/* 17 */ newEmptyHeaderField("accept-language"),
|
||||||
/* 18 */ new HeaderField("accept-ranges", EMPTY),
|
/* 18 */ newEmptyHeaderField("accept-ranges"),
|
||||||
/* 19 */ new HeaderField("accept", EMPTY),
|
/* 19 */ newEmptyHeaderField("accept"),
|
||||||
/* 20 */ new HeaderField("access-control-allow-origin", EMPTY),
|
/* 20 */ newEmptyHeaderField("access-control-allow-origin"),
|
||||||
/* 21 */ new HeaderField("age", EMPTY),
|
/* 21 */ newEmptyHeaderField("age"),
|
||||||
/* 22 */ new HeaderField("allow", EMPTY),
|
/* 22 */ newEmptyHeaderField("allow"),
|
||||||
/* 23 */ new HeaderField("authorization", EMPTY),
|
/* 23 */ newEmptyHeaderField("authorization"),
|
||||||
/* 24 */ new HeaderField("cache-control", EMPTY),
|
/* 24 */ newEmptyHeaderField("cache-control"),
|
||||||
/* 25 */ new HeaderField("content-disposition", EMPTY),
|
/* 25 */ newEmptyHeaderField("content-disposition"),
|
||||||
/* 26 */ new HeaderField("content-encoding", EMPTY),
|
/* 26 */ newEmptyHeaderField("content-encoding"),
|
||||||
/* 27 */ new HeaderField("content-language", EMPTY),
|
/* 27 */ newEmptyHeaderField("content-language"),
|
||||||
/* 28 */ new HeaderField("content-length", EMPTY),
|
/* 28 */ newEmptyHeaderField("content-length"),
|
||||||
/* 29 */ new HeaderField("content-location", EMPTY),
|
/* 29 */ newEmptyHeaderField("content-location"),
|
||||||
/* 30 */ new HeaderField("content-range", EMPTY),
|
/* 30 */ newEmptyHeaderField("content-range"),
|
||||||
/* 31 */ new HeaderField("content-type", EMPTY),
|
/* 31 */ newEmptyHeaderField("content-type"),
|
||||||
/* 32 */ new HeaderField("cookie", EMPTY),
|
/* 32 */ newEmptyHeaderField("cookie"),
|
||||||
/* 33 */ new HeaderField("date", EMPTY),
|
/* 33 */ newEmptyHeaderField("date"),
|
||||||
/* 34 */ new HeaderField("etag", EMPTY),
|
/* 34 */ newEmptyHeaderField("etag"),
|
||||||
/* 35 */ new HeaderField("expect", EMPTY),
|
/* 35 */ newEmptyHeaderField("expect"),
|
||||||
/* 36 */ new HeaderField("expires", EMPTY),
|
/* 36 */ newEmptyHeaderField("expires"),
|
||||||
/* 37 */ new HeaderField("from", EMPTY),
|
/* 37 */ newEmptyHeaderField("from"),
|
||||||
/* 38 */ new HeaderField("host", EMPTY),
|
/* 38 */ newEmptyHeaderField("host"),
|
||||||
/* 39 */ new HeaderField("if-match", EMPTY),
|
/* 39 */ newEmptyHeaderField("if-match"),
|
||||||
/* 40 */ new HeaderField("if-modified-since", EMPTY),
|
/* 40 */ newEmptyHeaderField("if-modified-since"),
|
||||||
/* 41 */ new HeaderField("if-none-match", EMPTY),
|
/* 41 */ newEmptyHeaderField("if-none-match"),
|
||||||
/* 42 */ new HeaderField("if-range", EMPTY),
|
/* 42 */ newEmptyHeaderField("if-range"),
|
||||||
/* 43 */ new HeaderField("if-unmodified-since", EMPTY),
|
/* 43 */ newEmptyHeaderField("if-unmodified-since"),
|
||||||
/* 44 */ new HeaderField("last-modified", EMPTY),
|
/* 44 */ newEmptyHeaderField("last-modified"),
|
||||||
/* 45 */ new HeaderField("link", EMPTY),
|
/* 45 */ newEmptyHeaderField("link"),
|
||||||
/* 46 */ new HeaderField("location", EMPTY),
|
/* 46 */ newEmptyHeaderField("location"),
|
||||||
/* 47 */ new HeaderField("max-forwards", EMPTY),
|
/* 47 */ newEmptyHeaderField("max-forwards"),
|
||||||
/* 48 */ new HeaderField("proxy-authenticate", EMPTY),
|
/* 48 */ newEmptyHeaderField("proxy-authenticate"),
|
||||||
/* 49 */ new HeaderField("proxy-authorization", EMPTY),
|
/* 49 */ newEmptyHeaderField("proxy-authorization"),
|
||||||
/* 50 */ new HeaderField("range", EMPTY),
|
/* 50 */ newEmptyHeaderField("range"),
|
||||||
/* 51 */ new HeaderField("referer", EMPTY),
|
/* 51 */ newEmptyHeaderField("referer"),
|
||||||
/* 52 */ new HeaderField("refresh", EMPTY),
|
/* 52 */ newEmptyHeaderField("refresh"),
|
||||||
/* 53 */ new HeaderField("retry-after", EMPTY),
|
/* 53 */ newEmptyHeaderField("retry-after"),
|
||||||
/* 54 */ new HeaderField("server", EMPTY),
|
/* 54 */ newEmptyHeaderField("server"),
|
||||||
/* 55 */ new HeaderField("set-cookie", EMPTY),
|
/* 55 */ newEmptyHeaderField("set-cookie"),
|
||||||
/* 56 */ new HeaderField("strict-transport-security", EMPTY),
|
/* 56 */ newEmptyHeaderField("strict-transport-security"),
|
||||||
/* 57 */ new HeaderField("transfer-encoding", EMPTY),
|
/* 57 */ newEmptyHeaderField("transfer-encoding"),
|
||||||
/* 58 */ new HeaderField("user-agent", EMPTY),
|
/* 58 */ newEmptyHeaderField("user-agent"),
|
||||||
/* 59 */ new HeaderField("vary", EMPTY),
|
/* 59 */ newEmptyHeaderField("vary"),
|
||||||
/* 60 */ new HeaderField("via", EMPTY),
|
/* 60 */ newEmptyHeaderField("via"),
|
||||||
/* 61 */ new HeaderField("www-authenticate", EMPTY)
|
/* 61 */ newEmptyHeaderField("www-authenticate")
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final Map<String, Integer> STATIC_INDEX_BY_NAME = createMap();
|
private static HeaderField newEmptyHeaderField(CharSequence name) {
|
||||||
|
return newHeaderField(name, AsciiString.EMPTY_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HeaderField newHeaderField(CharSequence name, CharSequence value) {
|
||||||
|
return new HeaderField(AsciiString.of(name), AsciiString.of(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final CharSequenceMap<Integer> STATIC_INDEX_BY_NAME = createMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of header fields in the static table.
|
* The number of header fields in the static table.
|
||||||
|
@ -126,9 +132,8 @@ final class StaticTable {
|
||||||
* Returns the lowest index value for the given header field name in the static table. Returns
|
* Returns the lowest index value for the given header field name in the static table. Returns
|
||||||
* -1 if the header field name is not in the static table.
|
* -1 if the header field name is not in the static table.
|
||||||
*/
|
*/
|
||||||
static int getIndex(byte[] name) {
|
static int getIndex(CharSequence name) {
|
||||||
String nameString = new String(name, 0, name.length, ISO_8859_1);
|
Integer index = STATIC_INDEX_BY_NAME.get(name);
|
||||||
Integer index = STATIC_INDEX_BY_NAME.get(nameString);
|
|
||||||
if (index == null) {
|
if (index == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -139,7 +144,7 @@ final class StaticTable {
|
||||||
* Returns the index value for the given header field in the static table. Returns -1 if the
|
* Returns the index value for the given header field in the static table. Returns -1 if the
|
||||||
* header field is not in the static table.
|
* header field is not in the static table.
|
||||||
*/
|
*/
|
||||||
static int getIndex(byte[] name, byte[] value) {
|
static int getIndex(CharSequence name, CharSequence value) {
|
||||||
int index = getIndex(name);
|
int index = getIndex(name);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -160,16 +165,18 @@ final class StaticTable {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a map of header name to index value to allow quick lookup
|
// create a map CharSequenceMap header name to index value to allow quick lookup
|
||||||
private static Map<String, Integer> createMap() {
|
private static CharSequenceMap<Integer> createMap() {
|
||||||
int length = STATIC_TABLE.size();
|
int length = STATIC_TABLE.size();
|
||||||
HashMap<String, Integer> ret = new HashMap<String, Integer>(length);
|
@SuppressWarnings("unchecked")
|
||||||
|
CharSequenceMap<Integer> ret = new CharSequenceMap<Integer>(true,
|
||||||
|
UnsupportedValueConverter.<Integer>instance(), length);
|
||||||
// Iterate through the static table in reverse order to
|
// Iterate through the static table in reverse order to
|
||||||
// save the smallest index for a given name in the map.
|
// save the smallest index for a given name in the map.
|
||||||
for (int index = length; index > 0; index--) {
|
for (int index = length; index > 0; index--) {
|
||||||
HeaderField entry = getEntry(index);
|
HeaderField entry = getEntry(index);
|
||||||
String name = new String(entry.name, 0, entry.name.length, ISO_8859_1);
|
CharSequence name = entry.name;
|
||||||
ret.put(name, index);
|
ret.set(name, index);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="http://tools.ietf.org/html/rfc7541">HPACK: Header Compression for HTTP/2</a>
|
* <a href="http://tools.ietf.org/html/rfc7541">HPACK: Header Compression for HTTP/2</a>.
|
||||||
|
* Please note this implementation is only compliant when used with HTTP/2 and so not meant to be used outside of
|
||||||
|
* this scope.
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
|
@ -22,8 +22,6 @@ import io.netty.util.AsciiString;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_HEADER_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.MAX_HEADER_TABLE_SIZE;
|
||||||
import static io.netty.handler.codec.http2.Http2TestUtil.randomBytes;
|
import static io.netty.handler.codec.http2.Http2TestUtil.randomBytes;
|
||||||
|
@ -73,12 +71,12 @@ public class DefaultHttp2HeadersDecoderTest {
|
||||||
|
|
||||||
private static ByteBuf encode(byte[]... entries) throws Exception {
|
private static ByteBuf encode(byte[]... entries) throws Exception {
|
||||||
Encoder encoder = new Encoder(MAX_HEADER_TABLE_SIZE);
|
Encoder encoder = new Encoder(MAX_HEADER_TABLE_SIZE);
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
ByteBuf out = Unpooled.buffer();
|
||||||
for (int ix = 0; ix < entries.length;) {
|
for (int ix = 0; ix < entries.length;) {
|
||||||
byte[] key = entries[ix++];
|
byte[] key = entries[ix++];
|
||||||
byte[] value = entries[ix++];
|
byte[] value = entries[ix++];
|
||||||
encoder.encodeHeader(stream, key, value, false);
|
encoder.encodeHeader(out, new AsciiString(key, false), new AsciiString(value, false), false);
|
||||||
}
|
}
|
||||||
return Unpooled.wrappedBuffer(stream.toByteArray());
|
return out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,14 +31,16 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static io.netty.util.internal.EmptyArrays.EMPTY_BYTES;
|
import static io.netty.util.AsciiString.EMPTY_STRING;
|
||||||
|
import static io.netty.util.AsciiString.of;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -54,74 +56,70 @@ public class DecoderTest {
|
||||||
private static final int MAX_HEADER_TABLE_SIZE = 4096;
|
private static final int MAX_HEADER_TABLE_SIZE = 4096;
|
||||||
|
|
||||||
private Decoder decoder;
|
private Decoder decoder;
|
||||||
private HeaderListener mockListener;
|
private Http2Headers mockHeaders;
|
||||||
|
|
||||||
private static String hex(String s) {
|
private static String hex(String s) {
|
||||||
return Hex.encodeHexString(s.getBytes());
|
return Hex.encodeHexString(s.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getBytes(String s) {
|
|
||||||
return s.getBytes(CharsetUtil.ISO_8859_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void decode(String encoded) throws IOException {
|
private void decode(String encoded) throws IOException {
|
||||||
byte[] b = Hex.decodeHex(encoded.toCharArray());
|
byte[] b = Hex.decodeHex(encoded.toCharArray());
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
ByteBuf in = Unpooled.wrappedBuffer(b);
|
||||||
try {
|
try {
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
decoder = new Decoder(MAX_HEADER_SIZE, MAX_HEADER_TABLE_SIZE);
|
decoder = new Decoder(MAX_HEADER_SIZE, MAX_HEADER_TABLE_SIZE, 32);
|
||||||
mockListener = mock(HeaderListener.class);
|
mockHeaders = mock(Http2Headers.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiteralHuffmanEncodedWithEmptyNameAndValue() throws IOException {
|
public void testLiteralHuffmanEncodedWithEmptyNameAndValue() throws IOException {
|
||||||
byte[] input = {0, (byte) 0x80, 0};
|
byte[] input = {0, (byte) 0x80, 0};
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(input);
|
ByteBuf in = Unpooled.wrappedBuffer(input);
|
||||||
try {
|
try {
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
verify(mockListener, times(1)).addHeader(EMPTY_BYTES, EMPTY_BYTES, false);
|
verify(mockHeaders, times(1)).add(EMPTY_STRING, EMPTY_STRING);
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testLiteralHuffmanEncodedWithPaddingGreaterThan7Throws() throws IOException {
|
public void testLiteralHuffmanEncodedWithPaddingGreaterThan7Throws() throws IOException {
|
||||||
byte[] input = {0, (byte) 0x81, -1};
|
byte[] input = {0, (byte) 0x81, -1};
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(input);
|
ByteBuf in = Unpooled.wrappedBuffer(input);
|
||||||
try {
|
try {
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testLiteralHuffmanEncodedWithDecodingEOSThrows() throws IOException {
|
public void testLiteralHuffmanEncodedWithDecodingEOSThrows() throws IOException {
|
||||||
byte[] input = {0, (byte) 0x84, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
|
byte[] input = {0, (byte) 0x84, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(input);
|
ByteBuf in = Unpooled.wrappedBuffer(input);
|
||||||
try {
|
try {
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testLiteralHuffmanEncodedWithPaddingNotCorrespondingToMSBThrows() throws IOException {
|
public void testLiteralHuffmanEncodedWithPaddingNotCorrespondingToMSBThrows() throws IOException {
|
||||||
byte[] input = {0, (byte) 0x81, 0};
|
byte[] input = {0, (byte) 0x81, 0};
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(input);
|
ByteBuf in = Unpooled.wrappedBuffer(input);
|
||||||
try {
|
try {
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,14 +127,14 @@ public class DecoderTest {
|
||||||
public void testIncompleteIndex() throws IOException {
|
public void testIncompleteIndex() throws IOException {
|
||||||
// Verify incomplete indices are unread
|
// Verify incomplete indices are unread
|
||||||
byte[] compressed = Hex.decodeHex("FFF0".toCharArray());
|
byte[] compressed = Hex.decodeHex("FFF0".toCharArray());
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(compressed);
|
ByteBuf in = Unpooled.wrappedBuffer(compressed);
|
||||||
try {
|
try {
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
assertEquals(1, in.available());
|
assertEquals(1, in.readableBytes());
|
||||||
decoder.decode(in, mockListener);
|
decoder.decode(in, mockHeaders);
|
||||||
assertEquals(1, in.available());
|
assertEquals(1, in.readableBytes());
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,21 +207,21 @@ public class DecoderTest {
|
||||||
@Test
|
@Test
|
||||||
public void testLiteralWithIncrementalIndexingWithEmptyName() throws Exception {
|
public void testLiteralWithIncrementalIndexingWithEmptyName() throws Exception {
|
||||||
decode("400005" + hex("value"));
|
decode("400005" + hex("value"));
|
||||||
verify(mockListener, times(1)).addHeader(EMPTY_BYTES, getBytes("value"), false);
|
verify(mockHeaders, times(1)).add(EMPTY_STRING, of("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiteralWithIncrementalIndexingCompleteEviction() throws Exception {
|
public void testLiteralWithIncrementalIndexingCompleteEviction() throws Exception {
|
||||||
// Verify indexed host header
|
// Verify indexed host header
|
||||||
decode("4004" + hex("name") + "05" + hex("value"));
|
decode("4004" + hex("name") + "05" + hex("value"));
|
||||||
verify(mockListener).addHeader(getBytes("name"), getBytes("value"), false);
|
verify(mockHeaders).add(of("name"), of("value"));
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
assertFalse(decoder.endHeaderBlock());
|
assertFalse(decoder.endHeaderBlock());
|
||||||
|
|
||||||
reset(mockListener);
|
reset(mockHeaders);
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (int i = 0; i < 4096; i++) {
|
for (int i = 0; i < 4096; i++) {
|
||||||
sb.append("a");
|
sb.append('a');
|
||||||
}
|
}
|
||||||
String value = sb.toString();
|
String value = sb.toString();
|
||||||
sb = new StringBuilder();
|
sb = new StringBuilder();
|
||||||
|
@ -232,14 +230,14 @@ public class DecoderTest {
|
||||||
sb.append("61"); // 'a'
|
sb.append("61"); // 'a'
|
||||||
}
|
}
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verify(mockListener).addHeader(getBytes(":authority"), getBytes(value), false);
|
verify(mockHeaders).add(of(":authority"), of(value));
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
assertFalse(decoder.endHeaderBlock());
|
assertFalse(decoder.endHeaderBlock());
|
||||||
|
|
||||||
// Verify next header is inserted at index 62
|
// Verify next header is inserted at index 62
|
||||||
decode("4004" + hex("name") + "05" + hex("value") + "BE");
|
decode("4004" + hex("name") + "05" + hex("value") + "BE");
|
||||||
verify(mockListener, times(2)).addHeader(getBytes("name"), getBytes("value"), false);
|
verify(mockHeaders, times(2)).add(of("name"), of("value"));
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -252,15 +250,15 @@ public class DecoderTest {
|
||||||
}
|
}
|
||||||
sb.append("00");
|
sb.append("00");
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
|
|
||||||
// Verify header block is reported as truncated
|
// Verify header block is reported as truncated
|
||||||
assertTrue(decoder.endHeaderBlock());
|
assertTrue(decoder.endHeaderBlock());
|
||||||
|
|
||||||
// Verify next header is inserted at index 62
|
// Verify next header is inserted at index 62
|
||||||
decode("4004" + hex("name") + "05" + hex("value") + "BE");
|
decode("4004" + hex("name") + "05" + hex("value") + "BE");
|
||||||
verify(mockListener, times(2)).addHeader(getBytes("name"), getBytes("value"), false);
|
verify(mockHeaders, times(2)).add(of("name"), of("value"));
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -274,21 +272,21 @@ public class DecoderTest {
|
||||||
sb.append("61"); // 'a'
|
sb.append("61"); // 'a'
|
||||||
}
|
}
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
|
|
||||||
// Verify header block is reported as truncated
|
// Verify header block is reported as truncated
|
||||||
assertTrue(decoder.endHeaderBlock());
|
assertTrue(decoder.endHeaderBlock());
|
||||||
|
|
||||||
// Verify next header is inserted at index 62
|
// Verify next header is inserted at index 62
|
||||||
decode("4004" + hex("name") + "05" + hex("value") + "BE");
|
decode("4004" + hex("name") + "05" + hex("value") + "BE");
|
||||||
verify(mockListener, times(2)).addHeader(getBytes("name"), getBytes("value"), false);
|
verify(mockHeaders, times(2)).add(of("name"), of("value"));
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLiteralWithoutIndexingWithEmptyName() throws Exception {
|
public void testLiteralWithoutIndexingWithEmptyName() throws Exception {
|
||||||
decode("000005" + hex("value"));
|
decode("000005" + hex("value"));
|
||||||
verify(mockListener, times(1)).addHeader(EMPTY_BYTES, getBytes("value"), false);
|
verify(mockHeaders, times(1)).add(EMPTY_STRING, of("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
|
@ -301,7 +299,7 @@ public class DecoderTest {
|
||||||
}
|
}
|
||||||
sb.append("00");
|
sb.append("00");
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
|
|
||||||
// Verify header block is reported as truncated
|
// Verify header block is reported as truncated
|
||||||
assertTrue(decoder.endHeaderBlock());
|
assertTrue(decoder.endHeaderBlock());
|
||||||
|
@ -321,7 +319,7 @@ public class DecoderTest {
|
||||||
sb.append("61"); // 'a'
|
sb.append("61"); // 'a'
|
||||||
}
|
}
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
|
|
||||||
// Verify header block is reported as truncated
|
// Verify header block is reported as truncated
|
||||||
assertTrue(decoder.endHeaderBlock());
|
assertTrue(decoder.endHeaderBlock());
|
||||||
|
@ -333,7 +331,7 @@ public class DecoderTest {
|
||||||
@Test
|
@Test
|
||||||
public void testLiteralNeverIndexedWithEmptyName() throws Exception {
|
public void testLiteralNeverIndexedWithEmptyName() throws Exception {
|
||||||
decode("100005" + hex("value"));
|
decode("100005" + hex("value"));
|
||||||
verify(mockListener, times(1)).addHeader(EMPTY_BYTES, getBytes("value"), true);
|
verify(mockHeaders, times(1)).add(EMPTY_STRING, of("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
|
@ -346,7 +344,7 @@ public class DecoderTest {
|
||||||
}
|
}
|
||||||
sb.append("00");
|
sb.append("00");
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
|
|
||||||
// Verify header block is reported as truncated
|
// Verify header block is reported as truncated
|
||||||
assertTrue(decoder.endHeaderBlock());
|
assertTrue(decoder.endHeaderBlock());
|
||||||
|
@ -366,7 +364,7 @@ public class DecoderTest {
|
||||||
sb.append("61"); // 'a'
|
sb.append("61"); // 'a'
|
||||||
}
|
}
|
||||||
decode(sb.toString());
|
decode(sb.toString());
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockHeaders);
|
||||||
|
|
||||||
// Verify header block is reported as truncated
|
// Verify header block is reported as truncated
|
||||||
assertTrue(decoder.endHeaderBlock());
|
assertTrue(decoder.endHeaderBlock());
|
||||||
|
|
|
@ -31,11 +31,12 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -44,7 +45,6 @@ public class HuffmanTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHuffman() throws IOException {
|
public void testHuffman() throws IOException {
|
||||||
|
|
||||||
String s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
String s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
for (int i = 0; i < s.length(); i++) {
|
for (int i = 0; i < s.length(); i++) {
|
||||||
roundTrip(s.substring(0, i));
|
roundTrip(s.substring(0, i));
|
||||||
|
@ -62,59 +62,59 @@ public class HuffmanTest {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
buf[i] = (byte) 0xFF;
|
buf[i] = (byte) 0xFF;
|
||||||
}
|
}
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeIllegalPadding() throws IOException {
|
public void testDecodeIllegalPadding() throws IOException {
|
||||||
byte[] buf = new byte[1];
|
byte[] buf = new byte[1];
|
||||||
buf[0] = 0x00; // '0', invalid padding
|
buf[0] = 0x00; // '0', invalid padding
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeExtraPadding() throws IOException {
|
public void testDecodeExtraPadding() throws IOException {
|
||||||
byte[] buf = makeBuf(0x0f, 0xFF); // '1', 'EOS'
|
byte[] buf = makeBuf(0x0f, 0xFF); // '1', 'EOS'
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeExtraPadding1byte() throws IOException {
|
public void testDecodeExtraPadding1byte() throws IOException {
|
||||||
byte[] buf = makeBuf(0xFF);
|
byte[] buf = makeBuf(0xFF);
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeExtraPadding2byte() throws IOException {
|
public void testDecodeExtraPadding2byte() throws IOException {
|
||||||
byte[] buf = makeBuf(0x1F, 0xFF); // 'a'
|
byte[] buf = makeBuf(0x1F, 0xFF); // 'a'
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeExtraPadding3byte() throws IOException {
|
public void testDecodeExtraPadding3byte() throws IOException {
|
||||||
byte[] buf = makeBuf(0x1F, 0xFF, 0xFF); // 'a'
|
byte[] buf = makeBuf(0x1F, 0xFF, 0xFF); // 'a'
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeExtraPadding4byte() throws IOException {
|
public void testDecodeExtraPadding4byte() throws IOException {
|
||||||
byte[] buf = makeBuf(0x1F, 0xFF, 0xFF, 0xFF); // 'a'
|
byte[] buf = makeBuf(0x1F, 0xFF, 0xFF, 0xFF); // 'a'
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodeExtraPadding29bit() throws IOException {
|
public void testDecodeExtraPadding29bit() throws IOException {
|
||||||
byte[] buf = makeBuf(0xFF, 0x9F, 0xFF, 0xFF, 0xFF); // '|'
|
byte[] buf = makeBuf(0xFF, 0x9F, 0xFF, 0xFF, 0xFF); // '|'
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
public void testDecodePartialSymbol() throws IOException {
|
public void testDecodePartialSymbol() throws IOException {
|
||||||
byte[] buf = makeBuf(0x52, 0xBC, 0x30, 0xFF, 0xFF, 0xFF, 0xFF); // " pFA\x00", 31 bits of padding, a.k.a. EOS
|
byte[] buf = makeBuf(0x52, 0xBC, 0x30, 0xFF, 0xFF, 0xFF, 0xFF); // " pFA\x00", 31 bits of padding, a.k.a. EOS
|
||||||
Huffman.DECODER.decode(buf);
|
decode(newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] makeBuf(int ... bytes) {
|
private static byte[] makeBuf(int ... bytes) {
|
||||||
byte[] buf = new byte[bytes.length];
|
byte[] buf = new byte[bytes.length];
|
||||||
for (int i = 0; i < buf.length; i++) {
|
for (int i = 0; i < buf.length; i++) {
|
||||||
buf[i] = (byte) bytes[i];
|
buf[i] = (byte) bytes[i];
|
||||||
|
@ -122,8 +122,8 @@ public class HuffmanTest {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void roundTrip(String s) throws IOException {
|
private static void roundTrip(String s) throws IOException {
|
||||||
roundTrip(Huffman.ENCODER, Huffman.DECODER, s);
|
roundTrip(new HuffmanEncoder(), newHuffmanDecoder(), s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void roundTrip(HuffmanEncoder encoder, HuffmanDecoder decoder, String s)
|
private static void roundTrip(HuffmanEncoder encoder, HuffmanDecoder decoder, String s)
|
||||||
|
@ -131,19 +131,38 @@ public class HuffmanTest {
|
||||||
roundTrip(encoder, decoder, s.getBytes());
|
roundTrip(encoder, decoder, s.getBytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void roundTrip(byte[] buf) throws IOException {
|
private static void roundTrip(byte[] buf) throws IOException {
|
||||||
roundTrip(Huffman.ENCODER, Huffman.DECODER, buf);
|
roundTrip(new HuffmanEncoder(), newHuffmanDecoder(), buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void roundTrip(HuffmanEncoder encoder, HuffmanDecoder decoder, byte[] buf)
|
private static void roundTrip(HuffmanEncoder encoder, HuffmanDecoder decoder, byte[] buf)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteBuf buffer = Unpooled.buffer();
|
||||||
DataOutputStream dos = new DataOutputStream(baos);
|
try {
|
||||||
|
encoder.encode(buffer, new AsciiString(buf, false));
|
||||||
|
byte[] bytes = new byte[buffer.readableBytes()];
|
||||||
|
buffer.readBytes(bytes);
|
||||||
|
|
||||||
encoder.encode(dos, buf);
|
byte[] actualBytes = decode(decoder, bytes);
|
||||||
|
|
||||||
byte[] actualBytes = decoder.decode(baos.toByteArray());
|
|
||||||
|
|
||||||
Assert.assertTrue(Arrays.equals(buf, actualBytes));
|
Assert.assertTrue(Arrays.equals(buf, actualBytes));
|
||||||
|
} finally {
|
||||||
|
buffer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] decode(HuffmanDecoder decoder, byte[] bytes) throws IOException {
|
||||||
|
ByteBuf buffer = Unpooled.wrappedBuffer(bytes);
|
||||||
|
try {
|
||||||
|
AsciiString decoded = decoder.decode(buffer, buffer.readableBytes());
|
||||||
|
Assert.assertFalse(buffer.isReadable());
|
||||||
|
return decoded.toByteArray();
|
||||||
|
} finally {
|
||||||
|
buffer.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HuffmanDecoder newHuffmanDecoder() {
|
||||||
|
return new HuffmanDecoder(32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,10 @@ import com.google.gson.JsonDeserializer;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -169,31 +170,39 @@ final class TestCase {
|
||||||
maxHeaderTableSize = Integer.MAX_VALUE;
|
maxHeaderTableSize = Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Decoder(8192, maxHeaderTableSize);
|
return new Decoder(8192, maxHeaderTableSize, 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] encode(Encoder encoder, List<HeaderField> headers, int maxHeaderTableSize,
|
private static byte[] encode(Encoder encoder, List<HeaderField> headers, int maxHeaderTableSize,
|
||||||
boolean sensitive)
|
boolean sensitive) {
|
||||||
throws IOException {
|
ByteBuf buffer = Unpooled.buffer();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
try {
|
||||||
|
|
||||||
if (maxHeaderTableSize != -1) {
|
if (maxHeaderTableSize != -1) {
|
||||||
encoder.setMaxHeaderTableSize(baos, maxHeaderTableSize);
|
encoder.setMaxHeaderTableSize(buffer, maxHeaderTableSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (HeaderField e : headers) {
|
for (HeaderField e : headers) {
|
||||||
encoder.encodeHeader(baos, e.name, e.value, sensitive);
|
encoder.encodeHeader(buffer, AsciiString.of(e.name), AsciiString.of(e.value), sensitive);
|
||||||
|
}
|
||||||
|
byte[] bytes = new byte[buffer.readableBytes()];
|
||||||
|
buffer.readBytes(bytes);
|
||||||
|
return bytes;
|
||||||
|
} finally {
|
||||||
|
buffer.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<HeaderField> decode(Decoder decoder, byte[] expected) throws IOException {
|
private static List<HeaderField> decode(Decoder decoder, byte[] expected) throws IOException {
|
||||||
|
ByteBuf in = Unpooled.wrappedBuffer(expected);
|
||||||
|
try {
|
||||||
List<HeaderField> headers = new ArrayList<HeaderField>();
|
List<HeaderField> headers = new ArrayList<HeaderField>();
|
||||||
TestHeaderListener listener = new TestHeaderListener(headers);
|
TestHeaderListener listener = new TestHeaderListener(headers);
|
||||||
decoder.decode(new ByteArrayInputStream(expected), listener);
|
decoder.decode(in, listener);
|
||||||
decoder.endHeaderBlock();
|
decoder.endHeaderBlock();
|
||||||
return headers;
|
return headers;
|
||||||
|
} finally {
|
||||||
|
in.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String concat(List<String> l) {
|
private static String concat(List<String> l) {
|
||||||
|
@ -237,8 +246,7 @@ final class TestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderField deserialize(JsonElement json, Type typeOfT,
|
public HeaderField deserialize(JsonElement json, Type typeOfT,
|
||||||
JsonDeserializationContext context)
|
JsonDeserializationContext context) {
|
||||||
throws JsonParseException {
|
|
||||||
JsonObject jsonObject = json.getAsJsonObject();
|
JsonObject jsonObject = json.getAsJsonObject();
|
||||||
Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
|
Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
|
||||||
if (entrySet.size() != 1) {
|
if (entrySet.size() != 1) {
|
||||||
|
|
|
@ -31,9 +31,11 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.http2.internal.hpack;
|
package io.netty.handler.codec.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
final class TestHeaderListener implements HeaderListener {
|
final class TestHeaderListener extends DefaultHttp2Headers {
|
||||||
|
|
||||||
private final List<HeaderField> headers;
|
private final List<HeaderField> headers;
|
||||||
|
|
||||||
|
@ -42,7 +44,8 @@ final class TestHeaderListener implements HeaderListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addHeader(byte[] name, byte[] value, boolean sensitive) {
|
public TestHeaderListener add(CharSequence name, CharSequence value) {
|
||||||
headers.add(new HeaderField(name, value));
|
headers.add(new HeaderField(name, value));
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,9 +31,12 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.microbench.http2.internal.hpack;
|
package io.netty.microbench.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.handler.codec.http2.DefaultHttp2Headers;
|
||||||
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.Decoder;
|
import io.netty.handler.codec.http2.internal.hpack.Decoder;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.Encoder;
|
import io.netty.handler.codec.http2.internal.hpack.Encoder;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.HeaderListener;
|
|
||||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||||
import org.openjdk.jmh.annotations.Benchmark;
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
@ -41,10 +44,9 @@ import org.openjdk.jmh.annotations.Level;
|
||||||
import org.openjdk.jmh.annotations.Mode;
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
import org.openjdk.jmh.annotations.Param;
|
import org.openjdk.jmh.annotations.Param;
|
||||||
import org.openjdk.jmh.annotations.Setup;
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.TearDown;
|
||||||
import org.openjdk.jmh.infra.Blackhole;
|
import org.openjdk.jmh.infra.Blackhole;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -65,35 +67,49 @@ public class DecoderBenchmark extends AbstractMicrobenchmark {
|
||||||
@Param({ "true", "false" })
|
@Param({ "true", "false" })
|
||||||
public boolean limitToAscii;
|
public boolean limitToAscii;
|
||||||
|
|
||||||
private byte[] input;
|
private ByteBuf input;
|
||||||
|
|
||||||
@Setup(Level.Trial)
|
@Setup(Level.Trial)
|
||||||
public void setup() throws IOException {
|
public void setup() throws IOException {
|
||||||
input = getSerializedHeaders(Util.headers(size, limitToAscii), sensitive);
|
input = Unpooled.wrappedBuffer(getSerializedHeaders(Util.headers(size, limitToAscii), sensitive));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TearDown(Level.Trial)
|
||||||
|
public void teardown() throws IOException {
|
||||||
|
input.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.Throughput)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
public void decode(final Blackhole bh) throws IOException {
|
public void decode(final Blackhole bh) throws IOException {
|
||||||
Decoder decoder = new Decoder(maxHeaderSize, maxTableSize);
|
Decoder decoder = new Decoder(maxHeaderSize, maxTableSize, 32);
|
||||||
decoder.decode(new ByteArrayInputStream(input), new HeaderListener() {
|
@SuppressWarnings("unchecked")
|
||||||
|
Http2Headers headers =
|
||||||
|
new DefaultHttp2Headers() {
|
||||||
@Override
|
@Override
|
||||||
public void addHeader(byte[] name, byte[] value, boolean sensitive) {
|
public Http2Headers add(CharSequence name, CharSequence value) {
|
||||||
bh.consume(sensitive);
|
bh.consume(sensitive);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
decoder.decode(input.duplicate(), headers);
|
||||||
decoder.endHeaderBlock();
|
decoder.endHeaderBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getSerializedHeaders(List<Header> headers, boolean sensitive)
|
private byte[] getSerializedHeaders(List<Header> headers, boolean sensitive) {
|
||||||
throws IOException {
|
|
||||||
Encoder encoder = new Encoder(4096);
|
Encoder encoder = new Encoder(4096);
|
||||||
|
|
||||||
ByteArrayOutputStream outputStream = size.newOutputStream();
|
ByteBuf out = size.newOutBuffer();
|
||||||
|
try {
|
||||||
for (int i = 0; i < headers.size(); ++i) {
|
for (int i = 0; i < headers.size(); ++i) {
|
||||||
Header header = headers.get(i);
|
Header header = headers.get(i);
|
||||||
encoder.encodeHeader(outputStream, header.name, header.value, sensitive);
|
encoder.encodeHeader(out, header.name, header.value, sensitive);
|
||||||
|
}
|
||||||
|
byte[] bytes = new byte[out.readableBytes()];
|
||||||
|
out.readBytes(bytes);
|
||||||
|
return bytes;
|
||||||
|
} finally {
|
||||||
|
out.release();
|
||||||
}
|
}
|
||||||
return outputStream.toByteArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.microbench.http2.internal.hpack;
|
package io.netty.microbench.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.handler.codec.http2.internal.hpack.Encoder;
|
import io.netty.handler.codec.http2.internal.hpack.Encoder;
|
||||||
import io.netty.microbench.util.AbstractMicrobenchmark;
|
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||||
import org.openjdk.jmh.annotations.Benchmark;
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
@ -39,9 +40,9 @@ import org.openjdk.jmh.annotations.Level;
|
||||||
import org.openjdk.jmh.annotations.Mode;
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
import org.openjdk.jmh.annotations.Param;
|
import org.openjdk.jmh.annotations.Param;
|
||||||
import org.openjdk.jmh.annotations.Setup;
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.TearDown;
|
||||||
import org.openjdk.jmh.infra.Blackhole;
|
import org.openjdk.jmh.infra.Blackhole;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -63,31 +64,36 @@ public class EncoderBenchmark extends AbstractMicrobenchmark {
|
||||||
public boolean limitToAscii;
|
public boolean limitToAscii;
|
||||||
|
|
||||||
private List<Header> headers;
|
private List<Header> headers;
|
||||||
private ByteArrayOutputStream outputStream;
|
private ByteBuf output;
|
||||||
|
|
||||||
@Setup(Level.Trial)
|
@Setup(Level.Trial)
|
||||||
public void setup() {
|
public void setup() {
|
||||||
headers = Util.headers(size, limitToAscii);
|
headers = Util.headers(size, limitToAscii);
|
||||||
outputStream = size.newOutputStream();
|
output = size.newOutBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TearDown(Level.Trial)
|
||||||
|
public void tearDown() {
|
||||||
|
output.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
@BenchmarkMode(Mode.Throughput)
|
@BenchmarkMode(Mode.Throughput)
|
||||||
public void encode(Blackhole bh) throws IOException {
|
public void encode(Blackhole bh) throws IOException {
|
||||||
Encoder encoder = new Encoder(maxTableSize);
|
Encoder encoder = new Encoder(maxTableSize);
|
||||||
outputStream.reset();
|
output.clear();
|
||||||
if (duplicates) {
|
if (duplicates) {
|
||||||
// If duplicates is set, re-add the same header each time.
|
// If duplicates is set, re-add the same header each time.
|
||||||
Header header = headers.get(0);
|
Header header = headers.get(0);
|
||||||
for (int i = 0; i < headers.size(); ++i) {
|
for (int i = 0; i < headers.size(); ++i) {
|
||||||
encoder.encodeHeader(outputStream, header.name, header.value, sensitive);
|
encoder.encodeHeader(output, header.name, header.value, sensitive);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < headers.size(); ++i) {
|
for (int i = 0; i < headers.size(); ++i) {
|
||||||
Header header = headers.get(i);
|
Header header = headers.get(i);
|
||||||
encoder.encodeHeader(outputStream, header.name, header.value, sensitive);
|
encoder.encodeHeader(output, header.name, header.value, sensitive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bh.consume(outputStream);
|
bh.consume(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.microbench.http2.internal.hpack;
|
package io.netty.microbench.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -42,12 +44,12 @@ class Header {
|
||||||
private static final String ALPHABET =
|
private static final String ALPHABET =
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
||||||
|
|
||||||
final byte[] name;
|
final CharSequence name;
|
||||||
final byte[] value;
|
final CharSequence value;
|
||||||
|
|
||||||
Header(byte[] name, byte[] value) {
|
Header(byte[] name, byte[] value) {
|
||||||
this.name = name;
|
this.name = new AsciiString(name, false);
|
||||||
this.value = value;
|
this.value = new AsciiString(value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,6 +31,9 @@
|
||||||
*/
|
*/
|
||||||
package io.netty.microbench.http2.internal.hpack;
|
package io.netty.microbench.http2.internal.hpack;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -56,7 +59,7 @@ public enum HeadersSize {
|
||||||
return Header.createHeaders(numHeaders, nameLength, valueLength, limitAscii);
|
return Header.createHeaders(numHeaders, nameLength, valueLength, limitAscii);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteArrayOutputStream newOutputStream() {
|
public ByteBuf newOutBuffer() {
|
||||||
return new ByteArrayOutputStream(numHeaders * (nameLength + valueLength));
|
return Unpooled.buffer(numHeaders * (nameLength + valueLength));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user