< prev index next >
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/hpack/Decoder.java
Print this page
@@ -22,24 +22,26 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.incubator.http.internal.hpack;
+import jdk.incubator.http.internal.hpack.HPACK.Logger;
import jdk.internal.vm.annotation.Stable;
import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.net.ProtocolException;
import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicLong;
+import static jdk.incubator.http.internal.hpack.HPACK.Logger.Level.EXTRA;
+import static jdk.incubator.http.internal.hpack.HPACK.Logger.Level.NORMAL;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
/**
* Decodes headers from their binary representation.
*
- * <p>Typical lifecycle looks like this:
+ * <p> Typical lifecycle looks like this:
*
* <p> {@link #Decoder(int) new Decoder}
* ({@link #setMaxCapacity(int) setMaxCapacity}?
* {@link #decode(ByteBuffer, boolean, DecodingCallback) decode})*
*
@@ -60,10 +62,13 @@
*
* @since 9
*/
public final class Decoder {
+ private final Logger logger;
+ private static final AtomicLong DECODERS_IDS = new AtomicLong();
+
@Stable
private static final State[] states = new State[256];
static {
// To be able to do a quick lookup, each of 256 possibilities are mapped
@@ -90,10 +95,11 @@
throw new InternalError(String.valueOf(i));
}
}
}
+ private final long id;
private final HeaderTable table;
private State state = State.READY;
private final IntegerReader integerReader;
private final StringReader stringReader;
@@ -109,56 +115,78 @@
/**
* Constructs a {@code Decoder} with the specified initial capacity of the
* header table.
*
* <p> The value has to be agreed between decoder and encoder out-of-band,
- * e.g. by a protocol that uses HPACK (see <a
- * href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table
- * Size</a>).
+ * e.g. by a protocol that uses HPACK
+ * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
*
* @param capacity
* a non-negative integer
*
* @throws IllegalArgumentException
* if capacity is negative
*/
public Decoder(int capacity) {
- setMaxCapacity(capacity);
- table = new HeaderTable(capacity);
+ id = DECODERS_IDS.incrementAndGet();
+ logger = HPACK.getLogger().subLogger("Decoder#" + id);
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("new decoder with maximum table size %s",
+ capacity));
+ }
+ if (logger.isLoggable(NORMAL)) {
+ /* To correlate with logging outside HPACK, knowing
+ hashCode/toString is important */
+ logger.log(NORMAL, () -> {
+ String hashCode = Integer.toHexString(
+ System.identityHashCode(this));
+ return format("toString='%s', identityHashCode=%s",
+ toString(), hashCode);
+ });
+ }
+ setMaxCapacity0(capacity);
+ table = new HeaderTable(capacity, logger.subLogger("HeaderTable"));
integerReader = new IntegerReader();
stringReader = new StringReader();
name = new StringBuilder(512);
value = new StringBuilder(1024);
}
/**
* Sets a maximum capacity of the header table.
*
* <p> The value has to be agreed between decoder and encoder out-of-band,
- * e.g. by a protocol that uses HPACK (see <a
- * href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table
- * Size</a>).
+ * e.g. by a protocol that uses HPACK
+ * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
*
* @param capacity
* a non-negative integer
*
* @throws IllegalArgumentException
* if capacity is negative
*/
public void setMaxCapacity(int capacity) {
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("setting maximum table size to %s",
+ capacity));
+ }
+ setMaxCapacity0(capacity);
+ }
+
+ private void setMaxCapacity0(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity >= 0: " + capacity);
}
// FIXME: await capacity update if less than what was prior to it
this.capacity = capacity;
}
/**
* Decodes a header block from the given buffer to the given callback.
*
- * <p> Suppose a header block is represented by a sequence of {@code
- * ByteBuffer}s in the form of {@code Iterator<ByteBuffer>}. And the
+ * <p> Suppose a header block is represented by a sequence of
+ * {@code ByteBuffer}s in the form of {@code Iterator<ByteBuffer>}. And the
* consumer of decoded headers is represented by the callback. Then to
* decode the header block, the following approach might be used:
*
* <pre>{@code
* while (buffers.hasNext()) {
@@ -172,11 +200,11 @@
* position to reflect the bytes read. The buffer's mark and limit will not
* be modified.
*
* <p> Once the method is invoked with {@code endOfHeaderBlock == true}, the
* current header block is deemed ended, and inconsistencies, if any, are
- * reported immediately by throwing an {@code UncheckedIOException}.
+ * reported immediately by throwing an {@code IOException}.
*
* <p> Each callback method is called only after the implementation has
* processed the corresponding bytes. If the bytes revealed a decoding
* error, the callback method is not called.
*
@@ -198,29 +226,36 @@
* @param endOfHeaderBlock
* true if the chunk is the final (or the only one) in the sequence
*
* @param consumer
* the callback
- * @throws UncheckedIOException
+ * @throws IOException
* in case of a decoding error
* @throws NullPointerException
* if either headerBlock or consumer are null
*/
- public void decode(ByteBuffer headerBlock, boolean endOfHeaderBlock,
- DecodingCallback consumer) {
+ public void decode(ByteBuffer headerBlock,
+ boolean endOfHeaderBlock,
+ DecodingCallback consumer) throws IOException {
requireNonNull(headerBlock, "headerBlock");
requireNonNull(consumer, "consumer");
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("reading %s, end of header block? %s",
+ headerBlock, endOfHeaderBlock));
+ }
while (headerBlock.hasRemaining()) {
proceed(headerBlock, consumer);
}
if (endOfHeaderBlock && state != State.READY) {
- throw new UncheckedIOException(
- new ProtocolException("Unexpected end of header block"));
+ logger.log(NORMAL, () -> format("unexpected end of %s representation",
+ state));
+ throw new IOException("Unexpected end of header block");
}
}
- private void proceed(ByteBuffer input, DecodingCallback action) {
+ private void proceed(ByteBuffer input, DecodingCallback action)
+ throws IOException {
switch (state) {
case READY:
resumeReady(input);
break;
case INDEXED:
@@ -237,18 +272,21 @@
break;
case SIZE_UPDATE:
resumeSizeUpdate(input, action);
break;
default:
- throw new InternalError(
- "Unexpected decoder state: " + String.valueOf(state));
+ throw new InternalError("Unexpected decoder state: " + state);
}
}
private void resumeReady(ByteBuffer input) {
int b = input.get(input.position()) & 0xff; // absolute read
State s = states[b];
+ if (logger.isLoggable(EXTRA)) {
+ logger.log(EXTRA, () -> format("next binary representation %s (first byte 0x%02x)",
+ s, b));
+ }
switch (s) {
case INDEXED:
integerReader.configure(7);
state = State.INDEXED;
firstValueIndex = true;
@@ -290,24 +328,40 @@
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 1 | Index (7+) |
// +---+---------------------------+
//
- private void resumeIndexed(ByteBuffer input, DecodingCallback action) {
+ private void resumeIndexed(ByteBuffer input, DecodingCallback action)
+ throws IOException {
if (!integerReader.read(input)) {
return;
}
intValue = integerReader.get();
integerReader.reset();
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("indexed %s", intValue));
+ }
try {
- HeaderTable.HeaderField f = table.get(intValue);
+ HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
action.onIndexed(intValue, f.name, f.value);
} finally {
state = State.READY;
}
}
+ private HeaderTable.HeaderField getHeaderFieldAt(int index)
+ throws IOException
+ {
+ HeaderTable.HeaderField f;
+ try {
+ f = table.get(index);
+ } catch (IndexOutOfBoundsException e) {
+ throw new IOException("header fields table index", e);
+ }
+ return f;
+ }
+
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 0 | 0 | 0 | 0 | Index (4+) |
// +---+---+-----------------------+
// | H | Value Length (7+) |
@@ -326,19 +380,28 @@
// | H | Value Length (7+) |
// +---+---------------------------+
// | Value String (Length octets) |
// +-------------------------------+
//
- private void resumeLiteral(ByteBuffer input, DecodingCallback action) {
+ private void resumeLiteral(ByteBuffer input, DecodingCallback action)
+ throws IOException {
if (!completeReading(input)) {
return;
}
try {
if (firstValueIndex) {
- HeaderTable.HeaderField f = table.get(intValue);
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
+ intValue, value));
+ }
+ HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
action.onLiteral(intValue, f.name, value, valueHuffmanEncoded);
} else {
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
+ name, value));
+ }
action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
}
} finally {
cleanUpAfterReading();
}
@@ -365,11 +428,13 @@
// | H | Value Length (7+) |
// +---+---------------------------+
// | Value String (Length octets) |
// +-------------------------------+
//
- private void resumeLiteralWithIndexing(ByteBuffer input, DecodingCallback action) {
+ private void resumeLiteralWithIndexing(ByteBuffer input,
+ DecodingCallback action)
+ throws IOException {
if (!completeReading(input)) {
return;
}
try {
//
@@ -379,21 +444,26 @@
// Let's create those string beforehand (and only once!) to benefit everyone
//
String n;
String v = value.toString();
if (firstValueIndex) {
- HeaderTable.HeaderField f = table.get(intValue);
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
+ intValue, value));
+ }
+ HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
n = f.name;
action.onLiteralWithIndexing(intValue, n, v, valueHuffmanEncoded);
} else {
n = name.toString();
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
+ n, value));
+ }
action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded);
}
table.put(n, v);
- } catch (IllegalArgumentException | IllegalStateException e) {
- throw new UncheckedIOException(
- (IOException) new ProtocolException().initCause(e));
} finally {
cleanUpAfterReading();
}
}
@@ -417,19 +487,29 @@
// | H | Value Length (7+) |
// +---+---------------------------+
// | Value String (Length octets) |
// +-------------------------------+
//
- private void resumeLiteralNeverIndexed(ByteBuffer input, DecodingCallback action) {
+ private void resumeLiteralNeverIndexed(ByteBuffer input,
+ DecodingCallback action)
+ throws IOException {
if (!completeReading(input)) {
return;
}
try {
if (firstValueIndex) {
- HeaderTable.HeaderField f = table.get(intValue);
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
+ intValue, value));
+ }
+ HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded);
} else {
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
+ name, value));
+ }
action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
}
} finally {
cleanUpAfterReading();
}
@@ -438,31 +518,36 @@
// 0 1 2 3 4 5 6 7
// +---+---+---+---+---+---+---+---+
// | 0 | 0 | 1 | Max size (5+) |
// +---+---------------------------+
//
- private void resumeSizeUpdate(ByteBuffer input, DecodingCallback action) {
+ private void resumeSizeUpdate(ByteBuffer input,
+ DecodingCallback action) throws IOException {
if (!integerReader.read(input)) {
return;
}
intValue = integerReader.get();
+ if (logger.isLoggable(NORMAL)) {
+ logger.log(NORMAL, () -> format("dynamic table size update %s",
+ intValue));
+ }
assert intValue >= 0;
if (intValue > capacity) {
- throw new UncheckedIOException(new ProtocolException(
- format("Received capacity exceeds expected: " +
- "capacity=%s, expected=%s", intValue, capacity)));
+ throw new IOException(
+ format("Received capacity exceeds expected: capacity=%s, expected=%s",
+ intValue, capacity));
}
integerReader.reset();
try {
action.onSizeUpdate(intValue);
table.setMaxSize(intValue);
} finally {
state = State.READY;
}
}
- private boolean completeReading(ByteBuffer input) {
+ private boolean completeReading(ByteBuffer input) throws IOException {
if (!firstValueRead) {
if (firstValueIndex) {
if (!integerReader.read(input)) {
return false;
}
< prev index next >