--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/BinaryRepresentationWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+
+interface BinaryRepresentationWriter {
+
+ boolean write(HeaderTable table, ByteBuffer destination);
+
+ BinaryRepresentationWriter reset();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/BulkSizeUpdateWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+
+import static java.util.Objects.requireNonNull;
+
+final class BulkSizeUpdateWriter implements BinaryRepresentationWriter {
+
+ private final SizeUpdateWriter writer = new SizeUpdateWriter();
+ private Iterator<Integer> maxSizes;
+ private boolean writing;
+ private boolean configured;
+
+ BulkSizeUpdateWriter maxHeaderTableSizes(Iterable<Integer> sizes) {
+ if (configured) {
+ throw new IllegalStateException("Already configured");
+ }
+ requireNonNull(sizes, "sizes");
+ maxSizes = sizes.iterator();
+ configured = true;
+ return this;
+ }
+
+ @Override
+ public boolean write(HeaderTable table, ByteBuffer destination) {
+ if (!configured) {
+ throw new IllegalStateException("Configure first");
+ }
+ while (true) {
+ if (writing) {
+ if (!writer.write(table, destination)) {
+ return false;
+ }
+ writing = false;
+ } else if (maxSizes.hasNext()) {
+ writing = true;
+ writer.reset();
+ writer.maxHeaderTableSize(maxSizes.next());
+ } else {
+ configured = false;
+ return true;
+ }
+ }
+ }
+
+ @Override
+ public BulkSizeUpdateWriter reset() {
+ maxSizes = null;
+ writing = false;
+ configured = false;
+ return this;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/Decoder.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+
+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> {@link #Decoder(int) new Decoder}
+ * ({@link #setMaxCapacity(int) setMaxCapacity}?
+ * {@link #decode(ByteBuffer, boolean, DecodingCallback) decode})*
+ *
+ * @apiNote
+ *
+ * <p> The design intentions behind Decoder were to facilitate flexible and
+ * incremental style of processing.
+ *
+ * <p> {@code Decoder} does not require a complete header block in a single
+ * {@code ByteBuffer}. The header block can be spread across many buffers of any
+ * size and decoded one-by-one the way it makes most sense for the user. This
+ * way also allows not to limit the size of the header block.
+ *
+ * <p> Headers are delivered to the {@linkplain DecodingCallback callback} as
+ * soon as they become decoded. Using the callback also gives the user a freedom
+ * to decide how headers are processed. The callback does not limit the number
+ * of headers decoded during single decoding operation.
+ *
+ * @since 9
+ */
+public final class Decoder {
+
+ private static final State[] states = new State[256];
+
+ static {
+ // To be able to do a quick lookup, each of 256 possibilities are mapped
+ // to corresponding states.
+ //
+ // We can safely do this since patterns 1, 01, 001, 0001, 0000 are
+ // Huffman prefixes and therefore are inherently not ambiguous.
+ //
+ // I do it mainly for better debugging (to not go each time step by step
+ // through if...else tree). As for performance win for the decoding, I
+ // believe is negligible.
+ for (int i = 0; i < states.length; i++) {
+ if ((i & 0b1000_0000) == 0b1000_0000) {
+ states[i] = State.INDEXED;
+ } else if ((i & 0b1100_0000) == 0b0100_0000) {
+ states[i] = State.LITERAL_WITH_INDEXING;
+ } else if ((i & 0b1110_0000) == 0b0010_0000) {
+ states[i] = State.SIZE_UPDATE;
+ } else if ((i & 0b1111_0000) == 0b0001_0000) {
+ states[i] = State.LITERAL_NEVER_INDEXED;
+ } else if ((i & 0b1111_0000) == 0b0000_0000) {
+ states[i] = State.LITERAL;
+ } else {
+ throw new InternalError(String.valueOf(i));
+ }
+ }
+ }
+
+ private final HeaderTable table;
+
+ private State state = State.READY;
+ private final IntegerReader integerReader;
+ private final StringReader stringReader;
+ private final StringBuilder name;
+ private final StringBuilder value;
+ private int intValue;
+ private boolean firstValueRead;
+ private boolean firstValueIndex;
+ private boolean nameHuffmanEncoded;
+ private boolean valueHuffmanEncoded;
+ private int capacity;
+
+ /**
+ * 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>).
+ *
+ * @param capacity
+ * a non-negative integer
+ *
+ * @throws IllegalArgumentException
+ * if capacity is negative
+ */
+ public Decoder(int capacity) {
+ setMaxCapacity(capacity);
+ table = new HeaderTable(capacity);
+ 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>).
+ *
+ * @param capacity
+ * a non-negative integer
+ *
+ * @throws IllegalArgumentException
+ * if capacity is negative
+ */
+ public void setMaxCapacity(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
+ * 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()) {
+ * ByteBuffer input = buffers.next();
+ * decoder.decode(input, callback, !buffers.hasNext());
+ * }
+ * }</pre>
+ *
+ * <p> The decoder reads as much as possible of the header block from the
+ * given buffer, starting at the buffer's position, and increments its
+ * 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}.
+ *
+ * <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.
+ *
+ * <p> In addition to exceptions thrown directly by the method, any
+ * exceptions thrown from the {@code callback} will bubble up.
+ *
+ * @apiNote The method asks for {@code endOfHeaderBlock} flag instead of
+ * returning it for two reasons. The first one is that the user of the
+ * decoder always knows which chunk is the last. The second one is to throw
+ * the most detailed exception possible, which might be useful for
+ * diagnosing issues.
+ *
+ * @implNote This implementation is not atomic in respect to decoding
+ * errors. In other words, if the decoding operation has thrown a decoding
+ * error, the decoder is no longer usable.
+ *
+ * @param headerBlock
+ * the chunk of the header block, may be empty
+ * @param endOfHeaderBlock
+ * true if the chunk is the final (or the only one) in the sequence
+ *
+ * @param consumer
+ * the callback
+ * @throws UncheckedIOException
+ * in case of a decoding error
+ * @throws NullPointerException
+ * if either headerBlock or consumer are null
+ */
+ public void decode(ByteBuffer headerBlock, boolean endOfHeaderBlock,
+ DecodingCallback consumer) {
+ requireNonNull(headerBlock, "headerBlock");
+ requireNonNull(consumer, "consumer");
+ while (headerBlock.hasRemaining()) {
+ proceed(headerBlock, consumer);
+ }
+ if (endOfHeaderBlock && state != State.READY) {
+ throw new UncheckedIOException(
+ new ProtocolException("Unexpected end of header block"));
+ }
+ }
+
+ private void proceed(ByteBuffer input, DecodingCallback action) {
+ switch (state) {
+ case READY:
+ resumeReady(input);
+ break;
+ case INDEXED:
+ resumeIndexed(input, action);
+ break;
+ case LITERAL:
+ resumeLiteral(input, action);
+ break;
+ case LITERAL_WITH_INDEXING:
+ resumeLiteralWithIndexing(input, action);
+ break;
+ case LITERAL_NEVER_INDEXED:
+ resumeLiteralNeverIndexed(input, action);
+ break;
+ case SIZE_UPDATE:
+ resumeSizeUpdate(input, action);
+ break;
+ default:
+ throw new InternalError(
+ "Unexpected decoder state: " + String.valueOf(state));
+ }
+ }
+
+ private void resumeReady(ByteBuffer input) {
+ int b = input.get(input.position()) & 0xff; // absolute read
+ State s = states[b];
+ switch (s) {
+ case INDEXED:
+ integerReader.configure(7);
+ state = State.INDEXED;
+ firstValueIndex = true;
+ break;
+ case LITERAL:
+ state = State.LITERAL;
+ firstValueIndex = (b & 0b0000_1111) != 0;
+ if (firstValueIndex) {
+ integerReader.configure(4);
+ }
+ break;
+ case LITERAL_WITH_INDEXING:
+ state = State.LITERAL_WITH_INDEXING;
+ firstValueIndex = (b & 0b0011_1111) != 0;
+ if (firstValueIndex) {
+ integerReader.configure(6);
+ }
+ break;
+ case LITERAL_NEVER_INDEXED:
+ state = State.LITERAL_NEVER_INDEXED;
+ firstValueIndex = (b & 0b0000_1111) != 0;
+ if (firstValueIndex) {
+ integerReader.configure(4);
+ }
+ break;
+ case SIZE_UPDATE:
+ integerReader.configure(5);
+ state = State.SIZE_UPDATE;
+ firstValueIndex = true;
+ break;
+ default:
+ throw new InternalError(String.valueOf(s));
+ }
+ if (!firstValueIndex) {
+ input.get(); // advance, next stop: "String Literal"
+ }
+ }
+
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 1 | Index (7+) |
+ // +---+---------------------------+
+ //
+ private void resumeIndexed(ByteBuffer input, DecodingCallback action) {
+ if (!integerReader.read(input)) {
+ return;
+ }
+ intValue = integerReader.get();
+ integerReader.reset();
+ try {
+ HeaderTable.HeaderField f = table.get(intValue);
+ action.onIndexed(intValue, f.name, f.value);
+ } finally {
+ state = State.READY;
+ }
+ }
+
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 0 | Index (4+) |
+ // +---+---+-----------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+ //
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 0 | 0 |
+ // +---+---+-----------------------+
+ // | H | Name Length (7+) |
+ // +---+---------------------------+
+ // | Name String (Length octets) |
+ // +---+---------------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+ //
+ private void resumeLiteral(ByteBuffer input, DecodingCallback action) {
+ if (!completeReading(input)) {
+ return;
+ }
+ try {
+ if (firstValueIndex) {
+ HeaderTable.HeaderField f = table.get(intValue);
+ action.onLiteral(intValue, f.name, value, valueHuffmanEncoded);
+ } else {
+ action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
+ }
+ } finally {
+ cleanUpAfterReading();
+ }
+ }
+
+ //
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 1 | Index (6+) |
+ // +---+---+-----------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+ //
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 1 | 0 |
+ // +---+---+-----------------------+
+ // | H | Name Length (7+) |
+ // +---+---------------------------+
+ // | Name String (Length octets) |
+ // +---+---------------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+ //
+ private void resumeLiteralWithIndexing(ByteBuffer input, DecodingCallback action) {
+ if (!completeReading(input)) {
+ return;
+ }
+ try {
+ //
+ // 1. (name, value) will be stored in the table as strings
+ // 2. Most likely the callback will also create strings from them
+ // ------------------------------------------------------------------------
+ // 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);
+ n = f.name;
+ action.onLiteralWithIndexing(intValue, n, v, valueHuffmanEncoded);
+ } else {
+ n = name.toString();
+ action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded);
+ }
+ table.put(n, v);
+ } catch (IllegalArgumentException | IllegalStateException e) {
+ throw new UncheckedIOException(
+ (IOException) new ProtocolException().initCause(e));
+ } finally {
+ cleanUpAfterReading();
+ }
+ }
+
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 1 | Index (4+) |
+ // +---+---+-----------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+ //
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 0 | 1 | 0 |
+ // +---+---+-----------------------+
+ // | H | Name Length (7+) |
+ // +---+---------------------------+
+ // | Name String (Length octets) |
+ // +---+---------------------------+
+ // | H | Value Length (7+) |
+ // +---+---------------------------+
+ // | Value String (Length octets) |
+ // +-------------------------------+
+ //
+ private void resumeLiteralNeverIndexed(ByteBuffer input, DecodingCallback action) {
+ if (!completeReading(input)) {
+ return;
+ }
+ try {
+ if (firstValueIndex) {
+ HeaderTable.HeaderField f = table.get(intValue);
+ action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded);
+ } else {
+ action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
+ }
+ } finally {
+ cleanUpAfterReading();
+ }
+ }
+
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | 0 | 0 | 1 | Max size (5+) |
+ // +---+---------------------------+
+ //
+ private void resumeSizeUpdate(ByteBuffer input, DecodingCallback action) {
+ if (!integerReader.read(input)) {
+ return;
+ }
+ intValue = integerReader.get();
+ assert intValue >= 0;
+ if (intValue > capacity) {
+ throw new UncheckedIOException(new ProtocolException(
+ 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) {
+ if (!firstValueRead) {
+ if (firstValueIndex) {
+ if (!integerReader.read(input)) {
+ return false;
+ }
+ intValue = integerReader.get();
+ integerReader.reset();
+ } else {
+ if (!stringReader.read(input, name)) {
+ return false;
+ }
+ nameHuffmanEncoded = stringReader.isHuffmanEncoded();
+ stringReader.reset();
+ }
+ firstValueRead = true;
+ return false;
+ } else {
+ if (!stringReader.read(input, value)) {
+ return false;
+ }
+ }
+ valueHuffmanEncoded = stringReader.isHuffmanEncoded();
+ stringReader.reset();
+ return true;
+ }
+
+ private void cleanUpAfterReading() {
+ name.setLength(0);
+ value.setLength(0);
+ firstValueRead = false;
+ state = State.READY;
+ }
+
+ private enum State {
+ READY,
+ INDEXED,
+ LITERAL_NEVER_INDEXED,
+ LITERAL,
+ LITERAL_WITH_INDEXING,
+ SIZE_UPDATE
+ }
+
+ HeaderTable getTable() {
+ return table;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/DecodingCallback.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Delivers results of the {@link Decoder#decode(ByteBuffer, boolean,
+ * DecodingCallback) decoding operation}.
+ *
+ * <p> Methods of the callback are never called by a decoder with any of the
+ * arguments being {@code null}.
+ *
+ * @apiNote
+ *
+ * <p> The callback provides methods for all possible <a
+ * href="https://tools.ietf.org/html/rfc7541#section-6">binary
+ * representations</a>. This could be useful for implementing an intermediary,
+ * logging, debugging, etc.
+ *
+ * <p> The callback is an interface in order to interoperate with lambdas (in
+ * the most common use case):
+ * <pre>{@code
+ * DecodingCallback callback = (name, value) -> System.out.println(name + ", " + value);
+ * }</pre>
+ *
+ * <p> Names and values are {@link CharSequence}s rather than {@link String}s in
+ * order to allow users to decide whether or not they need to create objects. A
+ * {@code CharSequence} might be used in-place, for example, to be appended to
+ * an {@link Appendable} (e.g. {@link StringBuilder}) and then discarded.
+ *
+ * <p> That said, if a passed {@code CharSequence} needs to outlast the method
+ * call, it needs to be copied.
+ *
+ * @since 9
+ */
+@FunctionalInterface
+public interface DecodingCallback {
+
+ /**
+ * A method the more specific methods of the callback forward their calls
+ * to.
+ *
+ * @param name
+ * header name
+ * @param value
+ * header value
+ */
+ void onDecoded(CharSequence name, CharSequence value);
+
+ /**
+ * A more finer-grained version of {@link #onDecoded(CharSequence,
+ * CharSequence)} that also reports on value sensitivity.
+ *
+ * <p> Value sensitivity must be considered, for example, when implementing
+ * an intermediary. A {@code value} is sensitive if it was represented as <a
+ * href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal Header
+ * Field Never Indexed</a>.
+ *
+ * <p> It is required that intermediaries MUST use the {@linkplain
+ * Encoder#header(CharSequence, CharSequence, boolean) same representation}
+ * for encoding this header field in order to protect its value which is not
+ * to be put at risk by compressing it.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes {@code onDecoded(name, value)}.
+ *
+ * @param name
+ * header name
+ * @param value
+ * header value
+ * @param sensitive
+ * whether or not the value is sensitive
+ *
+ * @see #onLiteralNeverIndexed(int, CharSequence, CharSequence, boolean)
+ * @see #onLiteralNeverIndexed(CharSequence, boolean, CharSequence, boolean)
+ */
+ default void onDecoded(CharSequence name, CharSequence value,
+ boolean sensitive) {
+ onDecoded(name, value);
+ }
+
+ /**
+ * An <a href="https://tools.ietf.org/html/rfc7541#section-6.1">Indexed
+ * Header Field</a> decoded.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, false)}.
+ *
+ * @param index
+ * index of an entry in the table
+ * @param name
+ * header name
+ * @param value
+ * header value
+ */
+ default void onIndexed(int index, CharSequence name, CharSequence value) {
+ onDecoded(name, value, false);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.2">Literal
+ * Header Field without Indexing</a> decoded, where a {@code name} was
+ * referred by an {@code index}.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, false)}.
+ *
+ * @param index
+ * index of an entry in the table
+ * @param name
+ * header name
+ * @param value
+ * header value
+ * @param valueHuffman
+ * if the {@code value} was Huffman encoded
+ */
+ default void onLiteral(int index, CharSequence name,
+ CharSequence value, boolean valueHuffman) {
+ onDecoded(name, value, false);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.2">Literal
+ * Header Field without Indexing</a> decoded, where both a {@code name} and
+ * a {@code value} were literal.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, false)}.
+ *
+ * @param name
+ * header name
+ * @param nameHuffman
+ * if the {@code name} was Huffman encoded
+ * @param value
+ * header value
+ * @param valueHuffman
+ * if the {@code value} was Huffman encoded
+ */
+ default void onLiteral(CharSequence name, boolean nameHuffman,
+ CharSequence value, boolean valueHuffman) {
+ onDecoded(name, value, false);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal
+ * Header Field Never Indexed</a> decoded, where a {@code name}
+ * was referred by an {@code index}.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, true)}.
+ *
+ * @param index
+ * index of an entry in the table
+ * @param name
+ * header name
+ * @param value
+ * header value
+ * @param valueHuffman
+ * if the {@code value} was Huffman encoded
+ */
+ default void onLiteralNeverIndexed(int index, CharSequence name,
+ CharSequence value,
+ boolean valueHuffman) {
+ onDecoded(name, value, true);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.3">Literal
+ * Header Field Never Indexed</a> decoded, where both a {@code
+ * name} and a {@code value} were literal.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, true)}.
+ *
+ * @param name
+ * header name
+ * @param nameHuffman
+ * if the {@code name} was Huffman encoded
+ * @param value
+ * header value
+ * @param valueHuffman
+ * if the {@code value} was Huffman encoded
+ */
+ default void onLiteralNeverIndexed(CharSequence name, boolean nameHuffman,
+ CharSequence value, boolean valueHuffman) {
+ onDecoded(name, value, true);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.1">Literal
+ * Header Field with Incremental Indexing</a> decoded, where a {@code name}
+ * was referred by an {@code index}.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, false)}.
+ *
+ * @param index
+ * index of an entry in the table
+ * @param name
+ * header name
+ * @param value
+ * header value
+ * @param valueHuffman
+ * if the {@code value} was Huffman encoded
+ */
+ default void onLiteralWithIndexing(int index,
+ CharSequence name,
+ CharSequence value, boolean valueHuffman) {
+ onDecoded(name, value, false);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.2.1">Literal
+ * Header Field with Incremental Indexing</a> decoded, where both a {@code
+ * name} and a {@code value} were literal.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation invokes
+ * {@code onDecoded(name, value, false)}.
+ *
+ * @param name
+ * header name
+ * @param nameHuffman
+ * if the {@code name} was Huffman encoded
+ * @param value
+ * header value
+ * @param valueHuffman
+ * if the {@code value} was Huffman encoded
+ */
+ default void onLiteralWithIndexing(CharSequence name, boolean nameHuffman,
+ CharSequence value, boolean valueHuffman) {
+ onDecoded(name, value, false);
+ }
+
+ /**
+ * A <a href="https://tools.ietf.org/html/rfc7541#section-6.3">Dynamic Table
+ * Size Update</a> decoded.
+ *
+ * @implSpec
+ *
+ * <p> The default implementation does nothing.
+ *
+ * @param capacity
+ * new capacity of the header table
+ */
+ default void onSizeUpdate(int capacity) { }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/Encoder.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
+import java.util.LinkedList;
+import java.util.List;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Encodes headers to their binary representation.
+ *
+ * <p>Typical lifecycle looks like this:
+ *
+ * <p> {@link #Encoder(int) new Encoder}
+ * ({@link #setMaxCapacity(int) setMaxCapacity}?
+ * {@link #encode(ByteBuffer) encode})*
+ *
+ * <p> Suppose headers are represented by {@code Map<String, List<String>>}. A
+ * supplier and a consumer of {@link ByteBuffer}s in forms of {@code
+ * Supplier<ByteBuffer>} and {@code Consumer<ByteBuffer>} respectively. Then to
+ * encode headers, the following approach might be used:
+ *
+ * <pre>{@code
+ * for (Map.Entry<String, List<String>> h : headers.entrySet()) {
+ * String name = h.getKey();
+ * for (String value : h.getValue()) {
+ * encoder.header(name, value); // Set up header
+ * boolean encoded;
+ * do {
+ * ByteBuffer b = buffersSupplier.get();
+ * encoded = encoder.encode(b); // Encode the header
+ * buffersConsumer.accept(b);
+ * } while (!encoded);
+ * }
+ * }
+ * }</pre>
+ *
+ * <p> Though the specification <a
+ * href="https://tools.ietf.org/html/rfc7541#section-2"> does not define</a> how
+ * an encoder is to be implemented, a default implementation is provided by the
+ * method {@link #header(CharSequence, CharSequence, boolean)}.
+ *
+ * <p> To provide a custom encoding implementation, {@code Encoder} has to be
+ * extended. A subclass then can access methods for encoding using specific
+ * representations (e.g. {@link #literal(int, CharSequence, boolean) literal},
+ * {@link #indexed(int) indexed}, etc.)
+ *
+ * @apiNote
+ *
+ * <p> An Encoder provides an incremental way of encoding headers.
+ * {@link #encode(ByteBuffer)} takes a buffer a returns a boolean indicating
+ * whether, or not, the buffer was sufficiently sized to hold the
+ * remaining of the encoded representation.
+ *
+ * <p> This way, there's no need to provide a buffer of a specific size, or to
+ * resize (and copy) the buffer on demand, when the remaining encoded
+ * representation will not fit in the buffer's remaining space. Instead, an
+ * array of existing buffers can be used, prepended with a frame that encloses
+ * the resulting header block afterwards.
+ *
+ * <p> Splitting the encoding operation into header set up and header encoding,
+ * separates long lived arguments ({@code name}, {@code value}, {@code
+ * sensitivity}, etc.) from the short lived ones (e.g. {@code buffer}),
+ * simplifying each operation itself.
+ *
+ * @implNote
+ *
+ * <p> The default implementation does not use dynamic table. It reports to a
+ * coupled Decoder a size update with the value of {@code 0}, and never changes
+ * it afterwards.
+ *
+ * @since 9
+ */
+public class Encoder {
+
+ // TODO: enum: no huffman/smart huffman/always huffman
+ private static final boolean DEFAULT_HUFFMAN = true;
+
+ private final IndexedWriter indexedWriter = new IndexedWriter();
+ private final LiteralWriter literalWriter = new LiteralWriter();
+ private final LiteralNeverIndexedWriter literalNeverIndexedWriter
+ = new LiteralNeverIndexedWriter();
+ private final LiteralWithIndexingWriter literalWithIndexingWriter
+ = new LiteralWithIndexingWriter();
+ private final SizeUpdateWriter sizeUpdateWriter = new SizeUpdateWriter();
+ private final BulkSizeUpdateWriter bulkSizeUpdateWriter
+ = new BulkSizeUpdateWriter();
+
+ private BinaryRepresentationWriter writer;
+ private final HeaderTable headerTable;
+
+ private boolean encoding;
+
+ private int maxCapacity;
+ private int currCapacity;
+ private int lastCapacity;
+ private long minCapacity;
+ private boolean capacityUpdate;
+ private boolean configuredCapacityUpdate;
+
+ /**
+ * Constructs an {@code Encoder} with the specified 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>).
+ *
+ * @param maxCapacity
+ * a non-negative integer
+ *
+ * @throws IllegalArgumentException
+ * if maxCapacity is negative
+ */
+ public Encoder(int maxCapacity) {
+ if (maxCapacity < 0) {
+ throw new IllegalArgumentException("maxCapacity >= 0: " + maxCapacity);
+ }
+ // Initial maximum capacity update mechanics
+ minCapacity = Long.MAX_VALUE;
+ currCapacity = -1;
+ setMaxCapacity(maxCapacity);
+ headerTable = new HeaderTable(lastCapacity);
+ }
+
+ /**
+ * Sets up the given header {@code (name, value)}.
+ *
+ * <p> Fixates {@code name} and {@code value} for the duration of encoding.
+ *
+ * @param name
+ * the name
+ * @param value
+ * the value
+ *
+ * @throws NullPointerException
+ * if any of the arguments are {@code null}
+ * @throws IllegalStateException
+ * if the encoder hasn't fully encoded the previous header, or
+ * hasn't yet started to encode it
+ * @see #header(CharSequence, CharSequence, boolean)
+ */
+ public void header(CharSequence name, CharSequence value)
+ throws IllegalStateException {
+ header(name, value, false);
+ }
+
+ /**
+ * Sets up the given header {@code (name, value)} with possibly sensitive
+ * value.
+ *
+ * <p> Fixates {@code name} and {@code value} for the duration of encoding.
+ *
+ * @param name
+ * the name
+ * @param value
+ * the value
+ * @param sensitive
+ * whether or not the value is sensitive
+ *
+ * @throws NullPointerException
+ * if any of the arguments are {@code null}
+ * @throws IllegalStateException
+ * if the encoder hasn't fully encoded the previous header, or
+ * hasn't yet started to encode it
+ * @see #header(CharSequence, CharSequence)
+ * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean)
+ */
+ public void header(CharSequence name, CharSequence value,
+ boolean sensitive) throws IllegalStateException {
+ // Arguably a good balance between complexity of implementation and
+ // efficiency of encoding
+ requireNonNull(name, "name");
+ requireNonNull(value, "value");
+ HeaderTable t = getHeaderTable();
+ int index = t.indexOf(name, value);
+ if (index > 0) {
+ indexed(index);
+ } else if (index < 0) {
+ if (sensitive) {
+ literalNeverIndexed(-index, value, DEFAULT_HUFFMAN);
+ } else {
+ literal(-index, value, DEFAULT_HUFFMAN);
+ }
+ } else {
+ if (sensitive) {
+ literalNeverIndexed(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
+ } else {
+ literal(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
+ }
+ }
+ }
+
+ /**
+ * 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>).
+ *
+ * <p> May be called any number of times after or before a complete header
+ * has been encoded.
+ *
+ * <p> If the encoder decides to change the actual capacity, an update will
+ * be encoded before a new encoding operation starts.
+ *
+ * @param capacity
+ * a non-negative integer
+ *
+ * @throws IllegalArgumentException
+ * if capacity is negative
+ * @throws IllegalStateException
+ * if the encoder hasn't fully encoded the previous header, or
+ * hasn't yet started to encode it
+ */
+ public void setMaxCapacity(int capacity) {
+ checkEncoding();
+ if (capacity < 0) {
+ throw new IllegalArgumentException("capacity >= 0: " + capacity);
+ }
+ int calculated = calculateCapacity(capacity);
+ if (calculated < 0 || calculated > capacity) {
+ throw new IllegalArgumentException(
+ format("0 <= calculated <= capacity: calculated=%s, capacity=%s",
+ calculated, capacity));
+ }
+ capacityUpdate = true;
+ // maxCapacity needs to be updated unconditionally, so the encoder
+ // always has the newest one (in case it decides to update it later
+ // unsolicitedly)
+ // Suppose maxCapacity = 4096, and the encoder has decided to use only
+ // 2048. It later can choose anything else from the region [0, 4096].
+ maxCapacity = capacity;
+ lastCapacity = calculated;
+ minCapacity = Math.min(minCapacity, lastCapacity);
+ }
+
+ protected int calculateCapacity(int maxCapacity) {
+ // Default implementation of the Encoder won't add anything to the
+ // table, therefore no need for a table space
+ return 0;
+ }
+
+ /**
+ * Encodes the {@linkplain #header(CharSequence, CharSequence) set up}
+ * header into the given buffer.
+ *
+ * <p> The encoder writes as much as possible of the header's binary
+ * representation into the given buffer, starting at the buffer's position,
+ * and increments its position to reflect the bytes written. The buffer's
+ * mark and limit will not be modified.
+ *
+ * <p> Once the method has returned {@code true}, the current header is
+ * deemed encoded. A new header may be set up.
+ *
+ * @param headerBlock
+ * the buffer to encode the header into, may be empty
+ *
+ * @return {@code true} if the current header has been fully encoded,
+ * {@code false} otherwise
+ *
+ * @throws NullPointerException
+ * if the buffer is {@code null}
+ * @throws ReadOnlyBufferException
+ * if this buffer is read-only
+ * @throws IllegalStateException
+ * if there is no set up header
+ */
+ public final boolean encode(ByteBuffer headerBlock) {
+ if (!encoding) {
+ throw new IllegalStateException("A header hasn't been set up");
+ }
+ if (!prependWithCapacityUpdate(headerBlock)) {
+ return false;
+ }
+ boolean done = writer.write(headerTable, headerBlock);
+ if (done) {
+ writer.reset(); // FIXME: WHY?
+ encoding = false;
+ }
+ return done;
+ }
+
+ private boolean prependWithCapacityUpdate(ByteBuffer headerBlock) {
+ if (capacityUpdate) {
+ if (!configuredCapacityUpdate) {
+ List<Integer> sizes = new LinkedList<>();
+ if (minCapacity < currCapacity) {
+ sizes.add((int) minCapacity);
+ if (minCapacity != lastCapacity) {
+ sizes.add(lastCapacity);
+ }
+ } else if (lastCapacity != currCapacity) {
+ sizes.add(lastCapacity);
+ }
+ bulkSizeUpdateWriter.maxHeaderTableSizes(sizes);
+ configuredCapacityUpdate = true;
+ }
+ boolean done = bulkSizeUpdateWriter.write(headerTable, headerBlock);
+ if (done) {
+ minCapacity = lastCapacity;
+ currCapacity = lastCapacity;
+ bulkSizeUpdateWriter.reset();
+ capacityUpdate = false;
+ configuredCapacityUpdate = false;
+ }
+ return done;
+ }
+ return true;
+ }
+
+ protected final void indexed(int index) throws IndexOutOfBoundsException {
+ checkEncoding();
+ encoding = true;
+ writer = indexedWriter.index(index);
+ }
+
+ protected final void literal(int index, CharSequence value,
+ boolean useHuffman)
+ throws IndexOutOfBoundsException {
+ checkEncoding();
+ encoding = true;
+ writer = literalWriter
+ .index(index).value(value, useHuffman);
+ }
+
+ protected final void literal(CharSequence name, boolean nameHuffman,
+ CharSequence value, boolean valueHuffman) {
+ checkEncoding();
+ encoding = true;
+ writer = literalWriter
+ .name(name, nameHuffman).value(value, valueHuffman);
+ }
+
+ protected final void literalNeverIndexed(int index,
+ CharSequence value,
+ boolean valueHuffman)
+ throws IndexOutOfBoundsException {
+ checkEncoding();
+ encoding = true;
+ writer = literalNeverIndexedWriter
+ .index(index).value(value, valueHuffman);
+ }
+
+ protected final void literalNeverIndexed(CharSequence name,
+ boolean nameHuffman,
+ CharSequence value,
+ boolean valueHuffman) {
+ checkEncoding();
+ encoding = true;
+ writer = literalNeverIndexedWriter
+ .name(name, nameHuffman).value(value, valueHuffman);
+ }
+
+ protected final void literalWithIndexing(int index,
+ CharSequence value,
+ boolean valueHuffman)
+ throws IndexOutOfBoundsException {
+ checkEncoding();
+ encoding = true;
+ writer = literalWithIndexingWriter
+ .index(index).value(value, valueHuffman);
+ }
+
+ protected final void literalWithIndexing(CharSequence name,
+ boolean nameHuffman,
+ CharSequence value,
+ boolean valueHuffman) {
+ checkEncoding();
+ encoding = true;
+ writer = literalWithIndexingWriter
+ .name(name, nameHuffman).value(value, valueHuffman);
+ }
+
+ protected final void sizeUpdate(int capacity)
+ throws IllegalArgumentException {
+ checkEncoding();
+ // Ensure subclass follows the contract
+ if (capacity > this.maxCapacity) {
+ throw new IllegalArgumentException(
+ format("capacity <= maxCapacity: capacity=%s, maxCapacity=%s",
+ capacity, maxCapacity));
+ }
+ writer = sizeUpdateWriter.maxHeaderTableSize(capacity);
+ }
+
+ protected final int getMaxCapacity() {
+ return maxCapacity;
+ }
+
+ protected final HeaderTable getHeaderTable() {
+ return headerTable;
+ }
+
+ protected final void checkEncoding() {
+ if (encoding) {
+ throw new IllegalStateException(
+ "Previous encoding operation hasn't finished yet");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/HeaderTable.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import static java.lang.String.format;
+
+//
+// Header Table combined from two tables: static and dynamic.
+//
+// There is a single address space for index values. Index-aware methods
+// correspond to the table as a whole. Size-aware methods only to the dynamic
+// part of it.
+//
+final class HeaderTable {
+
+ private static final HeaderField[] staticTable = {
+ null, // To make index 1-based, instead of 0-based
+ new HeaderField(":authority"),
+ new HeaderField(":method", "GET"),
+ new HeaderField(":method", "POST"),
+ new HeaderField(":path", "/"),
+ new HeaderField(":path", "/index.html"),
+ new HeaderField(":scheme", "http"),
+ new HeaderField(":scheme", "https"),
+ new HeaderField(":status", "200"),
+ new HeaderField(":status", "204"),
+ new HeaderField(":status", "206"),
+ new HeaderField(":status", "304"),
+ new HeaderField(":status", "400"),
+ new HeaderField(":status", "404"),
+ new HeaderField(":status", "500"),
+ new HeaderField("accept-charset"),
+ new HeaderField("accept-encoding", "gzip, deflate"),
+ new HeaderField("accept-language"),
+ new HeaderField("accept-ranges"),
+ new HeaderField("accept"),
+ new HeaderField("access-control-allow-origin"),
+ new HeaderField("age"),
+ new HeaderField("allow"),
+ new HeaderField("authorization"),
+ new HeaderField("cache-control"),
+ new HeaderField("content-disposition"),
+ new HeaderField("content-encoding"),
+ new HeaderField("content-language"),
+ new HeaderField("content-length"),
+ new HeaderField("content-location"),
+ new HeaderField("content-range"),
+ new HeaderField("content-type"),
+ new HeaderField("cookie"),
+ new HeaderField("date"),
+ new HeaderField("etag"),
+ new HeaderField("expect"),
+ new HeaderField("expires"),
+ new HeaderField("from"),
+ new HeaderField("host"),
+ new HeaderField("if-match"),
+ new HeaderField("if-modified-since"),
+ new HeaderField("if-none-match"),
+ new HeaderField("if-range"),
+ new HeaderField("if-unmodified-since"),
+ new HeaderField("last-modified"),
+ new HeaderField("link"),
+ new HeaderField("location"),
+ new HeaderField("max-forwards"),
+ new HeaderField("proxy-authenticate"),
+ new HeaderField("proxy-authorization"),
+ new HeaderField("range"),
+ new HeaderField("referer"),
+ new HeaderField("refresh"),
+ new HeaderField("retry-after"),
+ new HeaderField("server"),
+ new HeaderField("set-cookie"),
+ new HeaderField("strict-transport-security"),
+ new HeaderField("transfer-encoding"),
+ new HeaderField("user-agent"),
+ new HeaderField("vary"),
+ new HeaderField("via"),
+ new HeaderField("www-authenticate")
+ };
+
+ private static final int STATIC_TABLE_LENGTH = staticTable.length - 1;
+ private static final int ENTRY_SIZE = 32;
+ private static final Map<String, LinkedHashMap<String, Integer>> staticIndexes;
+
+ static {
+ staticIndexes = new HashMap<>(STATIC_TABLE_LENGTH);
+ for (int i = 1; i <= STATIC_TABLE_LENGTH; i++) {
+ HeaderField f = staticTable[i];
+ Map<String, Integer> values = staticIndexes
+ .computeIfAbsent(f.name, k -> new LinkedHashMap<>());
+ values.put(f.value, i);
+ }
+ }
+
+ private final Table dynamicTable = new Table(0);
+ private int maxSize;
+ private int size;
+
+ public HeaderTable(int maxSize) {
+ setMaxSize(maxSize);
+ }
+
+ //
+ // The method returns:
+ //
+ // * a positive integer i where i (i = [1..Integer.MAX_VALUE]) is an
+ // index of an entry with a header (n, v), where n.equals(name) &&
+ // v.equals(value)
+ //
+ // * a negative integer j where j (j = [-Integer.MAX_VALUE..-1]) is an
+ // index of an entry with a header (n, v), where n.equals(name)
+ //
+ // * 0 if there's no entry e such that e.getName().equals(name)
+ //
+ // The rationale behind this design is to allow to pack more useful data
+ // into a single invocation, facilitating a single pass where possible
+ // (the idea is the same as in java.util.Arrays.binarySearch(int[], int)).
+ //
+ public int indexOf(CharSequence name, CharSequence value) {
+ // Invoking toString() will possibly allocate Strings for the sake of
+ // the search, which doesn't feel right.
+ String n = name.toString();
+ String v = value.toString();
+
+ // 1. Try exact match in the static region
+ Map<String, Integer> values = staticIndexes.get(n);
+ if (values != null) {
+ Integer idx = values.get(v);
+ if (idx != null) {
+ return idx;
+ }
+ }
+ // 2. Try exact match in the dynamic region
+ int didx = dynamicTable.indexOf(n, v);
+ if (didx > 0) {
+ return STATIC_TABLE_LENGTH + didx;
+ } else if (didx < 0) {
+ if (values != null) {
+ // 3. Return name match from the static region
+ return -values.values().iterator().next(); // Iterator allocation
+ } else {
+ // 4. Return name match from the dynamic region
+ return -STATIC_TABLE_LENGTH + didx;
+ }
+ } else {
+ if (values != null) {
+ // 3. Return name match from the static region
+ return -values.values().iterator().next(); // Iterator allocation
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ public int size() {
+ return size;
+ }
+
+ public int maxSize() {
+ return maxSize;
+ }
+
+ public int length() {
+ return STATIC_TABLE_LENGTH + dynamicTable.size();
+ }
+
+ HeaderField get(int index) {
+ checkIndex(index);
+ if (index <= STATIC_TABLE_LENGTH) {
+ return staticTable[index];
+ } else {
+ return dynamicTable.get(index - STATIC_TABLE_LENGTH);
+ }
+ }
+
+ void put(CharSequence name, CharSequence value) {
+ // Invoking toString() will possibly allocate Strings. But that's
+ // unavoidable at this stage. If a CharSequence is going to be stored in
+ // the table, it must not be mutable (e.g. for the sake of hashing).
+ put(new HeaderField(name.toString(), value.toString()));
+ }
+
+ private void put(HeaderField h) {
+ int entrySize = sizeOf(h);
+ while (entrySize > maxSize - size && size != 0) {
+ evictEntry();
+ }
+ if (entrySize > maxSize - size) {
+ return;
+ }
+ size += entrySize;
+ dynamicTable.add(h);
+ }
+
+ void setMaxSize(int maxSize) {
+ if (maxSize < 0) {
+ throw new IllegalArgumentException
+ ("maxSize >= 0: maxSize=" + maxSize);
+ }
+ while (maxSize < size && size != 0) {
+ evictEntry();
+ }
+ this.maxSize = maxSize;
+ int upperBound = (maxSize / ENTRY_SIZE) + 1;
+ this.dynamicTable.setCapacity(upperBound);
+ }
+
+ HeaderField evictEntry() {
+ HeaderField f = dynamicTable.remove();
+ size -= sizeOf(f);
+ return f;
+ }
+
+ @Override
+ public String toString() {
+ double used = maxSize == 0 ? 0 : 100 * (((double) size) / maxSize);
+ return format("entries: %d; used %s/%s (%.1f%%)", dynamicTable.size(),
+ size, maxSize, used);
+ }
+
+ int checkIndex(int index) {
+ if (index < 1 || index > STATIC_TABLE_LENGTH + dynamicTable.size()) {
+ throw new IllegalArgumentException(
+ format("1 <= index <= length(): index=%s, length()=%s",
+ index, length()));
+ }
+ return index;
+ }
+
+ int sizeOf(HeaderField f) {
+ return f.name.length() + f.value.length() + ENTRY_SIZE;
+ }
+
+ //
+ // Diagnostic information in the form used in the RFC 7541
+ //
+ String getStateString() {
+ if (size == 0) {
+ return "empty.";
+ }
+
+ StringBuilder b = new StringBuilder();
+ for (int i = 1, size = dynamicTable.size(); i <= size; i++) {
+ HeaderField e = dynamicTable.get(i);
+ b.append(format("[%3d] (s = %3d) %s: %s%n", i,
+ sizeOf(e), e.name, e.value));
+ }
+ b.append(format(" Table size:%4s", this.size));
+ return b.toString();
+ }
+
+ // Convert to a Value Object (JDK-8046159)?
+ static final class HeaderField {
+
+ final String name;
+ final String value;
+
+ public HeaderField(String name) {
+ this(name, "");
+ }
+
+ public HeaderField(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return value.isEmpty() ? name : name + ": " + value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ HeaderField that = (HeaderField) o;
+ return name.equals(that.name) && value.equals(that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * (name.hashCode()) + value.hashCode();
+ }
+ }
+
+ //
+ // In order to be able to find an index of an entry with the given contents
+ // in the dynamic table an effective inverse mapping is needed. Here's a
+ // simple idea behind such a mapping.
+ //
+ // # The problem:
+ //
+ // We have a queue with an O(1) lookup by index:
+ //
+ // get: index -> x
+ //
+ // What we also want is an O(1) reverse lookup:
+ //
+ // indexOf: x -> index
+ //
+ // # Solution:
+ //
+ // Let's store an inverse mapping as a Map<X, Integer>. This have a problem
+ // that when a new element is added to the queue all indexes in the map
+ // becomes invalid. Namely, each i becomes shifted by 1 to the right:
+ //
+ // i -> i + 1
+ //
+ // And the new element is assigned with an index of 1. This would seem to
+ // require a pass through the map incrementing all indexes (map values) by
+ // 1, which is O(n).
+ //
+ // The good news is we can do much better then this!
+ //
+ // Let's create a single field of type long, called 'counter'. Then each
+ // time a new element 'x' is added to the queue, a value of this field gets
+ // incremented. Then the resulting value of the 'counter_x' is then put as a
+ // value under key 'x' to the map:
+ //
+ // map.put(x, counter_x)
+ //
+ // It gives us a map that maps an element to a value the counter had at the
+ // time the element had been added.
+ //
+ // In order to retrieve an index of any element 'x' in the queue (at any
+ // given time) we simply need to subtract the value (the snapshot of the
+ // counter at the time when the 'x' was added) from the current value of the
+ // counter. This operation basically answers the question:
+ //
+ // How many elements ago 'x' was the tail of the queue?
+ //
+ // Which is the same as its index in the queue now. Given, of course, it's
+ // still in the queue.
+ //
+ // I'm pretty sure in a real life long overflow will never happen, so it's
+ // not too practical to add recalibrating code, but a pedantic person might
+ // want to do so:
+ //
+ // if (counter == Long.MAX_VALUE) {
+ // recalibrate();
+ // }
+ //
+ // Where 'recalibrate()' goes through the table doing this:
+ //
+ // value -= counter
+ //
+ // That's given, of course, the size of the table itself is less than
+ // Long.MAX_VALUE :-)
+ //
+ private static final class Table {
+
+ private final Map<String, Map<String, Long>> map;
+ private final CircularBuffer<HeaderField> buffer;
+ private long counter = 1;
+
+ Table(int capacity) {
+ buffer = new CircularBuffer<>(capacity);
+ map = new HashMap<>(capacity);
+ }
+
+ void add(HeaderField f) {
+ buffer.add(f);
+ Map<String, Long> values = map.computeIfAbsent(f.name, k -> new HashMap<>());
+ values.put(f.value, counter++);
+ }
+
+ HeaderField get(int index) {
+ return buffer.get(index - 1);
+ }
+
+ int indexOf(String name, String value) {
+ Map<String, Long> values = map.get(name);
+ if (values == null) {
+ return 0;
+ }
+ Long index = values.get(value);
+ if (index != null) {
+ return (int) (counter - index);
+ } else {
+ assert !values.isEmpty();
+ Long any = values.values().iterator().next(); // Iterator allocation
+ return -(int) (counter - any);
+ }
+ }
+
+ HeaderField remove() {
+ HeaderField f = buffer.remove();
+ Map<String, Long> values = map.get(f.name);
+ Long index = values.remove(f.value);
+ assert index != null;
+ if (values.isEmpty()) {
+ map.remove(f.name);
+ }
+ return f;
+ }
+
+ int size() {
+ return buffer.size;
+ }
+
+ public void setCapacity(int capacity) {
+ buffer.resize(capacity);
+ }
+ }
+
+ // head
+ // v
+ // [ ][ ][A][B][C][D][ ][ ][ ]
+ // ^
+ // tail
+ //
+ // |<- size ->| (4)
+ // |<------ capacity ------->| (9)
+ //
+ static final class CircularBuffer<E> {
+
+ int tail, head, size, capacity;
+ Object[] elements;
+
+ CircularBuffer(int capacity) {
+ this.capacity = capacity;
+ elements = new Object[capacity];
+ }
+
+ void add(E elem) {
+ if (size == capacity) {
+ throw new IllegalStateException(
+ format("No room for '%s': capacity=%s", elem, capacity));
+ }
+ elements[head] = elem;
+ head = (head + 1) % capacity;
+ size++;
+ }
+
+ @SuppressWarnings("unchecked")
+ E remove() {
+ if (size == 0) {
+ throw new NoSuchElementException("Empty");
+ }
+ E elem = (E) elements[tail];
+ elements[tail] = null;
+ tail = (tail + 1) % capacity;
+ size--;
+ return elem;
+ }
+
+ @SuppressWarnings("unchecked")
+ E get(int index) {
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException(
+ format("0 <= index <= capacity: index=%s, capacity=%s",
+ index, capacity));
+ }
+ int idx = (tail + (size - index - 1)) % capacity;
+ return (E) elements[idx];
+ }
+
+ public void resize(int newCapacity) {
+ if (newCapacity < size) {
+ throw new IllegalStateException(
+ format("newCapacity >= size: newCapacity=%s, size=%s",
+ newCapacity, size));
+ }
+
+ Object[] newElements = new Object[newCapacity];
+
+ if (tail < head || size == 0) {
+ System.arraycopy(elements, tail, newElements, 0, size);
+ } else {
+ System.arraycopy(elements, tail, newElements, 0, elements.length - tail);
+ System.arraycopy(elements, 0, newElements, elements.length - tail, head);
+ }
+
+ elements = newElements;
+ tail = 0;
+ head = size;
+ this.capacity = newCapacity;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/Huffman.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+
+import static java.lang.String.format;
+
+/**
+ * Huffman coding table.
+ *
+ * <p> Instances of this class are safe for use by multiple threads.
+ *
+ * @since 9
+ */
+public final class Huffman {
+
+ // TODO: check if reset is done in both reader and writer
+
+ static final class Reader {
+
+ private Node curr; // position in the trie
+ private int len; // length of the path from the root to 'curr'
+ private int p; // byte probe
+
+ {
+ reset();
+ }
+
+ public void read(ByteBuffer source, Appendable destination,
+ boolean isLast) {
+ read(source, destination, true, isLast);
+ }
+
+ // Takes 'isLast' rather than returns whether the reading is done or
+ // not, for more informative exceptions.
+ void read(ByteBuffer source, Appendable destination, boolean reportEOS,
+ boolean isLast) {
+
+ Node c = curr;
+ int l = len;
+ /*
+ Since ByteBuffer is itself stateful, its position is
+ remembered here NOT as a part of Reader's state,
+ but to set it back in the case of a failure
+ */
+ int pos = source.position();
+
+ while (source.hasRemaining()) {
+ int d = source.get();
+ for (; p != 0; p >>= 1) {
+ c = c.getChild(p & d);
+ l++;
+ if (c.isLeaf()) {
+ if (reportEOS && c.isEOSPath) {
+ throw new IllegalArgumentException("Encountered EOS");
+ }
+ try {
+ destination.append(c.getChar());
+ } catch (RuntimeException | Error e) {
+ source.position(pos);
+ throw e;
+ } catch (IOException e) {
+ source.position(pos);
+ throw new UncheckedIOException(e);
+ }
+ c = INSTANCE.root;
+ l = 0;
+ }
+ curr = c;
+ len = l;
+ }
+ resetProbe();
+ pos++;
+ }
+ if (!isLast) {
+ return; // it's too early to jump to any conclusions, let's wait
+ }
+ if (c.isLeaf()) {
+ return; // it's perfectly ok, no extra padding bits
+ }
+ if (c.isEOSPath && len <= 7) {
+ return; // it's ok, some extra padding bits
+ }
+ if (c.isEOSPath) {
+ throw new IllegalArgumentException(
+ "Padding is too long (len=" + len + ") " +
+ "or unexpected end of data");
+ }
+ throw new IllegalArgumentException(
+ "Not a EOS prefix padding or unexpected end of data");
+ }
+
+ public void reset() {
+ curr = INSTANCE.root;
+ len = 0;
+ resetProbe();
+ }
+
+ private void resetProbe() {
+ p = 0x80;
+ }
+ }
+
+ static final class Writer {
+
+ private int pos; // position in 'source'
+ private int avail = 8; // number of least significant bits available in 'curr'
+ private int curr; // next byte to put to the destination
+ private int rem; // number of least significant bits in 'code' yet to be processed
+ private int code; // current code being written
+
+ private CharSequence source;
+ private int end;
+
+ public Writer from(CharSequence input, int start, int end) {
+ if (start < 0 || end < 0 || end > input.length() || start > end) {
+ throw new IndexOutOfBoundsException(
+ String.format("input.length()=%s, start=%s, end=%s",
+ input.length(), start, end));
+ }
+ pos = start;
+ this.end = end;
+ this.source = input;
+ return this;
+ }
+
+ public boolean write(ByteBuffer destination) {
+ for (; pos < end; pos++) {
+ if (rem == 0) {
+ Code desc = INSTANCE.codeOf(source.charAt(pos));
+ rem = desc.length;
+ code = desc.code;
+ }
+ while (rem > 0) {
+ if (rem < avail) {
+ curr |= (code << (avail - rem));
+ avail -= rem;
+ rem = 0;
+ } else {
+ int c = (curr | (code >>> (rem - avail)));
+ if (destination.hasRemaining()) {
+ destination.put((byte) c);
+ } else {
+ return false;
+ }
+ curr = c;
+ code <<= (32 - rem + avail); // throw written bits off the cliff (is this Sparta?)
+ code >>>= (32 - rem + avail); // return to the position
+ rem -= avail;
+ curr = 0;
+ avail = 8;
+ }
+ }
+ }
+
+ if (avail < 8) { // have to pad
+ if (destination.hasRemaining()) {
+ destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
+ avail = 8;
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public Writer reset() {
+ source = null;
+ end = -1;
+ pos = -1;
+ avail = 8;
+ curr = 0;
+ code = 0;
+ return this;
+ }
+ }
+
+ /**
+ * Shared instance.
+ */
+ public static final Huffman INSTANCE = new Huffman();
+
+ private final Code EOS = new Code(0x3fffffff, 30);
+ private final Code[] codes = new Code[257];
+ private final Node root = new Node() {
+ @Override
+ public String toString() { return "root"; }
+ };
+
+ // TODO: consider builder and immutable trie
+ private Huffman() {
+ // @formatter:off
+ addChar(0, 0x1ff8, 13);
+ addChar(1, 0x7fffd8, 23);
+ addChar(2, 0xfffffe2, 28);
+ addChar(3, 0xfffffe3, 28);
+ addChar(4, 0xfffffe4, 28);
+ addChar(5, 0xfffffe5, 28);
+ addChar(6, 0xfffffe6, 28);
+ addChar(7, 0xfffffe7, 28);
+ addChar(8, 0xfffffe8, 28);
+ addChar(9, 0xffffea, 24);
+ addChar(10, 0x3ffffffc, 30);
+ addChar(11, 0xfffffe9, 28);
+ addChar(12, 0xfffffea, 28);
+ addChar(13, 0x3ffffffd, 30);
+ addChar(14, 0xfffffeb, 28);
+ addChar(15, 0xfffffec, 28);
+ addChar(16, 0xfffffed, 28);
+ addChar(17, 0xfffffee, 28);
+ addChar(18, 0xfffffef, 28);
+ addChar(19, 0xffffff0, 28);
+ addChar(20, 0xffffff1, 28);
+ addChar(21, 0xffffff2, 28);
+ addChar(22, 0x3ffffffe, 30);
+ addChar(23, 0xffffff3, 28);
+ addChar(24, 0xffffff4, 28);
+ addChar(25, 0xffffff5, 28);
+ addChar(26, 0xffffff6, 28);
+ addChar(27, 0xffffff7, 28);
+ addChar(28, 0xffffff8, 28);
+ addChar(29, 0xffffff9, 28);
+ addChar(30, 0xffffffa, 28);
+ addChar(31, 0xffffffb, 28);
+ addChar(32, 0x14, 6);
+ addChar(33, 0x3f8, 10);
+ addChar(34, 0x3f9, 10);
+ addChar(35, 0xffa, 12);
+ addChar(36, 0x1ff9, 13);
+ addChar(37, 0x15, 6);
+ addChar(38, 0xf8, 8);
+ addChar(39, 0x7fa, 11);
+ addChar(40, 0x3fa, 10);
+ addChar(41, 0x3fb, 10);
+ addChar(42, 0xf9, 8);
+ addChar(43, 0x7fb, 11);
+ addChar(44, 0xfa, 8);
+ addChar(45, 0x16, 6);
+ addChar(46, 0x17, 6);
+ addChar(47, 0x18, 6);
+ addChar(48, 0x0, 5);
+ addChar(49, 0x1, 5);
+ addChar(50, 0x2, 5);
+ addChar(51, 0x19, 6);
+ addChar(52, 0x1a, 6);
+ addChar(53, 0x1b, 6);
+ addChar(54, 0x1c, 6);
+ addChar(55, 0x1d, 6);
+ addChar(56, 0x1e, 6);
+ addChar(57, 0x1f, 6);
+ addChar(58, 0x5c, 7);
+ addChar(59, 0xfb, 8);
+ addChar(60, 0x7ffc, 15);
+ addChar(61, 0x20, 6);
+ addChar(62, 0xffb, 12);
+ addChar(63, 0x3fc, 10);
+ addChar(64, 0x1ffa, 13);
+ addChar(65, 0x21, 6);
+ addChar(66, 0x5d, 7);
+ addChar(67, 0x5e, 7);
+ addChar(68, 0x5f, 7);
+ addChar(69, 0x60, 7);
+ addChar(70, 0x61, 7);
+ addChar(71, 0x62, 7);
+ addChar(72, 0x63, 7);
+ addChar(73, 0x64, 7);
+ addChar(74, 0x65, 7);
+ addChar(75, 0x66, 7);
+ addChar(76, 0x67, 7);
+ addChar(77, 0x68, 7);
+ addChar(78, 0x69, 7);
+ addChar(79, 0x6a, 7);
+ addChar(80, 0x6b, 7);
+ addChar(81, 0x6c, 7);
+ addChar(82, 0x6d, 7);
+ addChar(83, 0x6e, 7);
+ addChar(84, 0x6f, 7);
+ addChar(85, 0x70, 7);
+ addChar(86, 0x71, 7);
+ addChar(87, 0x72, 7);
+ addChar(88, 0xfc, 8);
+ addChar(89, 0x73, 7);
+ addChar(90, 0xfd, 8);
+ addChar(91, 0x1ffb, 13);
+ addChar(92, 0x7fff0, 19);
+ addChar(93, 0x1ffc, 13);
+ addChar(94, 0x3ffc, 14);
+ addChar(95, 0x22, 6);
+ addChar(96, 0x7ffd, 15);
+ addChar(97, 0x3, 5);
+ addChar(98, 0x23, 6);
+ addChar(99, 0x4, 5);
+ addChar(100, 0x24, 6);
+ addChar(101, 0x5, 5);
+ addChar(102, 0x25, 6);
+ addChar(103, 0x26, 6);
+ addChar(104, 0x27, 6);
+ addChar(105, 0x6, 5);
+ addChar(106, 0x74, 7);
+ addChar(107, 0x75, 7);
+ addChar(108, 0x28, 6);
+ addChar(109, 0x29, 6);
+ addChar(110, 0x2a, 6);
+ addChar(111, 0x7, 5);
+ addChar(112, 0x2b, 6);
+ addChar(113, 0x76, 7);
+ addChar(114, 0x2c, 6);
+ addChar(115, 0x8, 5);
+ addChar(116, 0x9, 5);
+ addChar(117, 0x2d, 6);
+ addChar(118, 0x77, 7);
+ addChar(119, 0x78, 7);
+ addChar(120, 0x79, 7);
+ addChar(121, 0x7a, 7);
+ addChar(122, 0x7b, 7);
+ addChar(123, 0x7ffe, 15);
+ addChar(124, 0x7fc, 11);
+ addChar(125, 0x3ffd, 14);
+ addChar(126, 0x1ffd, 13);
+ addChar(127, 0xffffffc, 28);
+ addChar(128, 0xfffe6, 20);
+ addChar(129, 0x3fffd2, 22);
+ addChar(130, 0xfffe7, 20);
+ addChar(131, 0xfffe8, 20);
+ addChar(132, 0x3fffd3, 22);
+ addChar(133, 0x3fffd4, 22);
+ addChar(134, 0x3fffd5, 22);
+ addChar(135, 0x7fffd9, 23);
+ addChar(136, 0x3fffd6, 22);
+ addChar(137, 0x7fffda, 23);
+ addChar(138, 0x7fffdb, 23);
+ addChar(139, 0x7fffdc, 23);
+ addChar(140, 0x7fffdd, 23);
+ addChar(141, 0x7fffde, 23);
+ addChar(142, 0xffffeb, 24);
+ addChar(143, 0x7fffdf, 23);
+ addChar(144, 0xffffec, 24);
+ addChar(145, 0xffffed, 24);
+ addChar(146, 0x3fffd7, 22);
+ addChar(147, 0x7fffe0, 23);
+ addChar(148, 0xffffee, 24);
+ addChar(149, 0x7fffe1, 23);
+ addChar(150, 0x7fffe2, 23);
+ addChar(151, 0x7fffe3, 23);
+ addChar(152, 0x7fffe4, 23);
+ addChar(153, 0x1fffdc, 21);
+ addChar(154, 0x3fffd8, 22);
+ addChar(155, 0x7fffe5, 23);
+ addChar(156, 0x3fffd9, 22);
+ addChar(157, 0x7fffe6, 23);
+ addChar(158, 0x7fffe7, 23);
+ addChar(159, 0xffffef, 24);
+ addChar(160, 0x3fffda, 22);
+ addChar(161, 0x1fffdd, 21);
+ addChar(162, 0xfffe9, 20);
+ addChar(163, 0x3fffdb, 22);
+ addChar(164, 0x3fffdc, 22);
+ addChar(165, 0x7fffe8, 23);
+ addChar(166, 0x7fffe9, 23);
+ addChar(167, 0x1fffde, 21);
+ addChar(168, 0x7fffea, 23);
+ addChar(169, 0x3fffdd, 22);
+ addChar(170, 0x3fffde, 22);
+ addChar(171, 0xfffff0, 24);
+ addChar(172, 0x1fffdf, 21);
+ addChar(173, 0x3fffdf, 22);
+ addChar(174, 0x7fffeb, 23);
+ addChar(175, 0x7fffec, 23);
+ addChar(176, 0x1fffe0, 21);
+ addChar(177, 0x1fffe1, 21);
+ addChar(178, 0x3fffe0, 22);
+ addChar(179, 0x1fffe2, 21);
+ addChar(180, 0x7fffed, 23);
+ addChar(181, 0x3fffe1, 22);
+ addChar(182, 0x7fffee, 23);
+ addChar(183, 0x7fffef, 23);
+ addChar(184, 0xfffea, 20);
+ addChar(185, 0x3fffe2, 22);
+ addChar(186, 0x3fffe3, 22);
+ addChar(187, 0x3fffe4, 22);
+ addChar(188, 0x7ffff0, 23);
+ addChar(189, 0x3fffe5, 22);
+ addChar(190, 0x3fffe6, 22);
+ addChar(191, 0x7ffff1, 23);
+ addChar(192, 0x3ffffe0, 26);
+ addChar(193, 0x3ffffe1, 26);
+ addChar(194, 0xfffeb, 20);
+ addChar(195, 0x7fff1, 19);
+ addChar(196, 0x3fffe7, 22);
+ addChar(197, 0x7ffff2, 23);
+ addChar(198, 0x3fffe8, 22);
+ addChar(199, 0x1ffffec, 25);
+ addChar(200, 0x3ffffe2, 26);
+ addChar(201, 0x3ffffe3, 26);
+ addChar(202, 0x3ffffe4, 26);
+ addChar(203, 0x7ffffde, 27);
+ addChar(204, 0x7ffffdf, 27);
+ addChar(205, 0x3ffffe5, 26);
+ addChar(206, 0xfffff1, 24);
+ addChar(207, 0x1ffffed, 25);
+ addChar(208, 0x7fff2, 19);
+ addChar(209, 0x1fffe3, 21);
+ addChar(210, 0x3ffffe6, 26);
+ addChar(211, 0x7ffffe0, 27);
+ addChar(212, 0x7ffffe1, 27);
+ addChar(213, 0x3ffffe7, 26);
+ addChar(214, 0x7ffffe2, 27);
+ addChar(215, 0xfffff2, 24);
+ addChar(216, 0x1fffe4, 21);
+ addChar(217, 0x1fffe5, 21);
+ addChar(218, 0x3ffffe8, 26);
+ addChar(219, 0x3ffffe9, 26);
+ addChar(220, 0xffffffd, 28);
+ addChar(221, 0x7ffffe3, 27);
+ addChar(222, 0x7ffffe4, 27);
+ addChar(223, 0x7ffffe5, 27);
+ addChar(224, 0xfffec, 20);
+ addChar(225, 0xfffff3, 24);
+ addChar(226, 0xfffed, 20);
+ addChar(227, 0x1fffe6, 21);
+ addChar(228, 0x3fffe9, 22);
+ addChar(229, 0x1fffe7, 21);
+ addChar(230, 0x1fffe8, 21);
+ addChar(231, 0x7ffff3, 23);
+ addChar(232, 0x3fffea, 22);
+ addChar(233, 0x3fffeb, 22);
+ addChar(234, 0x1ffffee, 25);
+ addChar(235, 0x1ffffef, 25);
+ addChar(236, 0xfffff4, 24);
+ addChar(237, 0xfffff5, 24);
+ addChar(238, 0x3ffffea, 26);
+ addChar(239, 0x7ffff4, 23);
+ addChar(240, 0x3ffffeb, 26);
+ addChar(241, 0x7ffffe6, 27);
+ addChar(242, 0x3ffffec, 26);
+ addChar(243, 0x3ffffed, 26);
+ addChar(244, 0x7ffffe7, 27);
+ addChar(245, 0x7ffffe8, 27);
+ addChar(246, 0x7ffffe9, 27);
+ addChar(247, 0x7ffffea, 27);
+ addChar(248, 0x7ffffeb, 27);
+ addChar(249, 0xffffffe, 28);
+ addChar(250, 0x7ffffec, 27);
+ addChar(251, 0x7ffffed, 27);
+ addChar(252, 0x7ffffee, 27);
+ addChar(253, 0x7ffffef, 27);
+ addChar(254, 0x7fffff0, 27);
+ addChar(255, 0x3ffffee, 26);
+ addEOS (256, EOS.code, EOS.length);
+ // @formatter:on
+ }
+
+
+ /**
+ * Calculates the number of bytes required to represent the given {@code
+ * CharSequence} with the Huffman coding.
+ *
+ * @param value
+ * characters
+ *
+ * @return number of bytes
+ *
+ * @throws NullPointerException
+ * if the value is null
+ */
+ public int lengthOf(CharSequence value) {
+ return lengthOf(value, 0, value.length());
+ }
+
+ /**
+ * Calculates the number of bytes required to represent a subsequence of the
+ * given {@code CharSequence} with the Huffman coding.
+ *
+ * @param value
+ * characters
+ * @param start
+ * the start index, inclusive
+ * @param end
+ * the end index, exclusive
+ *
+ * @return number of bytes
+ *
+ * @throws NullPointerException
+ * if the value is null
+ * @throws IndexOutOfBoundsException
+ * if any invocation of {@code value.charAt(i)}, where {@code start
+ * <= i < end} would throw an IndexOutOfBoundsException
+ */
+ public int lengthOf(CharSequence value, int start, int end) {
+ int len = 0;
+ for (int i = start; i < end; i++) {
+ char c = value.charAt(i);
+ len += INSTANCE.codeOf(c).length;
+ }
+ // Integer division with ceiling, assumption:
+ assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
+ return (len + 7) / 8;
+ }
+
+ private void addChar(int c, int code, int bitLength) {
+ addLeaf(c, code, bitLength, false);
+ codes[c] = new Code(code, bitLength);
+ }
+
+ private void addEOS(int c, int code, int bitLength) {
+ addLeaf(c, code, bitLength, true);
+ codes[c] = new Code(code, bitLength);
+ }
+
+ private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
+ if (bitLength < 1) {
+ throw new IllegalArgumentException("bitLength < 1");
+ }
+ Node curr = root;
+ for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
+ curr.isEOSPath |= isEOS; // If it's already true, it can't become false
+ curr = curr.addChildIfAbsent(p & code);
+ }
+ curr.isEOSPath |= isEOS; // The last one needs to have this property as well
+ if (curr.isLeaf()) {
+ throw new IllegalStateException("Specified code is already taken");
+ }
+ curr.setChar((char) c);
+ }
+
+ private Code codeOf(char c) {
+ if (c > 255) {
+ throw new IllegalArgumentException("char=" + ((int) c));
+ }
+ return codes[c];
+ }
+
+ //
+ // For debugging/testing purposes
+ //
+ Node getRoot() {
+ return root;
+ }
+
+ //
+ // Guarantees:
+ //
+ // if (isLeaf() == true) => getChar() is a legal call
+ // if (isLeaf() == false) => getChild(i) is a legal call (though it can
+ // return null)
+ //
+ static class Node {
+
+ Node left;
+ Node right;
+ boolean isEOSPath;
+
+ boolean charIsSet;
+ char c;
+
+ Node getChild(int selector) {
+ if (isLeaf()) {
+ throw new IllegalStateException("This is a leaf node");
+ }
+ Node result = selector == 0 ? left : right;
+ if (result == null) {
+ throw new IllegalStateException(format(
+ "Node doesn't have a child (selector=%s)", selector));
+ }
+ return result;
+ }
+
+ boolean isLeaf() {
+ return charIsSet;
+ }
+
+ char getChar() {
+ if (!isLeaf()) {
+ throw new IllegalStateException("This node is not a leaf node");
+ }
+ return c;
+ }
+
+ void setChar(char c) {
+ if (charIsSet) {
+ throw new IllegalStateException(
+ "This node has been taken already");
+ }
+ if (left != null || right != null) {
+ throw new IllegalStateException("The node cannot be made "
+ + "a leaf as it's already has a child");
+ }
+ this.c = c;
+ charIsSet = true;
+ }
+
+ Node addChildIfAbsent(int i) {
+ if (charIsSet) {
+ throw new IllegalStateException("The node cannot have a child "
+ + "as it's already a leaf node");
+ }
+ Node child;
+ if (i == 0) {
+ if ((child = left) == null) {
+ child = left = new Node();
+ }
+ } else {
+ if ((child = right) == null) {
+ child = right = new Node();
+ }
+ }
+ return child;
+ }
+
+ @Override
+ public String toString() {
+ if (isLeaf()) {
+ if (isEOSPath) {
+ return "EOS";
+ } else {
+ return format("char: (%3s) '%s'", (int) c, c);
+ }
+ }
+ return "/\\";
+ }
+ }
+
+ // TODO: value-based class?
+ // FIXME: can we re-use Node instead of this class?
+ private static final class Code {
+
+ final int code;
+ final int length;
+
+ private Code(int code, int length) {
+ this.code = code;
+ this.length = length;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ @Override
+ public String toString() {
+ long p = 1 << length;
+ return Long.toBinaryString(code + p).substring(1)
+ + ", length=" + length;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/ISO_8859_1.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+
+//
+// Custom implementation of ISO/IEC 8859-1:1998
+//
+// The rationale behind this is not to deal with CharsetEncoder/CharsetDecoder,
+// basically because it would require wrapping every single CharSequence into a
+// CharBuffer and then copying it back.
+//
+// But why not to give a CharBuffer instead of Appendable? Because I can choose
+// an Appendable (e.g. StringBuilder) that adjusts its length when needed and
+// therefore not to deal with pre-sized CharBuffers or copying.
+//
+// The encoding is simple and well known: 1 byte <-> 1 char
+//
+final class ISO_8859_1 {
+
+ private ISO_8859_1() { }
+
+ public static final class Reader {
+
+ public void read(ByteBuffer source, Appendable destination) {
+ for (int i = 0, len = source.remaining(); i < len; i++) {
+ char c = (char) (source.get() & 0xff);
+ try {
+ destination.append(c);
+ } catch (IOException e) {
+ throw new UncheckedIOException
+ ("Error appending to the destination", e);
+ }
+ }
+ }
+
+ public Reader reset() {
+ return this;
+ }
+ }
+
+ public static final class Writer {
+
+ private CharSequence source;
+ private int pos;
+ private int end;
+
+ public Writer configure(CharSequence source, int start, int end) {
+ this.source = source;
+ this.pos = start;
+ this.end = end;
+ return this;
+ }
+
+ public boolean write(ByteBuffer destination) {
+ for (; pos < end; pos++) {
+ char c = source.charAt(pos);
+ if (c > '\u00FF') {
+ throw new IllegalArgumentException(
+ "Illegal ISO-8859-1 char: " + (int) c);
+ }
+ if (destination.hasRemaining()) {
+ destination.put((byte) c);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Writer reset() {
+ source = null;
+ pos = -1;
+ end = -1;
+ return this;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/IndexNameValueWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+
+abstract class IndexNameValueWriter implements BinaryRepresentationWriter {
+
+ private final int pattern;
+ private final int prefix;
+ private final IntegerWriter intWriter = new IntegerWriter();
+ private final StringWriter nameWriter = new StringWriter();
+ private final StringWriter valueWriter = new StringWriter();
+
+ protected boolean indexedRepresentation;
+
+ private static final int NEW = 0;
+ private static final int NAME_PART_WRITTEN = 1;
+ private static final int VALUE_WRITTEN = 2;
+
+ private int state = NEW;
+
+ protected IndexNameValueWriter(int pattern, int prefix) {
+ this.pattern = pattern;
+ this.prefix = prefix;
+ }
+
+ IndexNameValueWriter index(int index) {
+ indexedRepresentation = true;
+ intWriter.configure(index, prefix, pattern);
+ return this;
+ }
+
+ IndexNameValueWriter name(CharSequence name, boolean useHuffman) {
+ indexedRepresentation = false;
+ intWriter.configure(0, prefix, pattern);
+ nameWriter.configure(name, useHuffman);
+ return this;
+ }
+
+ IndexNameValueWriter value(CharSequence value, boolean useHuffman) {
+ valueWriter.configure(value, useHuffman);
+ return this;
+ }
+
+ @Override
+ public boolean write(HeaderTable table, ByteBuffer destination) {
+ if (state < NAME_PART_WRITTEN) {
+ if (indexedRepresentation) {
+ if (!intWriter.write(destination)) {
+ return false;
+ }
+ } else {
+ if (!intWriter.write(destination) || !nameWriter.write(destination)) {
+ return false;
+ }
+ }
+ state = NAME_PART_WRITTEN;
+ }
+ if (state < VALUE_WRITTEN) {
+ if (!valueWriter.write(destination)) {
+ return false;
+ }
+ state = VALUE_WRITTEN;
+ }
+ return state == VALUE_WRITTEN;
+ }
+
+ @Override
+ public IndexNameValueWriter reset() {
+ intWriter.reset();
+ if (!indexedRepresentation) {
+ nameWriter.reset();
+ }
+ valueWriter.reset();
+ state = NEW;
+ return this;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/IndexedWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+
+final class IndexedWriter implements BinaryRepresentationWriter {
+
+ private final IntegerWriter intWriter = new IntegerWriter();
+
+ IndexedWriter() { }
+
+ IndexedWriter index(int index) {
+ intWriter.configure(index, 7, 0b1000_0000);
+ return this;
+ }
+
+ @Override
+ public boolean write(HeaderTable table, ByteBuffer destination) {
+ return intWriter.write(destination);
+ }
+
+ @Override
+ public BinaryRepresentationWriter reset() {
+ intWriter.reset();
+ return this;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/IntegerReader.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static java.lang.String.format;
+
+final class IntegerReader {
+
+ private static final int NEW = 0;
+ private static final int CONFIGURED = 1;
+ private static final int FIRST_BYTE_READ = 2;
+ private static final int DONE = 4;
+
+ private int state = NEW;
+
+ private int N;
+ private int maxValue;
+ private int value;
+ private long r;
+ private long b = 1;
+
+ public IntegerReader configure(int N) {
+ return configure(N, Integer.MAX_VALUE);
+ }
+
+ //
+ // Why is it important to configure 'maxValue' here. After all we can wait
+ // for the integer to be fully read and then check it. Can't we?
+ //
+ // Two reasons.
+ //
+ // 1. Value wraps around long won't be unnoticed.
+ // 2. It can spit out an exception as soon as it becomes clear there's
+ // an overflow. Therefore, no need to wait for the value to be fully read.
+ //
+ public IntegerReader configure(int N, int maxValue) {
+ if (state != NEW) {
+ throw new IllegalStateException("Already configured");
+ }
+ checkPrefix(N);
+ if (maxValue < 0) {
+ throw new IllegalArgumentException(
+ "maxValue >= 0: maxValue=" + maxValue);
+ }
+ this.maxValue = maxValue;
+ this.N = N;
+ state = CONFIGURED;
+ return this;
+ }
+
+ public boolean read(ByteBuffer input) {
+ if (state == NEW) {
+ throw new IllegalStateException("Configure first");
+ }
+ if (state == DONE) {
+ return true;
+ }
+ if (!input.hasRemaining()) {
+ return false;
+ }
+ if (state == CONFIGURED) {
+ int max = (2 << (N - 1)) - 1;
+ int n = input.get() & max;
+ if (n != max) {
+ value = n;
+ state = DONE;
+ return true;
+ } else {
+ r = max;
+ }
+ state = FIRST_BYTE_READ;
+ }
+ if (state == FIRST_BYTE_READ) {
+ // variable-length quantity (VLQ)
+ byte i;
+ do {
+ if (!input.hasRemaining()) {
+ return false;
+ }
+ i = input.get();
+ long increment = b * (i & 127);
+ if (r + increment > maxValue) {
+ throw new IllegalArgumentException(format(
+ "Integer overflow: maxValue=%,d, value=%,d",
+ maxValue, r + increment));
+ }
+ r += increment;
+ b *= 128;
+ } while ((128 & i) == 128);
+
+ value = (int) r;
+ state = DONE;
+ return true;
+ }
+ throw new InternalError(Arrays.toString(
+ new Object[]{state, N, maxValue, value, r, b}));
+ }
+
+ public int get() throws IllegalStateException {
+ if (state != DONE) {
+ throw new IllegalStateException("Has not been fully read yet");
+ }
+ return value;
+ }
+
+ private static void checkPrefix(int N) {
+ if (N < 1 || N > 8) {
+ throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
+ }
+ }
+
+ public IntegerReader reset() {
+ b = 1;
+ state = NEW;
+ return this;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/IntegerWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+final class IntegerWriter {
+
+ private static final int NEW = 0;
+ private static final int CONFIGURED = 1;
+ private static final int FIRST_BYTE_WRITTEN = 2;
+ private static final int DONE = 4;
+
+ private int state = NEW;
+
+ private int payload;
+ private int N;
+ private int value;
+
+ //
+ // 0 1 2 3 4 5 6 7
+ // +---+---+---+---+---+---+---+---+
+ // | | | | | | | | |
+ // +---+---+---+-------------------+
+ // |<--------->|<----------------->|
+ // payload N=5
+ //
+ // payload is the contents of the left-hand side part of the octet;
+ // it is truncated to fit into 8-N bits, where 1 <= N <= 8;
+ //
+ public IntegerWriter configure(int value, int N, int payload) {
+ if (state != NEW) {
+ throw new IllegalStateException("Already configured");
+ }
+ if (value < 0) {
+ throw new IllegalArgumentException("value >= 0: value=" + value);
+ }
+ checkPrefix(N);
+ this.value = value;
+ this.N = N;
+ this.payload = payload & 0xFF & (0xFFFFFFFF << N);
+ state = CONFIGURED;
+ return this;
+ }
+
+ public boolean write(ByteBuffer output) {
+ if (state == NEW) {
+ throw new IllegalStateException("Configure first");
+ }
+ if (state == DONE) {
+ return true;
+ }
+
+ if (!output.hasRemaining()) {
+ return false;
+ }
+ if (state == CONFIGURED) {
+ int max = (2 << (N - 1)) - 1;
+ if (value < max) {
+ output.put((byte) (payload | value));
+ state = DONE;
+ return true;
+ }
+ output.put((byte) (payload | max));
+ value -= max;
+ state = FIRST_BYTE_WRITTEN;
+ }
+ if (state == FIRST_BYTE_WRITTEN) {
+ while (value >= 128 && output.hasRemaining()) {
+ output.put((byte) (value % 128 + 128));
+ value /= 128;
+ }
+ if (!output.hasRemaining()) {
+ return false;
+ }
+ output.put((byte) value);
+ state = DONE;
+ return true;
+ }
+ throw new InternalError(Arrays.toString(
+ new Object[]{state, payload, N, value}));
+ }
+
+ private static void checkPrefix(int N) {
+ if (N < 1 || N > 8) {
+ throw new IllegalArgumentException("1 <= N <= 8: N= " + N);
+ }
+ }
+
+ public IntegerWriter reset() {
+ state = NEW;
+ return this;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/LiteralNeverIndexedWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+final class LiteralNeverIndexedWriter extends IndexNameValueWriter {
+
+ LiteralNeverIndexedWriter() {
+ super(0b0001_0000, 4);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/LiteralWithIndexingWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+
+final class LiteralWithIndexingWriter extends IndexNameValueWriter {
+
+ private boolean tableUpdated;
+
+ private CharSequence name;
+ private CharSequence value;
+ private int index;
+
+ LiteralWithIndexingWriter() {
+ super(0b0100_0000, 6);
+ }
+
+ @Override
+ LiteralWithIndexingWriter index(int index) {
+ super.index(index);
+ this.index = index;
+ return this;
+ }
+
+ @Override
+ LiteralWithIndexingWriter name(CharSequence name, boolean useHuffman) {
+ super.name(name, useHuffman);
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ LiteralWithIndexingWriter value(CharSequence value, boolean useHuffman) {
+ super.value(value, useHuffman);
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public boolean write(HeaderTable table, ByteBuffer destination) {
+ if (!tableUpdated) {
+ CharSequence n;
+ if (indexedRepresentation) {
+ n = table.get(index).name;
+ } else {
+ n = name;
+ }
+ table.put(n, value);
+ tableUpdated = true;
+ }
+ return super.write(table, destination);
+ }
+
+ @Override
+ public IndexNameValueWriter reset() {
+ tableUpdated = false;
+ name = null;
+ value = null;
+ index = -1;
+ return super.reset();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/LiteralWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+final class LiteralWriter extends IndexNameValueWriter {
+
+ LiteralWriter() {
+ super(0b0000_0000, 4);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/SizeUpdateWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+
+final class SizeUpdateWriter implements BinaryRepresentationWriter {
+
+ private final IntegerWriter intWriter = new IntegerWriter();
+ private int maxSize;
+ private boolean tableUpdated;
+
+ SizeUpdateWriter() { }
+
+ SizeUpdateWriter maxHeaderTableSize(int size) {
+ intWriter.configure(size, 5, 0b0010_0000);
+ this.maxSize = size;
+ return this;
+ }
+
+ @Override
+ public boolean write(HeaderTable table, ByteBuffer destination) {
+ if (!tableUpdated) {
+ table.setMaxSize(maxSize);
+ tableUpdated = true;
+ }
+ return intWriter.write(destination);
+ }
+
+ @Override
+ public BinaryRepresentationWriter reset() {
+ intWriter.reset();
+ maxSize = -1;
+ tableUpdated = false;
+ return this;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/StringReader.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+//
+// 0 1 2 3 4 5 6 7
+// +---+---+---+---+---+---+---+---+
+// | H | String Length (7+) |
+// +---+---------------------------+
+// | String Data (Length octets) |
+// +-------------------------------+
+//
+final class StringReader {
+
+ private static final int NEW = 0;
+ private static final int FIRST_BYTE_READ = 1;
+ private static final int LENGTH_READ = 2;
+ private static final int DONE = 4;
+
+ private final IntegerReader intReader = new IntegerReader();
+ private final Huffman.Reader huffmanReader = new Huffman.Reader();
+ private final ISO_8859_1.Reader plainReader = new ISO_8859_1.Reader();
+
+ private int state = NEW;
+
+ private boolean huffman;
+ private int remainingLength;
+
+ boolean read(ByteBuffer input, Appendable output) {
+ if (state == DONE) {
+ return true;
+ }
+ if (!input.hasRemaining()) {
+ return false;
+ }
+ if (state == NEW) {
+ int p = input.position();
+ huffman = (input.get(p) & 0b10000000) != 0;
+ state = FIRST_BYTE_READ;
+ intReader.configure(7);
+ }
+ if (state == FIRST_BYTE_READ) {
+ boolean lengthRead = intReader.read(input);
+ if (!lengthRead) {
+ return false;
+ }
+ remainingLength = intReader.get();
+ state = LENGTH_READ;
+ }
+ if (state == LENGTH_READ) {
+ boolean isLast = input.remaining() >= remainingLength;
+ int oldLimit = input.limit();
+ if (isLast) {
+ input.limit(input.position() + remainingLength);
+ }
+ if (huffman) {
+ huffmanReader.read(input, output, isLast);
+ } else {
+ plainReader.read(input, output);
+ }
+ if (isLast) {
+ input.limit(oldLimit);
+ }
+ return isLast;
+ }
+ throw new InternalError(Arrays.toString(
+ new Object[]{state, huffman, remainingLength}));
+ }
+
+ boolean isHuffmanEncoded() {
+ if (state < FIRST_BYTE_READ) {
+ throw new IllegalStateException("Has not been fully read yet");
+ }
+ return huffman;
+ }
+
+ void reset() {
+ if (huffman) {
+ huffmanReader.reset();
+ } else {
+ plainReader.reset();
+ }
+ intReader.reset();
+ state = NEW;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/StringWriter.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+//
+// 0 1 2 3 4 5 6 7
+// +---+---+---+---+---+---+---+---+
+// | H | String Length (7+) |
+// +---+---------------------------+
+// | String Data (Length octets) |
+// +-------------------------------+
+//
+// StringWriter does not require a notion of endOfInput (isLast) in 'write'
+// methods due to the nature of string representation in HPACK. Namely, the
+// length of the string is put before string's contents. Therefore the length is
+// always known beforehand.
+//
+// Expected use:
+//
+// configure write* (reset configure write*)*
+//
+final class StringWriter {
+
+ private static final int NEW = 0;
+ private static final int CONFIGURED = 1;
+ private static final int LENGTH_WRITTEN = 2;
+ private static final int DONE = 4;
+
+ private final IntegerWriter intWriter = new IntegerWriter();
+ private final Huffman.Writer huffmanWriter = new Huffman.Writer();
+ private final ISO_8859_1.Writer plainWriter = new ISO_8859_1.Writer();
+
+ private int state = NEW;
+ private boolean huffman;
+
+ StringWriter configure(CharSequence input, boolean huffman) {
+ return configure(input, 0, input.length(), huffman);
+ }
+
+ StringWriter configure(CharSequence input, int start, int end,
+ boolean huffman) {
+ if (start < 0 || end < 0 || end > input.length() || start > end) {
+ throw new IndexOutOfBoundsException(
+ String.format("input.length()=%s, start=%s, end=%s",
+ input.length(), start, end));
+ }
+ if (!huffman) {
+ plainWriter.configure(input, start, end);
+ intWriter.configure(end - start, 7, 0b0000_0000);
+ } else {
+ huffmanWriter.from(input, start, end);
+ intWriter.configure(Huffman.INSTANCE.lengthOf(input, start, end),
+ 7, 0b1000_0000);
+ }
+
+ this.huffman = huffman;
+ state = CONFIGURED;
+ return this;
+ }
+
+ boolean write(ByteBuffer output) {
+ if (state == DONE) {
+ return true;
+ }
+ if (state == NEW) {
+ throw new IllegalStateException("Configure first");
+ }
+ if (!output.hasRemaining()) {
+ return false;
+ }
+ if (state == CONFIGURED) {
+ if (intWriter.write(output)) {
+ state = LENGTH_WRITTEN;
+ } else {
+ return false;
+ }
+ }
+ if (state == LENGTH_WRITTEN) {
+ boolean written = huffman
+ ? huffmanWriter.write(output)
+ : plainWriter.write(output);
+ if (written) {
+ state = DONE;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ throw new InternalError(Arrays.toString(new Object[]{state, huffman}));
+ }
+
+ void reset() {
+ intWriter.reset();
+ if (huffman) {
+ huffmanWriter.reset();
+ } else {
+ plainWriter.reset();
+ }
+ state = NEW;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/package-info.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/**
+ * HPACK (Header Compression for HTTP/2) implementation conforming to
+ * <a href="https://tools.ietf.org/html/rfc7541">RFC 7541</a>.
+ *
+ * <p> Headers can be decoded and encoded by {@link sun.net.httpclient.hpack.Decoder}
+ * and {@link sun.net.httpclient.hpack.Encoder} respectively.
+ *
+ * <p> Instances of these classes are not safe for use by multiple threads.
+ */
+package sun.net.httpclient.hpack;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/HpackDriver.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8153353
+ * @modules java.httpclient/sun.net.httpclient.hpack
+ * @key randomness
+ * @compile/module=java.httpclient sun/net/httpclient/hpack/SpecHelper.java
+ * @compile/module=java.httpclient sun/net/httpclient/hpack/TestHelper.java
+ * @compile/module=java.httpclient sun/net/httpclient/hpack/BuffersTestingKit.java
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.BinaryPrimitivesTest
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.CircularBufferTest
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.DecoderTest
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.EncoderTest
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.HeaderTableTest
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.HuffmanTest
+ * @run testng/othervm -XaddReads:java.httpclient=ALL-UNNAMED java.httpclient/sun.net.httpclient.hpack.TestHelper
+ */
+public class HpackDriver { }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/BinaryPrimitivesTest.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+import static sun.net.httpclient.hpack.BuffersTestingKit.*;
+import static sun.net.httpclient.hpack.TestHelper.newRandom;
+
+//
+// Some of the tests below overlap in what they test. This allows to diagnose
+// bugs quicker and with less pain by simply ruling out common working bits.
+//
+public final class BinaryPrimitivesTest {
+
+ private final Random rnd = newRandom();
+
+ @Test
+ public void integerRead1() {
+ verifyRead(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
+ }
+
+ @Test
+ public void integerRead2() {
+ verifyRead(bytes(0b00001010), 10, 5);
+ }
+
+ @Test
+ public void integerRead3() {
+ verifyRead(bytes(0b00101010), 42, 8);
+ }
+
+ @Test
+ public void integerWrite1() {
+ verifyWrite(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
+ }
+
+ @Test
+ public void integerWrite2() {
+ verifyWrite(bytes(0b00001010), 10, 5);
+ }
+
+ @Test
+ public void integerWrite3() {
+ verifyWrite(bytes(0b00101010), 42, 8);
+ }
+
+ //
+ // Since readInteger(x) is the inverse of writeInteger(x), thus:
+ //
+ // for all x: readInteger(writeInteger(x)) == x
+ //
+ @Test
+ public void integerIdentity() {
+ final int MAX_VALUE = 1 << 22;
+ int totalCases = 0;
+ int maxFilling = 0;
+ IntegerReader r = new IntegerReader();
+ IntegerWriter w = new IntegerWriter();
+ ByteBuffer buf = ByteBuffer.allocate(8);
+ for (int N = 1; N < 9; N++) {
+ for (int expected = 0; expected <= MAX_VALUE; expected++) {
+ w.reset().configure(expected, N, 1).write(buf);
+ buf.flip();
+ totalCases++;
+ maxFilling = Math.max(maxFilling, buf.remaining());
+ r.reset().configure(N).read(buf);
+ assertEquals(r.get(), expected);
+ buf.clear();
+ }
+ }
+ System.out.printf("totalCases: %,d, maxFilling: %,d, maxValue: %,d%n",
+ totalCases, maxFilling, MAX_VALUE);
+ }
+
+ @Test
+ public void integerReadChunked() {
+ final int NUM_TESTS = 1024;
+ IntegerReader r = new IntegerReader();
+ ByteBuffer bb = ByteBuffer.allocate(8);
+ IntegerWriter w = new IntegerWriter();
+ for (int i = 0; i < NUM_TESTS; i++) {
+ final int N = 1 + rnd.nextInt(8);
+ final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
+ w.reset().configure(expected, N, rnd.nextInt()).write(bb);
+ bb.flip();
+
+ forEachSplit(bb,
+ (buffers) -> {
+ Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
+ r.configure(N);
+ for (ByteBuffer b : buf) {
+ r.read(b);
+ }
+ assertEquals(r.get(), expected);
+ r.reset();
+ });
+ bb.clear();
+ }
+ }
+
+ // FIXME: use maxValue in the test
+
+ @Test
+ // FIXME: tune values for better coverage
+ public void integerWriteChunked() {
+ ByteBuffer bb = ByteBuffer.allocate(6);
+ IntegerWriter w = new IntegerWriter();
+ IntegerReader r = new IntegerReader();
+ for (int i = 0; i < 1024; i++) { // number of tests
+ final int N = 1 + rnd.nextInt(8);
+ final int payload = rnd.nextInt(255);
+ final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
+
+ forEachSplit(bb,
+ (buffers) -> {
+ List<ByteBuffer> buf = new ArrayList<>();
+ relocateBuffers(injectEmptyBuffers(buffers)).forEach(buf::add);
+ boolean written = false;
+ w.configure(expected, N, payload); // TODO: test for payload it can be read after written
+ for (ByteBuffer b : buf) {
+ int pos = b.position();
+ written = w.write(b);
+ b.position(pos);
+ }
+ if (!written) {
+ fail("please increase bb size");
+ }
+ r.configure(N).read(concat(buf));
+ // TODO: check payload here
+ assertEquals(r.get(), expected);
+ w.reset();
+ r.reset();
+ bb.clear();
+ });
+ }
+ }
+
+
+ //
+ // Since readString(x) is the inverse of writeString(x), thus:
+ //
+ // for all x: readString(writeString(x)) == x
+ //
+ @Test
+ public void stringIdentity() {
+ final int MAX_STRING_LENGTH = 4096;
+ ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6); // it takes 6 bytes to encode string length of Integer.MAX_VALUE
+ CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
+ StringReader reader = new StringReader();
+ StringWriter writer = new StringWriter();
+ for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
+ for (int i = 0; i < 64; i++) {
+ // not so much "test in isolation", I know... we're testing .reset() as well
+ bytes.clear();
+ chars.clear();
+
+ byte[] b = new byte[len];
+ rnd.nextBytes(b);
+
+ String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
+
+ boolean written = writer
+ .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
+ .write(bytes);
+
+ if (!written) {
+ fail("please increase 'bytes' size");
+ }
+ bytes.flip();
+ reader.read(bytes, chars);
+ chars.flip();
+ assertEquals(chars.toString(), expected);
+ reader.reset();
+ writer.reset();
+ }
+ }
+ }
+
+// @Test
+// public void huffmanStringWriteChunked() {
+// fail();
+// }
+//
+// @Test
+// public void huffmanStringReadChunked() {
+// fail();
+// }
+
+ @Test
+ public void stringWriteChunked() {
+ final int MAX_STRING_LENGTH = 8;
+ final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
+ final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
+ final StringReader reader = new StringReader();
+ final StringWriter writer = new StringWriter();
+ for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
+
+ byte[] b = new byte[len];
+ rnd.nextBytes(b);
+
+ String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
+
+ forEachSplit(bytes, (buffers) -> {
+ writer.configure(expected, 0, expected.length(), false);
+ boolean written = false;
+ for (ByteBuffer buf : buffers) {
+ int p0 = buf.position();
+ written = writer.write(buf);
+ buf.position(p0);
+ }
+ if (!written) {
+ fail("please increase 'bytes' size");
+ }
+ reader.read(concat(buffers), chars);
+ chars.flip();
+ assertEquals(chars.toString(), expected);
+ reader.reset();
+ writer.reset();
+ chars.clear();
+ bytes.clear();
+ });
+ }
+ }
+
+ @Test
+ public void stringReadChunked() {
+ final int MAX_STRING_LENGTH = 16;
+ final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
+ final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
+ final StringReader reader = new StringReader();
+ final StringWriter writer = new StringWriter();
+ for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
+
+ byte[] b = new byte[len];
+ rnd.nextBytes(b);
+
+ String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string
+
+ boolean written = writer
+ .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
+ .write(bytes);
+ writer.reset();
+
+ if (!written) {
+ fail("please increase 'bytes' size");
+ }
+ bytes.flip();
+
+ forEachSplit(bytes, (buffers) -> {
+ for (ByteBuffer buf : buffers) {
+ int p0 = buf.position();
+ reader.read(buf, chars);
+ buf.position(p0);
+ }
+ chars.flip();
+ assertEquals(chars.toString(), expected);
+ reader.reset();
+ chars.clear();
+ });
+
+ bytes.clear();
+ }
+ }
+
+// @Test
+// public void test_Huffman_String_Identity() {
+// StringWriter writer = new StringWriter();
+// StringReader reader = new StringReader();
+// // 256 * 8 gives 2048 bits in case of plain 8 bit coding
+// // 256 * 30 gives you 7680 bits or 960 bytes in case of almost
+// // improbable event of 256 30 bits symbols in a row
+// ByteBuffer binary = ByteBuffer.allocate(960);
+// CharBuffer text = CharBuffer.allocate(960 / 5); // 5 = minimum code length
+// for (int len = 0; len < 128; len++) {
+// for (int i = 0; i < 256; i++) {
+// // not so much "test in isolation", I know...
+// binary.clear();
+//
+// byte[] bytes = new byte[len];
+// rnd.nextBytes(bytes);
+//
+// String s = new String(bytes, StandardCharsets.ISO_8859_1);
+//
+// writer.write(CharBuffer.wrap(s), binary, true);
+// binary.flip();
+// reader.read(binary, text);
+// text.flip();
+// assertEquals(text.toString(), s);
+// }
+// }
+// }
+
+ // TODO: atomic failures: e.g. readonly/overflow
+
+ private static byte[] bytes(int... data) {
+ byte[] bytes = new byte[data.length];
+ for (int i = 0; i < data.length; i++) {
+ bytes[i] = (byte) data[i];
+ }
+ return bytes;
+ }
+
+ private static void verifyRead(byte[] data, int expected, int N) {
+ ByteBuffer buf = ByteBuffer.wrap(data, 0, data.length);
+ IntegerReader reader = new IntegerReader();
+ reader.configure(N).read(buf);
+ assertEquals(expected, reader.get());
+ }
+
+ private void verifyWrite(byte[] expected, int data, int N) {
+ IntegerWriter w = new IntegerWriter();
+ ByteBuffer buf = ByteBuffer.allocate(2 * expected.length);
+ w.configure(data, N, 1).write(buf);
+ buf.flip();
+ assertEquals(ByteBuffer.wrap(expected), buf);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/BuffersTestingKit.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static java.nio.ByteBuffer.allocate;
+
+public final class BuffersTestingKit {
+
+ /**
+ * Relocates a {@code [position, limit)} region of the given buffer to
+ * corresponding region in a new buffer starting with provided {@code
+ * newPosition}.
+ *
+ * <p> Might be useful to make sure ByteBuffer's users do not rely on any
+ * absolute positions, but solely on what's reported by position(), limit().
+ *
+ * <p> The contents between the given buffer and the returned one are not
+ * shared.
+ */
+ public static ByteBuffer relocate(ByteBuffer buffer, int newPosition,
+ int newCapacity) {
+ int oldPosition = buffer.position();
+ int oldLimit = buffer.limit();
+
+ if (newPosition + oldLimit - oldPosition > newCapacity) {
+ throw new IllegalArgumentException();
+ }
+
+ ByteBuffer result;
+ if (buffer.isDirect()) {
+ result = ByteBuffer.allocateDirect(newCapacity);
+ } else {
+ result = allocate(newCapacity);
+ }
+
+ result.position(newPosition);
+ result.put(buffer).limit(result.position()).position(newPosition);
+ buffer.position(oldPosition);
+
+ if (buffer.isReadOnly()) {
+ return result.asReadOnlyBuffer();
+ }
+ return result;
+ }
+
+ public static Iterable<? extends ByteBuffer> relocateBuffers(
+ Iterable<? extends ByteBuffer> source) {
+ return () ->
+ new Iterator<ByteBuffer>() {
+
+ private final Iterator<? extends ByteBuffer> it = source.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return it.hasNext();
+ }
+
+ @Override
+ public ByteBuffer next() {
+ ByteBuffer buf = it.next();
+ int remaining = buf.remaining();
+ int newCapacity = remaining + random.nextInt(17);
+ int newPosition = random.nextInt(newCapacity - remaining + 1);
+ return relocate(buf, newPosition, newCapacity);
+ }
+ };
+ }
+
+ // TODO: not always of size 0 (it's fine for buffer to report !b.hasRemaining())
+ public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
+ Iterable<? extends ByteBuffer> source) {
+ return injectEmptyBuffers(source, () -> allocate(0));
+ }
+
+ public static Iterable<? extends ByteBuffer> injectEmptyBuffers(
+ Iterable<? extends ByteBuffer> source,
+ Supplier<? extends ByteBuffer> emptyBufferFactory) {
+
+ return () ->
+ new Iterator<ByteBuffer>() {
+
+ private final Iterator<? extends ByteBuffer> it = source.iterator();
+ private ByteBuffer next = calculateNext();
+
+ private ByteBuffer calculateNext() {
+ if (random.nextBoolean()) {
+ return emptyBufferFactory.get();
+ } else if (it.hasNext()) {
+ return it.next();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public ByteBuffer next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ ByteBuffer next = this.next;
+ this.next = calculateNext();
+ return next;
+ }
+ };
+ }
+
+ public static ByteBuffer concat(Iterable<? extends ByteBuffer> split) {
+ return concat(split, ByteBuffer::allocate);
+ }
+
+ public static ByteBuffer concat(Iterable<? extends ByteBuffer> split,
+ Function<? super Integer, ? extends ByteBuffer> concatBufferFactory) {
+ int size = 0;
+ for (ByteBuffer bb : split) {
+ size += bb.remaining();
+ }
+
+ ByteBuffer result = concatBufferFactory.apply(size);
+ for (ByteBuffer bb : split) {
+ result.put(bb);
+ }
+
+ result.flip();
+ return result;
+ }
+
+ public static void forEachSplit(ByteBuffer bb,
+ Consumer<? super Iterable<? extends ByteBuffer>> action) {
+ forEachSplit(bb.remaining(),
+ (lengths) -> {
+ int end = bb.position();
+ List<ByteBuffer> buffers = new LinkedList<>();
+ for (int len : lengths) {
+ ByteBuffer d = bb.duplicate();
+ d.position(end);
+ d.limit(end + len);
+ end += len;
+ buffers.add(d);
+ }
+ action.accept(buffers);
+ });
+ }
+
+ private static void forEachSplit(int n, Consumer<? super Iterable<? extends Integer>> action) {
+ forEachSplit(n, new Stack<>(), action);
+ }
+
+ private static void forEachSplit(int n, Stack<Integer> path,
+ Consumer<? super Iterable<? extends Integer>> action) {
+ if (n == 0) {
+ action.accept(path);
+ } else {
+ for (int i = 1; i <= n; i++) {
+ path.push(i);
+ forEachSplit(n - i, path, action);
+ path.pop();
+ }
+ }
+ }
+
+ private static final Random random = new Random();
+
+ private BuffersTestingKit() {
+ throw new InternalError();
+ }
+
+// public static void main(String[] args) {
+//
+// List<ByteBuffer> buffers = Arrays.asList(
+// (ByteBuffer) allocate(3).position(1).limit(2),
+// allocate(0),
+// allocate(7));
+//
+// Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
+// List<ByteBuffer> result = new ArrayList<>();
+// buf.forEach(result::add);
+// System.out.println(result);
+// }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/CircularBufferTest.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import sun.net.httpclient.hpack.HeaderTable.CircularBuffer;
+
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import static org.testng.Assert.assertEquals;
+import static sun.net.httpclient.hpack.TestHelper.newRandom;
+
+public final class CircularBufferTest {
+
+ private final Random r = newRandom();
+
+ @BeforeClass
+ public void setUp() {
+ r.setSeed(System.currentTimeMillis());
+ }
+
+ @Test
+ public void queue() {
+ for (int capacity = 1; capacity <= 2048; capacity++) {
+ queueOnce(capacity, 32);
+ }
+ }
+
+ @Test
+ public void resize() {
+ for (int capacity = 1; capacity <= 4096; capacity++) {
+ resizeOnce(capacity);
+ }
+ }
+
+ @Test
+ public void downSizeEmptyBuffer() {
+ CircularBuffer<Integer> buffer = new CircularBuffer<>(16);
+ buffer.resize(15);
+ }
+
+ private void resizeOnce(int capacity) {
+
+ int nextNumberToPut = 0;
+
+ Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
+ CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
+
+ // Fill full, so the next add will wrap
+ for (int i = 0; i < capacity; i++, nextNumberToPut++) {
+ buffer.add(nextNumberToPut);
+ referenceQueue.add(nextNumberToPut);
+ }
+ int gets = r.nextInt(capacity); // [0, capacity)
+ for (int i = 0; i < gets; i++) {
+ referenceQueue.poll();
+ buffer.remove();
+ }
+ int puts = r.nextInt(gets + 1); // [0, gets]
+ for (int i = 0; i < puts; i++, nextNumberToPut++) {
+ buffer.add(nextNumberToPut);
+ referenceQueue.add(nextNumberToPut);
+ }
+
+ Integer[] expected = referenceQueue.toArray(new Integer[0]);
+ buffer.resize(expected.length);
+
+ assertEquals(buffer.elements, expected);
+ }
+
+ private void queueOnce(int capacity, int numWraps) {
+
+ Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
+ CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
+
+ int nextNumberToPut = 0;
+ int totalPuts = 0;
+ int putsLimit = capacity * numWraps;
+ int remainingCapacity = capacity;
+ int size = 0;
+
+ while (totalPuts < putsLimit) {
+ assert remainingCapacity + size == capacity;
+ int puts = r.nextInt(remainingCapacity + 1); // [0, remainingCapacity]
+ remainingCapacity -= puts;
+ size += puts;
+ for (int i = 0; i < puts; i++, nextNumberToPut++) {
+ referenceQueue.add(nextNumberToPut);
+ buffer.add(nextNumberToPut);
+ }
+ totalPuts += puts;
+ int gets = r.nextInt(size + 1); // [0, size]
+ size -= gets;
+ remainingCapacity += gets;
+ for (int i = 0; i < gets; i++) {
+ Integer expected = referenceQueue.poll();
+ Integer actual = buffer.remove();
+ assertEquals(actual, expected);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/DecoderTest.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,595 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.Test;
+
+import java.io.UncheckedIOException;
+import java.net.ProtocolException;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static sun.net.httpclient.hpack.TestHelper.*;
+
+public final class DecoderTest {
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
+ //
+ @Test
+ public void example1() {
+ // @formatter:off
+ test("400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
+ "746f 6d2d 6865 6164 6572",
+
+ "[ 1] (s = 55) custom-key: custom-header\n" +
+ " Table size: 55",
+
+ "custom-key: custom-header");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
+ //
+ @Test
+ public void example2() {
+ // @formatter:off
+ test("040c 2f73 616d 706c 652f 7061 7468",
+ "empty.",
+ ":path: /sample/path");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
+ //
+ @Test
+ public void example3() {
+ // @formatter:off
+ test("1008 7061 7373 776f 7264 0673 6563 7265\n" +
+ "74",
+ "empty.",
+ "password: secret");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
+ //
+ @Test
+ public void example4() {
+ // @formatter:off
+ test("82",
+ "empty.",
+ ":method: GET");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.3
+ //
+ @Test
+ public void example5() {
+ // @formatter:off
+ Decoder d = new Decoder(256);
+
+ test(d, "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
+ "2e63 6f6d",
+
+ "[ 1] (s = 57) :authority: www.example.com\n" +
+ " Table size: 57",
+
+ ":method: GET\n" +
+ ":scheme: http\n" +
+ ":path: /\n" +
+ ":authority: www.example.com");
+
+ test(d, "8286 84be 5808 6e6f 2d63 6163 6865",
+
+ "[ 1] (s = 53) cache-control: no-cache\n" +
+ "[ 2] (s = 57) :authority: www.example.com\n" +
+ " Table size: 110",
+
+ ":method: GET\n" +
+ ":scheme: http\n" +
+ ":path: /\n" +
+ ":authority: www.example.com\n" +
+ "cache-control: no-cache");
+
+ test(d, "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
+ "0c63 7573 746f 6d2d 7661 6c75 65",
+
+ "[ 1] (s = 54) custom-key: custom-value\n" +
+ "[ 2] (s = 53) cache-control: no-cache\n" +
+ "[ 3] (s = 57) :authority: www.example.com\n" +
+ " Table size: 164",
+
+ ":method: GET\n" +
+ ":scheme: https\n" +
+ ":path: /index.html\n" +
+ ":authority: www.example.com\n" +
+ "custom-key: custom-value");
+
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.4
+ //
+ @Test
+ public void example6() {
+ // @formatter:off
+ Decoder d = new Decoder(256);
+
+ test(d, "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
+ "ff",
+
+ "[ 1] (s = 57) :authority: www.example.com\n" +
+ " Table size: 57",
+
+ ":method: GET\n" +
+ ":scheme: http\n" +
+ ":path: /\n" +
+ ":authority: www.example.com");
+
+ test(d, "8286 84be 5886 a8eb 1064 9cbf",
+
+ "[ 1] (s = 53) cache-control: no-cache\n" +
+ "[ 2] (s = 57) :authority: www.example.com\n" +
+ " Table size: 110",
+
+ ":method: GET\n" +
+ ":scheme: http\n" +
+ ":path: /\n" +
+ ":authority: www.example.com\n" +
+ "cache-control: no-cache");
+
+ test(d, "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
+ "a849 e95b b8e8 b4bf",
+
+ "[ 1] (s = 54) custom-key: custom-value\n" +
+ "[ 2] (s = 53) cache-control: no-cache\n" +
+ "[ 3] (s = 57) :authority: www.example.com\n" +
+ " Table size: 164",
+
+ ":method: GET\n" +
+ ":scheme: https\n" +
+ ":path: /index.html\n" +
+ ":authority: www.example.com\n" +
+ "custom-key: custom-value");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.5
+ //
+ @Test
+ public void example7() {
+ // @formatter:off
+ Decoder d = new Decoder(256);
+
+ test(d, "4803 3330 3258 0770 7269 7661 7465 611d\n" +
+ "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
+ "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
+ "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
+ "6c65 2e63 6f6d",
+
+ "[ 1] (s = 63) location: https://www.example.com\n" +
+ "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 3] (s = 52) cache-control: private\n" +
+ "[ 4] (s = 42) :status: 302\n" +
+ " Table size: 222",
+
+ ":status: 302\n" +
+ "cache-control: private\n" +
+ "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "location: https://www.example.com");
+
+ test(d, "4803 3330 37c1 c0bf",
+
+ "[ 1] (s = 42) :status: 307\n" +
+ "[ 2] (s = 63) location: https://www.example.com\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 4] (s = 52) cache-control: private\n" +
+ " Table size: 222",
+
+ ":status: 307\n" +
+ "cache-control: private\n" +
+ "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "location: https://www.example.com");
+
+ test(d, "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
+ "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
+ "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
+ "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
+ "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
+ "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
+ "3d31",
+
+ "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+ "[ 2] (s = 52) content-encoding: gzip\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+ " Table size: 215",
+
+ ":status: 200\n" +
+ "cache-control: private\n" +
+ "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+ "location: https://www.example.com\n" +
+ "content-encoding: gzip\n" +
+ "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.6
+ //
+ @Test
+ public void example8() {
+ // @formatter:off
+ Decoder d = new Decoder(256);
+
+ test(d, "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
+ "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
+ "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
+ "e9ae 82ae 43d3",
+
+ "[ 1] (s = 63) location: https://www.example.com\n" +
+ "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 3] (s = 52) cache-control: private\n" +
+ "[ 4] (s = 42) :status: 302\n" +
+ " Table size: 222",
+
+ ":status: 302\n" +
+ "cache-control: private\n" +
+ "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "location: https://www.example.com");
+
+ test(d, "4883 640e ffc1 c0bf",
+
+ "[ 1] (s = 42) :status: 307\n" +
+ "[ 2] (s = 63) location: https://www.example.com\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 4] (s = 52) cache-control: private\n" +
+ " Table size: 222",
+
+ ":status: 307\n" +
+ "cache-control: private\n" +
+ "date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "location: https://www.example.com");
+
+ test(d, "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
+ "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
+ "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
+ "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
+ "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
+
+ "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+ "[ 2] (s = 52) content-encoding: gzip\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+ " Table size: 215",
+
+ ":status: 200\n" +
+ "cache-control: private\n" +
+ "date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+ "location: https://www.example.com\n" +
+ "content-encoding: gzip\n" +
+ "set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
+ // @formatter:on
+ }
+
+ @Test
+ // One of responses from Apache Server that helped to catch a bug
+ public void testX() {
+ Decoder d = new Decoder(4096);
+ // @formatter:off
+ test(d, "3fe1 1f88 6196 d07a be94 03ea 693f 7504\n" +
+ "00b6 a05c b827 2e32 fa98 b46f 769e 86b1\n" +
+ "9272 b025 da5c 2ea9 fd70 a8de 7fb5 3556\n" +
+ "5ab7 6ece c057 02e2 2ad2 17bf 6c96 d07a\n" +
+ "be94 0854 cb6d 4a08 0075 40bd 71b6 6e05\n" +
+ "a531 68df 0f13 8efe 4522 cd32 21b6 5686\n" +
+ "eb23 781f cf52 848f d24a 8f0f 0d02 3435\n" +
+ "5f87 497c a589 d34d 1f",
+
+ "[ 1] (s = 53) content-type: text/html\n" +
+ "[ 2] (s = 50) accept-ranges: bytes\n" +
+ "[ 3] (s = 74) last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
+ "[ 4] (s = 77) server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
+ "[ 5] (s = 65) date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
+ " Table size: 319",
+
+ ":status: 200\n" +
+ "date: Mon, 09 Nov 2015 16:26:39 GMT\n" +
+ "server: Apache/2.4.17 (Unix) OpenSSL/1.0.2e-dev\n" +
+ "last-modified: Mon, 11 Jun 2007 18:53:14 GMT\n" +
+ "etag: \"2d-432a5e4a73a80\"\n" +
+ "accept-ranges: bytes\n" +
+ "content-length: 45\n" +
+ "content-type: text/html");
+ // @formatter:on
+ }
+
+ //
+ // This test is missing in the spec
+ //
+ @Test
+ public void sizeUpdate() {
+ Decoder d = new Decoder(4096);
+ assertEquals(d.getTable().maxSize(), 4096);
+ d.decode(ByteBuffer.wrap(new byte[]{0b00111110}), true, nopCallback()); // newSize = 30
+ assertEquals(d.getTable().maxSize(), 30);
+ }
+
+ @Test
+ public void incorrectSizeUpdate() {
+ ByteBuffer b = ByteBuffer.allocate(8);
+ Encoder e = new Encoder(8192) {
+ @Override
+ protected int calculateCapacity(int maxCapacity) {
+ return maxCapacity;
+ }
+ };
+ e.header("a", "b");
+ e.encode(b);
+ b.flip();
+ {
+ Decoder d = new Decoder(4096);
+ UncheckedIOException ex = assertVoidThrows(UncheckedIOException.class,
+ () -> d.decode(b, true, (name, value) -> { }));
+
+ assertNotNull(ex.getCause());
+ assertEquals(ex.getCause().getClass(), ProtocolException.class);
+ }
+ b.flip();
+ {
+ Decoder d = new Decoder(4096);
+ UncheckedIOException ex = assertVoidThrows(UncheckedIOException.class,
+ () -> d.decode(b, false, (name, value) -> { }));
+
+ assertNotNull(ex.getCause());
+ assertEquals(ex.getCause().getClass(), ProtocolException.class);
+ }
+ }
+
+ @Test
+ public void corruptedHeaderBlockInteger() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ (byte) 0b11111111, // indexed
+ (byte) 0b10011010 // 25 + ...
+ });
+ UncheckedIOException e = assertVoidThrows(UncheckedIOException.class,
+ () -> d.decode(data, true, nopCallback()));
+ assertNotNull(e.getCause());
+ assertEquals(e.getCause().getClass(), ProtocolException.class);
+ assertExceptionMessageContains(e, "Unexpected end of header block");
+ }
+
+ // 5.1. Integer Representation
+ // ...
+ // Integer encodings that exceed implementation limits -- in value or octet
+ // length -- MUST be treated as decoding errors. Different limits can
+ // be set for each of the different uses of integers, based on
+ // implementation constraints.
+ @Test
+ public void headerBlockIntegerNoOverflow() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ (byte) 0b11111111, // indexed + 127
+ // Integer.MAX_VALUE - 127 (base 128, little-endian):
+ (byte) 0b10000000,
+ (byte) 0b11111111,
+ (byte) 0b11111111,
+ (byte) 0b11111111,
+ (byte) 0b00000111
+ });
+
+ IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
+ () -> d.decode(data, true, nopCallback()));
+
+ assertExceptionMessageContains(e, "index=2147483647");
+ }
+
+ @Test
+ public void headerBlockIntegerOverflow() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ (byte) 0b11111111, // indexed + 127
+ // Integer.MAX_VALUE - 127 + 1 (base 128, little endian):
+ (byte) 0b10000001,
+ (byte) 0b11111111,
+ (byte) 0b11111111,
+ (byte) 0b11111111,
+ (byte) 0b00000111
+ });
+
+ IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
+ () -> d.decode(data, true, nopCallback()));
+
+ assertExceptionMessageContains(e, "Integer overflow");
+ }
+
+ @Test
+ public void corruptedHeaderBlockString1() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ 0b00001111, // literal, index=15
+ 0b00000000,
+ 0b00001000, // huffman=false, length=8
+ 0b00000000, // \
+ 0b00000000, // but only 3 octets available...
+ 0b00000000 // /
+ });
+ UncheckedIOException e = assertVoidThrows(UncheckedIOException.class,
+ () -> d.decode(data, true, nopCallback()));
+ assertNotNull(e.getCause());
+ assertEquals(e.getCause().getClass(), ProtocolException.class);
+ assertExceptionMessageContains(e, "Unexpected end of header block");
+ }
+
+ @Test
+ public void corruptedHeaderBlockString2() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ 0b00001111, // literal, index=15
+ 0b00000000,
+ (byte) 0b10001000, // huffman=true, length=8
+ 0b00000000, // \
+ 0b00000000, // \
+ 0b00000000, // but only 5 octets available...
+ 0b00000000, // /
+ 0b00000000 // /
+ });
+ UncheckedIOException e = assertVoidThrows(UncheckedIOException.class,
+ () -> d.decode(data, true, nopCallback()));
+ assertNotNull(e.getCause());
+ assertEquals(e.getCause().getClass(), ProtocolException.class);
+ assertExceptionMessageContains(e, "Unexpected end of header block");
+ }
+
+ // 5.2. String Literal Representation
+ // ...A Huffman-encoded string literal containing the EOS symbol MUST be
+ // treated as a decoding error...
+ @Test
+ public void corruptedHeaderBlockHuffmanStringEOS() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ 0b00001111, // literal, index=15
+ 0b00000000,
+ (byte) 0b10000110, // huffman=true, length=6
+ 0b00011001, 0b01001101, (byte) 0b11111111,
+ (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111100
+ });
+ IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
+ () -> d.decode(data, true, nopCallback()));
+
+ assertExceptionMessageContains(e, "Encountered EOS");
+ }
+
+ // 5.2. String Literal Representation
+ // ...A padding strictly longer than 7 bits MUST be treated as a decoding
+ // error...
+ @Test
+ public void corruptedHeaderBlockHuffmanStringLongPadding1() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ 0b00001111, // literal, index=15
+ 0b00000000,
+ (byte) 0b10000011, // huffman=true, length=3
+ 0b00011001, 0b01001101, (byte) 0b11111111
+ // len("aei") + len(padding) = (5 + 5 + 5) + (9)
+ });
+ IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
+ () -> d.decode(data, true, nopCallback()));
+
+ assertExceptionMessageContains(e, "Padding is too long", "len=9");
+ }
+
+ @Test
+ public void corruptedHeaderBlockHuffmanStringLongPadding2() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ 0b00001111, // literal, index=15
+ 0b00000000,
+ (byte) 0b10000011, // huffman=true, length=3
+ 0b00011001, 0b01111010, (byte) 0b11111111
+ // len("aek") + len(padding) = (5 + 5 + 7) + (7)
+ });
+ assertVoidDoesNotThrow(() -> d.decode(data, true, nopCallback()));
+ }
+
+ // 5.2. String Literal Representation
+ // ...A padding not corresponding to the most significant bits of the code
+ // for the EOS symbol MUST be treated as a decoding error...
+ @Test
+ public void corruptedHeaderBlockHuffmanStringNotEOSPadding() {
+ Decoder d = new Decoder(4096);
+ ByteBuffer data = ByteBuffer.wrap(new byte[]{
+ 0b00001111, // literal, index=15
+ 0b00000000,
+ (byte) 0b10000011, // huffman=true, length=3
+ 0b00011001, 0b01111010, (byte) 0b11111110
+ });
+ IllegalArgumentException e = assertVoidThrows(IllegalArgumentException.class,
+ () -> d.decode(data, true, nopCallback()));
+
+ assertExceptionMessageContains(e, "Not a EOS prefix");
+ }
+
+ @Test
+ public void argsTestBiConsumerIsNull() {
+ Decoder decoder = new Decoder(4096);
+ assertVoidThrows(NullPointerException.class,
+ () -> decoder.decode(ByteBuffer.allocate(16), true, null));
+ }
+
+ @Test
+ public void argsTestByteBufferIsNull() {
+ Decoder decoder = new Decoder(4096);
+ assertVoidThrows(NullPointerException.class,
+ () -> decoder.decode(null, true, nopCallback()));
+ }
+
+ @Test
+ public void argsTestBothAreNull() {
+ Decoder decoder = new Decoder(4096);
+ assertVoidThrows(NullPointerException.class,
+ () -> decoder.decode(null, true, null));
+ }
+
+ private static void test(String hexdump,
+ String headerTable, String headerList) {
+ test(new Decoder(4096), hexdump, headerTable, headerList);
+ }
+
+ //
+ // Sometimes we need to keep the same decoder along several runs,
+ // as it models the same connection
+ //
+ private static void test(Decoder d, String hexdump,
+ String expectedHeaderTable, String expectedHeaderList) {
+
+ ByteBuffer source = SpecHelper.toBytes(hexdump);
+
+ List<String> actual = new LinkedList<>();
+ d.decode(source, true, (name, value) -> {
+ if (value == null) {
+ actual.add(name.toString());
+ } else {
+ actual.add(name + ": " + value);
+ }
+ });
+
+ assertEquals(d.getTable().getStateString(), expectedHeaderTable);
+ assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
+ }
+
+ private static DecodingCallback nopCallback() {
+ return (t, u) -> { };
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/EncoderTest.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,623 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.Test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import static java.util.Arrays.asList;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static sun.net.httpclient.hpack.SpecHelper.toHexdump;
+import static sun.net.httpclient.hpack.TestHelper.assertVoidThrows;
+
+// TODO: map textual representation of commands from the spec to actual
+// calls to encoder (actually, this is a good idea for decoder as well)
+public final class EncoderTest {
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.1
+ //
+ @Test
+ public void example1() {
+
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ e.literalWithIndexing("custom-key", false, "custom-header", false);
+ // @formatter:off
+ test(e,
+
+ "400a 6375 7374 6f6d 2d6b 6579 0d63 7573\n" +
+ "746f 6d2d 6865 6164 6572",
+
+ "[ 1] (s = 55) custom-key: custom-header\n" +
+ " Table size: 55");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.2
+ //
+ @Test
+ public void example2() {
+
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ e.literal(4, "/sample/path", false);
+ // @formatter:off
+ test(e,
+
+ "040c 2f73 616d 706c 652f 7061 7468",
+
+ "empty.");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.3
+ //
+ @Test
+ public void example3() {
+
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ e.literalNeverIndexed("password", false, "secret", false);
+ // @formatter:off
+ test(e,
+
+ "1008 7061 7373 776f 7264 0673 6563 7265\n" +
+ "74",
+
+ "empty.");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.2.4
+ //
+ @Test
+ public void example4() {
+
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ e.indexed(2);
+ // @formatter:off
+ test(e,
+
+ "82",
+
+ "empty.");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.3
+ //
+ @Test
+ public void example5() {
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ ByteBuffer output = ByteBuffer.allocate(64);
+ e.indexed(2);
+ e.encode(output);
+ e.indexed(6);
+ e.encode(output);
+ e.indexed(4);
+ e.encode(output);
+ e.literalWithIndexing(1, "www.example.com", false);
+ e.encode(output);
+
+ output.flip();
+
+ // @formatter:off
+ test(e, output,
+
+ "8286 8441 0f77 7777 2e65 7861 6d70 6c65\n" +
+ "2e63 6f6d",
+
+ "[ 1] (s = 57) :authority: www.example.com\n" +
+ " Table size: 57");
+
+ output.clear();
+
+ e.indexed( 2);
+ e.encode(output);
+ e.indexed( 6);
+ e.encode(output);
+ e.indexed( 4);
+ e.encode(output);
+ e.indexed(62);
+ e.encode(output);
+ e.literalWithIndexing(24, "no-cache", false);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "8286 84be 5808 6e6f 2d63 6163 6865",
+
+ "[ 1] (s = 53) cache-control: no-cache\n" +
+ "[ 2] (s = 57) :authority: www.example.com\n" +
+ " Table size: 110");
+
+ output.clear();
+
+ e.indexed( 2);
+ e.encode(output);
+ e.indexed( 7);
+ e.encode(output);
+ e.indexed( 5);
+ e.encode(output);
+ e.indexed(63);
+ e.encode(output);
+ e.literalWithIndexing("custom-key", false, "custom-value", false);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "8287 85bf 400a 6375 7374 6f6d 2d6b 6579\n" +
+ "0c63 7573 746f 6d2d 7661 6c75 65",
+
+ "[ 1] (s = 54) custom-key: custom-value\n" +
+ "[ 2] (s = 53) cache-control: no-cache\n" +
+ "[ 3] (s = 57) :authority: www.example.com\n" +
+ " Table size: 164");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.4
+ //
+ @Test
+ public void example6() {
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ ByteBuffer output = ByteBuffer.allocate(64);
+ e.indexed(2);
+ e.encode(output);
+ e.indexed(6);
+ e.encode(output);
+ e.indexed(4);
+ e.encode(output);
+ e.literalWithIndexing(1, "www.example.com", true);
+ e.encode(output);
+
+ output.flip();
+
+ // @formatter:off
+ test(e, output,
+
+ "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4\n" +
+ "ff",
+
+ "[ 1] (s = 57) :authority: www.example.com\n" +
+ " Table size: 57");
+
+ output.clear();
+
+ e.indexed( 2);
+ e.encode(output);
+ e.indexed( 6);
+ e.encode(output);
+ e.indexed( 4);
+ e.encode(output);
+ e.indexed(62);
+ e.encode(output);
+ e.literalWithIndexing(24, "no-cache", true);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "8286 84be 5886 a8eb 1064 9cbf",
+
+ "[ 1] (s = 53) cache-control: no-cache\n" +
+ "[ 2] (s = 57) :authority: www.example.com\n" +
+ " Table size: 110");
+
+ output.clear();
+
+ e.indexed( 2);
+ e.encode(output);
+ e.indexed( 7);
+ e.encode(output);
+ e.indexed( 5);
+ e.encode(output);
+ e.indexed(63);
+ e.encode(output);
+ e.literalWithIndexing("custom-key", true, "custom-value", true);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925\n" +
+ "a849 e95b b8e8 b4bf",
+
+ "[ 1] (s = 54) custom-key: custom-value\n" +
+ "[ 2] (s = 53) cache-control: no-cache\n" +
+ "[ 3] (s = 57) :authority: www.example.com\n" +
+ " Table size: 164");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.5
+ //
+ @Test
+ public void example7() {
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ ByteBuffer output = ByteBuffer.allocate(128);
+ // @formatter:off
+ e.literalWithIndexing( 8, "302", false);
+ e.encode(output);
+ e.literalWithIndexing(24, "private", false);
+ e.encode(output);
+ e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", false);
+ e.encode(output);
+ e.literalWithIndexing(46, "https://www.example.com", false);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "4803 3330 3258 0770 7269 7661 7465 611d\n" +
+ "4d6f 6e2c 2032 3120 4f63 7420 3230 3133\n" +
+ "2032 303a 3133 3a32 3120 474d 546e 1768\n" +
+ "7474 7073 3a2f 2f77 7777 2e65 7861 6d70\n" +
+ "6c65 2e63 6f6d",
+
+ "[ 1] (s = 63) location: https://www.example.com\n" +
+ "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 3] (s = 52) cache-control: private\n" +
+ "[ 4] (s = 42) :status: 302\n" +
+ " Table size: 222");
+
+ output.clear();
+
+ e.literalWithIndexing( 8, "307", false);
+ e.encode(output);
+ e.indexed(65);
+ e.encode(output);
+ e.indexed(64);
+ e.encode(output);
+ e.indexed(63);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "4803 3330 37c1 c0bf",
+
+ "[ 1] (s = 42) :status: 307\n" +
+ "[ 2] (s = 63) location: https://www.example.com\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 4] (s = 52) cache-control: private\n" +
+ " Table size: 222");
+
+ output.clear();
+
+ e.indexed( 8);
+ e.encode(output);
+ e.indexed(65);
+ e.encode(output);
+ e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", false);
+ e.encode(output);
+ e.indexed(64);
+ e.encode(output);
+ e.literalWithIndexing(26, "gzip", false);
+ e.encode(output);
+ e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", false);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420\n" +
+ "3230 3133 2032 303a 3133 3a32 3220 474d\n" +
+ "54c0 5a04 677a 6970 7738 666f 6f3d 4153\n" +
+ "444a 4b48 514b 425a 584f 5157 454f 5049\n" +
+ "5541 5851 5745 4f49 553b 206d 6178 2d61\n" +
+ "6765 3d33 3630 303b 2076 6572 7369 6f6e\n" +
+ "3d31",
+
+ "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+ "[ 2] (s = 52) content-encoding: gzip\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+ " Table size: 215");
+ // @formatter:on
+ }
+
+ //
+ // http://tools.ietf.org/html/rfc7541#appendix-C.6
+ //
+ @Test
+ public void example8() {
+ Encoder e = newCustomEncoder(256);
+ drainInitialUpdate(e);
+
+ ByteBuffer output = ByteBuffer.allocate(128);
+ // @formatter:off
+ e.literalWithIndexing( 8, "302", true);
+ e.encode(output);
+ e.literalWithIndexing(24, "private", true);
+ e.encode(output);
+ e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:21 GMT", true);
+ e.encode(output);
+ e.literalWithIndexing(46, "https://www.example.com", true);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "4882 6402 5885 aec3 771a 4b61 96d0 7abe\n" +
+ "9410 54d4 44a8 2005 9504 0b81 66e0 82a6\n" +
+ "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8\n" +
+ "e9ae 82ae 43d3",
+
+ "[ 1] (s = 63) location: https://www.example.com\n" +
+ "[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 3] (s = 52) cache-control: private\n" +
+ "[ 4] (s = 42) :status: 302\n" +
+ " Table size: 222");
+
+ output.clear();
+
+ e.literalWithIndexing( 8, "307", true);
+ e.encode(output);
+ e.indexed(65);
+ e.encode(output);
+ e.indexed(64);
+ e.encode(output);
+ e.indexed(63);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "4883 640e ffc1 c0bf",
+
+ "[ 1] (s = 42) :status: 307\n" +
+ "[ 2] (s = 63) location: https://www.example.com\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT\n" +
+ "[ 4] (s = 52) cache-control: private\n" +
+ " Table size: 222");
+
+ output.clear();
+
+ e.indexed( 8);
+ e.encode(output);
+ e.indexed(65);
+ e.encode(output);
+ e.literalWithIndexing(33, "Mon, 21 Oct 2013 20:13:22 GMT", true);
+ e.encode(output);
+ e.indexed(64);
+ e.encode(output);
+ e.literalWithIndexing(26, "gzip", true);
+ e.encode(output);
+ e.literalWithIndexing(55, "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", true);
+ e.encode(output);
+
+ output.flip();
+
+ test(e, output,
+
+ "88c1 6196 d07a be94 1054 d444 a820 0595\n" +
+ "040b 8166 e084 a62d 1bff c05a 839b d9ab\n" +
+ "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b\n" +
+ "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f\n" +
+ "9587 3160 65c0 03ed 4ee5 b106 3d50 07",
+
+ "[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1\n" +
+ "[ 2] (s = 52) content-encoding: gzip\n" +
+ "[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT\n" +
+ " Table size: 215");
+ // @formatter:on
+ }
+
+ @Test
+ public void initialSizeUpdateDefaultEncoder() {
+ Function<Integer, Encoder> e = Encoder::new;
+ testSizeUpdate(e, 1024, asList(), asList(0));
+ testSizeUpdate(e, 1024, asList(1024), asList(0));
+ testSizeUpdate(e, 1024, asList(1024, 1024), asList(0));
+ testSizeUpdate(e, 1024, asList(1024, 512), asList(0));
+ testSizeUpdate(e, 1024, asList(512, 1024), asList(0));
+ testSizeUpdate(e, 1024, asList(512, 2048), asList(0));
+ }
+
+ @Test
+ public void initialSizeUpdateCustomEncoder() {
+ Function<Integer, Encoder> e = EncoderTest::newCustomEncoder;
+ testSizeUpdate(e, 1024, asList(), asList(1024));
+ testSizeUpdate(e, 1024, asList(1024), asList(1024));
+ testSizeUpdate(e, 1024, asList(1024, 1024), asList(1024));
+ testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
+ testSizeUpdate(e, 1024, asList(512, 1024), asList(1024));
+ testSizeUpdate(e, 1024, asList(512, 2048), asList(2048));
+ }
+
+ @Test
+ public void seriesOfSizeUpdatesDefaultEncoder() {
+ Function<Integer, Encoder> e = c -> {
+ Encoder encoder = new Encoder(c);
+ drainInitialUpdate(encoder);
+ return encoder;
+ };
+ testSizeUpdate(e, 0, asList(0), asList());
+ testSizeUpdate(e, 1024, asList(1024), asList());
+ testSizeUpdate(e, 1024, asList(2048), asList());
+ testSizeUpdate(e, 1024, asList(512), asList());
+ testSizeUpdate(e, 1024, asList(1024, 1024), asList());
+ testSizeUpdate(e, 1024, asList(1024, 2048), asList());
+ testSizeUpdate(e, 1024, asList(2048, 1024), asList());
+ testSizeUpdate(e, 1024, asList(1024, 512), asList());
+ testSizeUpdate(e, 1024, asList(512, 1024), asList());
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#section-4.2
+ //
+ @Test
+ public void seriesOfSizeUpdatesCustomEncoder() {
+ Function<Integer, Encoder> e = c -> {
+ Encoder encoder = newCustomEncoder(c);
+ drainInitialUpdate(encoder);
+ return encoder;
+ };
+ testSizeUpdate(e, 0, asList(0), asList());
+ testSizeUpdate(e, 1024, asList(1024), asList());
+ testSizeUpdate(e, 1024, asList(2048), asList(2048));
+ testSizeUpdate(e, 1024, asList(512), asList(512));
+ testSizeUpdate(e, 1024, asList(1024, 1024), asList());
+ testSizeUpdate(e, 1024, asList(1024, 2048), asList(2048));
+ testSizeUpdate(e, 1024, asList(2048, 1024), asList());
+ testSizeUpdate(e, 1024, asList(1024, 512), asList(512));
+ testSizeUpdate(e, 1024, asList(512, 1024), asList(512, 1024));
+ }
+
+ @Test
+ public void callSequenceViolations() {
+ { // Hasn't set up a header
+ Encoder e = new Encoder(0);
+ assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
+ }
+ { // Can't set up header while there's an unfinished encoding
+ Encoder e = new Encoder(0);
+ e.indexed(32);
+ assertVoidThrows(IllegalStateException.class, () -> e.indexed(32));
+ }
+ { // Can't setMaxCapacity while there's an unfinished encoding
+ Encoder e = new Encoder(0);
+ e.indexed(32);
+ assertVoidThrows(IllegalStateException.class, () -> e.setMaxCapacity(512));
+ }
+ { // Hasn't set up a header
+ Encoder e = new Encoder(0);
+ e.setMaxCapacity(256);
+ assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
+ }
+ { // Hasn't set up a header after the previous encoding
+ Encoder e = new Encoder(0);
+ e.indexed(0);
+ boolean encoded = e.encode(ByteBuffer.allocate(16));
+ assertTrue(encoded); // assumption
+ assertVoidThrows(IllegalStateException.class, () -> e.encode(ByteBuffer.allocate(16)));
+ }
+ }
+
+ private static void test(Encoder encoder,
+ String expectedTableState,
+ String expectedHexdump) {
+
+ ByteBuffer b = ByteBuffer.allocate(128);
+ encoder.encode(b);
+ b.flip();
+ test(encoder, b, expectedTableState, expectedHexdump);
+ }
+
+ private static void test(Encoder encoder,
+ ByteBuffer output,
+ String expectedHexdump,
+ String expectedTableState) {
+
+ String actualTableState = encoder.getHeaderTable().getStateString();
+ assertEquals(actualTableState, expectedTableState);
+
+ String actualHexdump = toHexdump(output);
+ assertEquals(actualHexdump, expectedHexdump.replaceAll("\\n", " "));
+ }
+
+ // initial size - the size encoder is constructed with
+ // updates - a sequence of values for consecutive calls to encoder.setMaxCapacity
+ // expected - a sequence of values expected to be decoded by a decoder
+ private void testSizeUpdate(Function<Integer, Encoder> encoder,
+ int initialSize,
+ List<Integer> updates,
+ List<Integer> expected) {
+ Encoder e = encoder.apply(initialSize);
+ updates.forEach(e::setMaxCapacity);
+ ByteBuffer b = ByteBuffer.allocate(64);
+ e.header("a", "b");
+ e.encode(b);
+ b.flip();
+ Decoder d = new Decoder(updates.isEmpty() ? initialSize : Collections.max(updates));
+ List<Integer> actual = new ArrayList<>();
+ d.decode(b, true, new DecodingCallback() {
+ @Override
+ public void onDecoded(CharSequence name, CharSequence value) { }
+
+ @Override
+ public void onSizeUpdate(int capacity) {
+ actual.add(capacity);
+ }
+ });
+ assertEquals(actual, expected);
+ }
+
+ //
+ // Default encoder does not need any table, therefore a subclass that
+ // behaves differently is needed
+ //
+ private static Encoder newCustomEncoder(int maxCapacity) {
+ return new Encoder(maxCapacity) {
+ @Override
+ protected int calculateCapacity(int maxCapacity) {
+ return maxCapacity;
+ }
+ };
+ }
+
+ private static void drainInitialUpdate(Encoder e) {
+ ByteBuffer b = ByteBuffer.allocate(4);
+ e.header("a", "b");
+ boolean done;
+ do {
+ done = e.encode(b);
+ b.flip();
+ } while (!done);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/HeaderTableTest.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,375 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.Test;
+import sun.net.httpclient.hpack.HeaderTable.HeaderField;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.String.format;
+import static org.testng.Assert.assertEquals;
+import static sun.net.httpclient.hpack.TestHelper.*;
+
+public class HeaderTableTest {
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-A
+ //
+ // @formatter:off
+ private static final String SPEC =
+ " | 1 | :authority | |\n" +
+ " | 2 | :method | GET |\n" +
+ " | 3 | :method | POST |\n" +
+ " | 4 | :path | / |\n" +
+ " | 5 | :path | /index.html |\n" +
+ " | 6 | :scheme | http |\n" +
+ " | 7 | :scheme | https |\n" +
+ " | 8 | :status | 200 |\n" +
+ " | 9 | :status | 204 |\n" +
+ " | 10 | :status | 206 |\n" +
+ " | 11 | :status | 304 |\n" +
+ " | 12 | :status | 400 |\n" +
+ " | 13 | :status | 404 |\n" +
+ " | 14 | :status | 500 |\n" +
+ " | 15 | accept-charset | |\n" +
+ " | 16 | accept-encoding | gzip, deflate |\n" +
+ " | 17 | accept-language | |\n" +
+ " | 18 | accept-ranges | |\n" +
+ " | 19 | accept | |\n" +
+ " | 20 | access-control-allow-origin | |\n" +
+ " | 21 | age | |\n" +
+ " | 22 | allow | |\n" +
+ " | 23 | authorization | |\n" +
+ " | 24 | cache-control | |\n" +
+ " | 25 | content-disposition | |\n" +
+ " | 26 | content-encoding | |\n" +
+ " | 27 | content-language | |\n" +
+ " | 28 | content-length | |\n" +
+ " | 29 | content-location | |\n" +
+ " | 30 | content-range | |\n" +
+ " | 31 | content-type | |\n" +
+ " | 32 | cookie | |\n" +
+ " | 33 | date | |\n" +
+ " | 34 | etag | |\n" +
+ " | 35 | expect | |\n" +
+ " | 36 | expires | |\n" +
+ " | 37 | from | |\n" +
+ " | 38 | host | |\n" +
+ " | 39 | if-match | |\n" +
+ " | 40 | if-modified-since | |\n" +
+ " | 41 | if-none-match | |\n" +
+ " | 42 | if-range | |\n" +
+ " | 43 | if-unmodified-since | |\n" +
+ " | 44 | last-modified | |\n" +
+ " | 45 | link | |\n" +
+ " | 46 | location | |\n" +
+ " | 47 | max-forwards | |\n" +
+ " | 48 | proxy-authenticate | |\n" +
+ " | 49 | proxy-authorization | |\n" +
+ " | 50 | range | |\n" +
+ " | 51 | referer | |\n" +
+ " | 52 | refresh | |\n" +
+ " | 53 | retry-after | |\n" +
+ " | 54 | server | |\n" +
+ " | 55 | set-cookie | |\n" +
+ " | 56 | strict-transport-security | |\n" +
+ " | 57 | transfer-encoding | |\n" +
+ " | 58 | user-agent | |\n" +
+ " | 59 | vary | |\n" +
+ " | 60 | via | |\n" +
+ " | 61 | www-authenticate | |\n";
+ // @formatter:on
+
+ private static final int STATIC_TABLE_LENGTH = createStaticEntries().size();
+ private final Random rnd = newRandom();
+
+ @Test
+ public void staticData() {
+ HeaderTable table = new HeaderTable(0);
+ Map<Integer, HeaderField> staticHeaderFields = createStaticEntries();
+
+ Map<String, Integer> minimalIndexes = new HashMap<>();
+
+ for (Map.Entry<Integer, HeaderField> e : staticHeaderFields.entrySet()) {
+ Integer idx = e.getKey();
+ String hName = e.getValue().name;
+ Integer midx = minimalIndexes.get(hName);
+ if (midx == null) {
+ minimalIndexes.put(hName, idx);
+ } else {
+ minimalIndexes.put(hName, Math.min(idx, midx));
+ }
+ }
+
+ staticHeaderFields.entrySet().forEach(
+ e -> {
+ // lookup
+ HeaderField actualHeaderField = table.get(e.getKey());
+ HeaderField expectedHeaderField = e.getValue();
+ assertEquals(actualHeaderField, expectedHeaderField);
+
+ // reverse lookup (name, value)
+ String hName = expectedHeaderField.name;
+ String hValue = expectedHeaderField.value;
+ int expectedIndex = e.getKey();
+ int actualIndex = table.indexOf(hName, hValue);
+
+ assertEquals(actualIndex, expectedIndex);
+
+ // reverse lookup (name)
+ int expectedMinimalIndex = minimalIndexes.get(hName);
+ int actualMinimalIndex = table.indexOf(hName, "blah-blah");
+
+ assertEquals(-actualMinimalIndex, expectedMinimalIndex);
+ }
+ );
+ }
+
+ @Test
+ public void constructorSetsMaxSize() {
+ int size = rnd.nextInt(64);
+ HeaderTable t = new HeaderTable(size);
+ assertEquals(t.size(), 0);
+ assertEquals(t.maxSize(), size);
+ }
+
+ @Test
+ public void negativeMaximumSize() {
+ int maxSize = -(rnd.nextInt(100) + 1); // [-100, -1]
+ IllegalArgumentException e =
+ assertVoidThrows(IllegalArgumentException.class,
+ () -> new HeaderTable(0).setMaxSize(maxSize));
+ assertExceptionMessageContains(e, "maxSize");
+ }
+
+ @Test
+ public void zeroMaximumSize() {
+ HeaderTable table = new HeaderTable(0);
+ table.setMaxSize(0);
+ assertEquals(table.maxSize(), 0);
+ }
+
+ @Test
+ public void negativeIndex() {
+ int idx = -(rnd.nextInt(256) + 1); // [-256, -1]
+ IllegalArgumentException e =
+ assertVoidThrows(IllegalArgumentException.class,
+ () -> new HeaderTable(0).get(idx));
+ assertExceptionMessageContains(e, "index");
+ }
+
+ @Test
+ public void zeroIndex() {
+ IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class,
+ () -> new HeaderTable(0).get(0));
+ assertExceptionMessageContains(e, "index");
+ }
+
+ @Test
+ public void length() {
+ HeaderTable table = new HeaderTable(0);
+ assertEquals(table.length(), STATIC_TABLE_LENGTH);
+ }
+
+ @Test
+ public void indexOutsideStaticRange() {
+ HeaderTable table = new HeaderTable(0);
+ int idx = table.length() + (rnd.nextInt(256) + 1);
+ IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class,
+ () -> table.get(idx));
+ assertExceptionMessageContains(e, "index");
+ }
+
+ @Test
+ public void entryPutAfterStaticArea() {
+ HeaderTable table = new HeaderTable(256);
+ int idx = table.length() + 1;
+ assertThrows(IllegalArgumentException.class, () -> table.get(idx));
+
+ byte[] bytes = new byte[32];
+ rnd.nextBytes(bytes);
+ String name = new String(bytes, StandardCharsets.ISO_8859_1);
+ String value = "custom-value";
+
+ table.put(name, value);
+ HeaderField f = table.get(idx);
+ assertEquals(name, f.name);
+ assertEquals(value, f.value);
+ }
+
+ @Test
+ public void staticTableHasZeroSize() {
+ HeaderTable table = new HeaderTable(0);
+ assertEquals(0, table.size());
+ }
+
+ @Test
+ public void lowerIndexPriority() {
+ HeaderTable table = new HeaderTable(256);
+ int oldLength = table.length();
+ table.put("bender", "rodriguez");
+ table.put("bender", "rodriguez");
+ table.put("bender", "rodriguez");
+
+ assertEquals(table.length(), oldLength + 3); // more like an assumption
+ int i = table.indexOf("bender", "rodriguez");
+ assertEquals(oldLength + 1, i);
+ }
+
+ @Test
+ public void lowerIndexPriority2() {
+ HeaderTable table = new HeaderTable(256);
+ int oldLength = table.length();
+ int idx = rnd.nextInt(oldLength) + 1;
+ HeaderField f = table.get(idx);
+ table.put(f.name, f.value);
+ assertEquals(table.length(), oldLength + 1);
+ int i = table.indexOf(f.name, f.value);
+ assertEquals(idx, i);
+ }
+
+ // TODO: negative indexes check
+ // TODO: ensure full table clearance when adding huge header field
+ // TODO: ensure eviction deletes minimum needed entries, not more
+
+ @Test
+ public void fifo() {
+ HeaderTable t = new HeaderTable(Integer.MAX_VALUE);
+ // Let's add a series of header fields
+ int NUM_HEADERS = 32;
+ for (int i = 1; i <= NUM_HEADERS; i++) {
+ String s = String.valueOf(i);
+ t.put(s, s);
+ }
+ // They MUST appear in a FIFO order:
+ // newer entries are at lower indexes
+ // older entries are at higher indexes
+ for (int j = 1; j <= NUM_HEADERS; j++) {
+ HeaderField f = t.get(STATIC_TABLE_LENGTH + j);
+ int actualName = Integer.parseInt(f.name);
+ int expectedName = NUM_HEADERS - j + 1;
+ assertEquals(expectedName, actualName);
+ }
+ // Entries MUST be evicted in the order they were added:
+ // the newer the entry the later it is evicted
+ for (int k = 1; k <= NUM_HEADERS; k++) {
+ HeaderField f = t.evictEntry();
+ assertEquals(String.valueOf(k), f.name);
+ }
+ }
+
+ @Test
+ public void indexOf() {
+ HeaderTable t = new HeaderTable(Integer.MAX_VALUE);
+ // Let's put a series of header fields
+ int NUM_HEADERS = 32;
+ for (int i = 1; i <= NUM_HEADERS; i++) {
+ String s = String.valueOf(i);
+ t.put(s, s);
+ }
+ // and verify indexOf (reverse lookup) returns correct indexes for
+ // full lookup
+ for (int j = 1; j <= NUM_HEADERS; j++) {
+ String s = String.valueOf(j);
+ int actualIndex = t.indexOf(s, s);
+ int expectedIndex = STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1;
+ assertEquals(expectedIndex, actualIndex);
+ }
+ // as well as for just a name lookup
+ for (int j = 1; j <= NUM_HEADERS; j++) {
+ String s = String.valueOf(j);
+ int actualIndex = t.indexOf(s, "blah");
+ int expectedIndex = -(STATIC_TABLE_LENGTH + NUM_HEADERS - j + 1);
+ assertEquals(expectedIndex, actualIndex);
+ }
+ // lookup for non-existent name returns 0
+ assertEquals(0, t.indexOf("chupacabra", "1"));
+ }
+
+ @Test
+ public void testToString() {
+ HeaderTable table = new HeaderTable(0);
+ {
+ table.setMaxSize(2048);
+ assertEquals("entries: 0; used 0/2048 (0.0%)", table.toString());
+ }
+
+ {
+ String name = "custom-name";
+ String value = "custom-value";
+ int size = 512;
+
+ table.setMaxSize(size);
+ table.put(name, value);
+ String s = table.toString();
+
+ int used = name.length() + value.length() + 32;
+ double ratio = used * 100.0 / size;
+
+ String expected = format("entries: 1; used %s/%s (%.1f%%)", used, size, ratio);
+ assertEquals(expected, s);
+ }
+
+ {
+ table.setMaxSize(78);
+ table.put(":method", "");
+ table.put(":status", "");
+ String s = table.toString();
+ assertEquals("entries: 2; used 78/78 (100.0%)", s);
+ }
+ }
+
+ @Test
+ public void stateString() {
+ HeaderTable table = new HeaderTable(256);
+ table.put("custom-key", "custom-header");
+ // @formatter:off
+ assertEquals("[ 1] (s = 55) custom-key: custom-header\n" +
+ " Table size: 55", table.getStateString());
+ // @formatter:on
+ }
+
+ private static Map<Integer, HeaderField> createStaticEntries() {
+ Pattern line = Pattern.compile(
+ "\\|\\s*(?<index>\\d+?)\\s*\\|\\s*(?<name>.+?)\\s*\\|\\s*(?<value>.*?)\\s*\\|");
+ Matcher m = line.matcher(SPEC);
+ Map<Integer, HeaderField> result = new HashMap<>();
+ while (m.find()) {
+ int index = Integer.parseInt(m.group("index"));
+ String name = m.group("name");
+ String value = m.group("value");
+ HeaderField f = new HeaderField(name, value);
+ result.put(index, f);
+ }
+ return Collections.unmodifiableMap(result); // lol
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/HuffmanTest.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,623 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.Integer.parseInt;
+import static org.testng.Assert.*;
+
+public final class HuffmanTest {
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-B
+ //
+ private static final String SPEC =
+ // @formatter:off
+ " code as bits as hex len\n" +
+ " sym aligned to MSB aligned in\n" +
+ " to LSB bits\n" +
+ " ( 0) |11111111|11000 1ff8 [13]\n" +
+ " ( 1) |11111111|11111111|1011000 7fffd8 [23]\n" +
+ " ( 2) |11111111|11111111|11111110|0010 fffffe2 [28]\n" +
+ " ( 3) |11111111|11111111|11111110|0011 fffffe3 [28]\n" +
+ " ( 4) |11111111|11111111|11111110|0100 fffffe4 [28]\n" +
+ " ( 5) |11111111|11111111|11111110|0101 fffffe5 [28]\n" +
+ " ( 6) |11111111|11111111|11111110|0110 fffffe6 [28]\n" +
+ " ( 7) |11111111|11111111|11111110|0111 fffffe7 [28]\n" +
+ " ( 8) |11111111|11111111|11111110|1000 fffffe8 [28]\n" +
+ " ( 9) |11111111|11111111|11101010 ffffea [24]\n" +
+ " ( 10) |11111111|11111111|11111111|111100 3ffffffc [30]\n" +
+ " ( 11) |11111111|11111111|11111110|1001 fffffe9 [28]\n" +
+ " ( 12) |11111111|11111111|11111110|1010 fffffea [28]\n" +
+ " ( 13) |11111111|11111111|11111111|111101 3ffffffd [30]\n" +
+ " ( 14) |11111111|11111111|11111110|1011 fffffeb [28]\n" +
+ " ( 15) |11111111|11111111|11111110|1100 fffffec [28]\n" +
+ " ( 16) |11111111|11111111|11111110|1101 fffffed [28]\n" +
+ " ( 17) |11111111|11111111|11111110|1110 fffffee [28]\n" +
+ " ( 18) |11111111|11111111|11111110|1111 fffffef [28]\n" +
+ " ( 19) |11111111|11111111|11111111|0000 ffffff0 [28]\n" +
+ " ( 20) |11111111|11111111|11111111|0001 ffffff1 [28]\n" +
+ " ( 21) |11111111|11111111|11111111|0010 ffffff2 [28]\n" +
+ " ( 22) |11111111|11111111|11111111|111110 3ffffffe [30]\n" +
+ " ( 23) |11111111|11111111|11111111|0011 ffffff3 [28]\n" +
+ " ( 24) |11111111|11111111|11111111|0100 ffffff4 [28]\n" +
+ " ( 25) |11111111|11111111|11111111|0101 ffffff5 [28]\n" +
+ " ( 26) |11111111|11111111|11111111|0110 ffffff6 [28]\n" +
+ " ( 27) |11111111|11111111|11111111|0111 ffffff7 [28]\n" +
+ " ( 28) |11111111|11111111|11111111|1000 ffffff8 [28]\n" +
+ " ( 29) |11111111|11111111|11111111|1001 ffffff9 [28]\n" +
+ " ( 30) |11111111|11111111|11111111|1010 ffffffa [28]\n" +
+ " ( 31) |11111111|11111111|11111111|1011 ffffffb [28]\n" +
+ " ' ' ( 32) |010100 14 [ 6]\n" +
+ " '!' ( 33) |11111110|00 3f8 [10]\n" +
+ " '\"' ( 34) |11111110|01 3f9 [10]\n" +
+ " '#' ( 35) |11111111|1010 ffa [12]\n" +
+ " '$' ( 36) |11111111|11001 1ff9 [13]\n" +
+ " '%' ( 37) |010101 15 [ 6]\n" +
+ " '&' ( 38) |11111000 f8 [ 8]\n" +
+ " ''' ( 39) |11111111|010 7fa [11]\n" +
+ " '(' ( 40) |11111110|10 3fa [10]\n" +
+ " ')' ( 41) |11111110|11 3fb [10]\n" +
+ " '*' ( 42) |11111001 f9 [ 8]\n" +
+ " '+' ( 43) |11111111|011 7fb [11]\n" +
+ " ',' ( 44) |11111010 fa [ 8]\n" +
+ " '-' ( 45) |010110 16 [ 6]\n" +
+ " '.' ( 46) |010111 17 [ 6]\n" +
+ " '/' ( 47) |011000 18 [ 6]\n" +
+ " '0' ( 48) |00000 0 [ 5]\n" +
+ " '1' ( 49) |00001 1 [ 5]\n" +
+ " '2' ( 50) |00010 2 [ 5]\n" +
+ " '3' ( 51) |011001 19 [ 6]\n" +
+ " '4' ( 52) |011010 1a [ 6]\n" +
+ " '5' ( 53) |011011 1b [ 6]\n" +
+ " '6' ( 54) |011100 1c [ 6]\n" +
+ " '7' ( 55) |011101 1d [ 6]\n" +
+ " '8' ( 56) |011110 1e [ 6]\n" +
+ " '9' ( 57) |011111 1f [ 6]\n" +
+ " ':' ( 58) |1011100 5c [ 7]\n" +
+ " ';' ( 59) |11111011 fb [ 8]\n" +
+ " '<' ( 60) |11111111|1111100 7ffc [15]\n" +
+ " '=' ( 61) |100000 20 [ 6]\n" +
+ " '>' ( 62) |11111111|1011 ffb [12]\n" +
+ " '?' ( 63) |11111111|00 3fc [10]\n" +
+ " '@' ( 64) |11111111|11010 1ffa [13]\n" +
+ " 'A' ( 65) |100001 21 [ 6]\n" +
+ " 'B' ( 66) |1011101 5d [ 7]\n" +
+ " 'C' ( 67) |1011110 5e [ 7]\n" +
+ " 'D' ( 68) |1011111 5f [ 7]\n" +
+ " 'E' ( 69) |1100000 60 [ 7]\n" +
+ " 'F' ( 70) |1100001 61 [ 7]\n" +
+ " 'G' ( 71) |1100010 62 [ 7]\n" +
+ " 'H' ( 72) |1100011 63 [ 7]\n" +
+ " 'I' ( 73) |1100100 64 [ 7]\n" +
+ " 'J' ( 74) |1100101 65 [ 7]\n" +
+ " 'K' ( 75) |1100110 66 [ 7]\n" +
+ " 'L' ( 76) |1100111 67 [ 7]\n" +
+ " 'M' ( 77) |1101000 68 [ 7]\n" +
+ " 'N' ( 78) |1101001 69 [ 7]\n" +
+ " 'O' ( 79) |1101010 6a [ 7]\n" +
+ " 'P' ( 80) |1101011 6b [ 7]\n" +
+ " 'Q' ( 81) |1101100 6c [ 7]\n" +
+ " 'R' ( 82) |1101101 6d [ 7]\n" +
+ " 'S' ( 83) |1101110 6e [ 7]\n" +
+ " 'T' ( 84) |1101111 6f [ 7]\n" +
+ " 'U' ( 85) |1110000 70 [ 7]\n" +
+ " 'V' ( 86) |1110001 71 [ 7]\n" +
+ " 'W' ( 87) |1110010 72 [ 7]\n" +
+ " 'X' ( 88) |11111100 fc [ 8]\n" +
+ " 'Y' ( 89) |1110011 73 [ 7]\n" +
+ " 'Z' ( 90) |11111101 fd [ 8]\n" +
+ " '[' ( 91) |11111111|11011 1ffb [13]\n" +
+ " '\\' ( 92) |11111111|11111110|000 7fff0 [19]\n" +
+ " ']' ( 93) |11111111|11100 1ffc [13]\n" +
+ " '^' ( 94) |11111111|111100 3ffc [14]\n" +
+ " '_' ( 95) |100010 22 [ 6]\n" +
+ " '`' ( 96) |11111111|1111101 7ffd [15]\n" +
+ " 'a' ( 97) |00011 3 [ 5]\n" +
+ " 'b' ( 98) |100011 23 [ 6]\n" +
+ " 'c' ( 99) |00100 4 [ 5]\n" +
+ " 'd' (100) |100100 24 [ 6]\n" +
+ " 'e' (101) |00101 5 [ 5]\n" +
+ " 'f' (102) |100101 25 [ 6]\n" +
+ " 'g' (103) |100110 26 [ 6]\n" +
+ " 'h' (104) |100111 27 [ 6]\n" +
+ " 'i' (105) |00110 6 [ 5]\n" +
+ " 'j' (106) |1110100 74 [ 7]\n" +
+ " 'k' (107) |1110101 75 [ 7]\n" +
+ " 'l' (108) |101000 28 [ 6]\n" +
+ " 'm' (109) |101001 29 [ 6]\n" +
+ " 'n' (110) |101010 2a [ 6]\n" +
+ " 'o' (111) |00111 7 [ 5]\n" +
+ " 'p' (112) |101011 2b [ 6]\n" +
+ " 'q' (113) |1110110 76 [ 7]\n" +
+ " 'r' (114) |101100 2c [ 6]\n" +
+ " 's' (115) |01000 8 [ 5]\n" +
+ " 't' (116) |01001 9 [ 5]\n" +
+ " 'u' (117) |101101 2d [ 6]\n" +
+ " 'v' (118) |1110111 77 [ 7]\n" +
+ " 'w' (119) |1111000 78 [ 7]\n" +
+ " 'x' (120) |1111001 79 [ 7]\n" +
+ " 'y' (121) |1111010 7a [ 7]\n" +
+ " 'z' (122) |1111011 7b [ 7]\n" +
+ " '{' (123) |11111111|1111110 7ffe [15]\n" +
+ " '|' (124) |11111111|100 7fc [11]\n" +
+ " '}' (125) |11111111|111101 3ffd [14]\n" +
+ " '~' (126) |11111111|11101 1ffd [13]\n" +
+ " (127) |11111111|11111111|11111111|1100 ffffffc [28]\n" +
+ " (128) |11111111|11111110|0110 fffe6 [20]\n" +
+ " (129) |11111111|11111111|010010 3fffd2 [22]\n" +
+ " (130) |11111111|11111110|0111 fffe7 [20]\n" +
+ " (131) |11111111|11111110|1000 fffe8 [20]\n" +
+ " (132) |11111111|11111111|010011 3fffd3 [22]\n" +
+ " (133) |11111111|11111111|010100 3fffd4 [22]\n" +
+ " (134) |11111111|11111111|010101 3fffd5 [22]\n" +
+ " (135) |11111111|11111111|1011001 7fffd9 [23]\n" +
+ " (136) |11111111|11111111|010110 3fffd6 [22]\n" +
+ " (137) |11111111|11111111|1011010 7fffda [23]\n" +
+ " (138) |11111111|11111111|1011011 7fffdb [23]\n" +
+ " (139) |11111111|11111111|1011100 7fffdc [23]\n" +
+ " (140) |11111111|11111111|1011101 7fffdd [23]\n" +
+ " (141) |11111111|11111111|1011110 7fffde [23]\n" +
+ " (142) |11111111|11111111|11101011 ffffeb [24]\n" +
+ " (143) |11111111|11111111|1011111 7fffdf [23]\n" +
+ " (144) |11111111|11111111|11101100 ffffec [24]\n" +
+ " (145) |11111111|11111111|11101101 ffffed [24]\n" +
+ " (146) |11111111|11111111|010111 3fffd7 [22]\n" +
+ " (147) |11111111|11111111|1100000 7fffe0 [23]\n" +
+ " (148) |11111111|11111111|11101110 ffffee [24]\n" +
+ " (149) |11111111|11111111|1100001 7fffe1 [23]\n" +
+ " (150) |11111111|11111111|1100010 7fffe2 [23]\n" +
+ " (151) |11111111|11111111|1100011 7fffe3 [23]\n" +
+ " (152) |11111111|11111111|1100100 7fffe4 [23]\n" +
+ " (153) |11111111|11111110|11100 1fffdc [21]\n" +
+ " (154) |11111111|11111111|011000 3fffd8 [22]\n" +
+ " (155) |11111111|11111111|1100101 7fffe5 [23]\n" +
+ " (156) |11111111|11111111|011001 3fffd9 [22]\n" +
+ " (157) |11111111|11111111|1100110 7fffe6 [23]\n" +
+ " (158) |11111111|11111111|1100111 7fffe7 [23]\n" +
+ " (159) |11111111|11111111|11101111 ffffef [24]\n" +
+ " (160) |11111111|11111111|011010 3fffda [22]\n" +
+ " (161) |11111111|11111110|11101 1fffdd [21]\n" +
+ " (162) |11111111|11111110|1001 fffe9 [20]\n" +
+ " (163) |11111111|11111111|011011 3fffdb [22]\n" +
+ " (164) |11111111|11111111|011100 3fffdc [22]\n" +
+ " (165) |11111111|11111111|1101000 7fffe8 [23]\n" +
+ " (166) |11111111|11111111|1101001 7fffe9 [23]\n" +
+ " (167) |11111111|11111110|11110 1fffde [21]\n" +
+ " (168) |11111111|11111111|1101010 7fffea [23]\n" +
+ " (169) |11111111|11111111|011101 3fffdd [22]\n" +
+ " (170) |11111111|11111111|011110 3fffde [22]\n" +
+ " (171) |11111111|11111111|11110000 fffff0 [24]\n" +
+ " (172) |11111111|11111110|11111 1fffdf [21]\n" +
+ " (173) |11111111|11111111|011111 3fffdf [22]\n" +
+ " (174) |11111111|11111111|1101011 7fffeb [23]\n" +
+ " (175) |11111111|11111111|1101100 7fffec [23]\n" +
+ " (176) |11111111|11111111|00000 1fffe0 [21]\n" +
+ " (177) |11111111|11111111|00001 1fffe1 [21]\n" +
+ " (178) |11111111|11111111|100000 3fffe0 [22]\n" +
+ " (179) |11111111|11111111|00010 1fffe2 [21]\n" +
+ " (180) |11111111|11111111|1101101 7fffed [23]\n" +
+ " (181) |11111111|11111111|100001 3fffe1 [22]\n" +
+ " (182) |11111111|11111111|1101110 7fffee [23]\n" +
+ " (183) |11111111|11111111|1101111 7fffef [23]\n" +
+ " (184) |11111111|11111110|1010 fffea [20]\n" +
+ " (185) |11111111|11111111|100010 3fffe2 [22]\n" +
+ " (186) |11111111|11111111|100011 3fffe3 [22]\n" +
+ " (187) |11111111|11111111|100100 3fffe4 [22]\n" +
+ " (188) |11111111|11111111|1110000 7ffff0 [23]\n" +
+ " (189) |11111111|11111111|100101 3fffe5 [22]\n" +
+ " (190) |11111111|11111111|100110 3fffe6 [22]\n" +
+ " (191) |11111111|11111111|1110001 7ffff1 [23]\n" +
+ " (192) |11111111|11111111|11111000|00 3ffffe0 [26]\n" +
+ " (193) |11111111|11111111|11111000|01 3ffffe1 [26]\n" +
+ " (194) |11111111|11111110|1011 fffeb [20]\n" +
+ " (195) |11111111|11111110|001 7fff1 [19]\n" +
+ " (196) |11111111|11111111|100111 3fffe7 [22]\n" +
+ " (197) |11111111|11111111|1110010 7ffff2 [23]\n" +
+ " (198) |11111111|11111111|101000 3fffe8 [22]\n" +
+ " (199) |11111111|11111111|11110110|0 1ffffec [25]\n" +
+ " (200) |11111111|11111111|11111000|10 3ffffe2 [26]\n" +
+ " (201) |11111111|11111111|11111000|11 3ffffe3 [26]\n" +
+ " (202) |11111111|11111111|11111001|00 3ffffe4 [26]\n" +
+ " (203) |11111111|11111111|11111011|110 7ffffde [27]\n" +
+ " (204) |11111111|11111111|11111011|111 7ffffdf [27]\n" +
+ " (205) |11111111|11111111|11111001|01 3ffffe5 [26]\n" +
+ " (206) |11111111|11111111|11110001 fffff1 [24]\n" +
+ " (207) |11111111|11111111|11110110|1 1ffffed [25]\n" +
+ " (208) |11111111|11111110|010 7fff2 [19]\n" +
+ " (209) |11111111|11111111|00011 1fffe3 [21]\n" +
+ " (210) |11111111|11111111|11111001|10 3ffffe6 [26]\n" +
+ " (211) |11111111|11111111|11111100|000 7ffffe0 [27]\n" +
+ " (212) |11111111|11111111|11111100|001 7ffffe1 [27]\n" +
+ " (213) |11111111|11111111|11111001|11 3ffffe7 [26]\n" +
+ " (214) |11111111|11111111|11111100|010 7ffffe2 [27]\n" +
+ " (215) |11111111|11111111|11110010 fffff2 [24]\n" +
+ " (216) |11111111|11111111|00100 1fffe4 [21]\n" +
+ " (217) |11111111|11111111|00101 1fffe5 [21]\n" +
+ " (218) |11111111|11111111|11111010|00 3ffffe8 [26]\n" +
+ " (219) |11111111|11111111|11111010|01 3ffffe9 [26]\n" +
+ " (220) |11111111|11111111|11111111|1101 ffffffd [28]\n" +
+ " (221) |11111111|11111111|11111100|011 7ffffe3 [27]\n" +
+ " (222) |11111111|11111111|11111100|100 7ffffe4 [27]\n" +
+ " (223) |11111111|11111111|11111100|101 7ffffe5 [27]\n" +
+ " (224) |11111111|11111110|1100 fffec [20]\n" +
+ " (225) |11111111|11111111|11110011 fffff3 [24]\n" +
+ " (226) |11111111|11111110|1101 fffed [20]\n" +
+ " (227) |11111111|11111111|00110 1fffe6 [21]\n" +
+ " (228) |11111111|11111111|101001 3fffe9 [22]\n" +
+ " (229) |11111111|11111111|00111 1fffe7 [21]\n" +
+ " (230) |11111111|11111111|01000 1fffe8 [21]\n" +
+ " (231) |11111111|11111111|1110011 7ffff3 [23]\n" +
+ " (232) |11111111|11111111|101010 3fffea [22]\n" +
+ " (233) |11111111|11111111|101011 3fffeb [22]\n" +
+ " (234) |11111111|11111111|11110111|0 1ffffee [25]\n" +
+ " (235) |11111111|11111111|11110111|1 1ffffef [25]\n" +
+ " (236) |11111111|11111111|11110100 fffff4 [24]\n" +
+ " (237) |11111111|11111111|11110101 fffff5 [24]\n" +
+ " (238) |11111111|11111111|11111010|10 3ffffea [26]\n" +
+ " (239) |11111111|11111111|1110100 7ffff4 [23]\n" +
+ " (240) |11111111|11111111|11111010|11 3ffffeb [26]\n" +
+ " (241) |11111111|11111111|11111100|110 7ffffe6 [27]\n" +
+ " (242) |11111111|11111111|11111011|00 3ffffec [26]\n" +
+ " (243) |11111111|11111111|11111011|01 3ffffed [26]\n" +
+ " (244) |11111111|11111111|11111100|111 7ffffe7 [27]\n" +
+ " (245) |11111111|11111111|11111101|000 7ffffe8 [27]\n" +
+ " (246) |11111111|11111111|11111101|001 7ffffe9 [27]\n" +
+ " (247) |11111111|11111111|11111101|010 7ffffea [27]\n" +
+ " (248) |11111111|11111111|11111101|011 7ffffeb [27]\n" +
+ " (249) |11111111|11111111|11111111|1110 ffffffe [28]\n" +
+ " (250) |11111111|11111111|11111101|100 7ffffec [27]\n" +
+ " (251) |11111111|11111111|11111101|101 7ffffed [27]\n" +
+ " (252) |11111111|11111111|11111101|110 7ffffee [27]\n" +
+ " (253) |11111111|11111111|11111101|111 7ffffef [27]\n" +
+ " (254) |11111111|11111111|11111110|000 7fffff0 [27]\n" +
+ " (255) |11111111|11111111|11111011|10 3ffffee [26]\n" +
+ " EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]";
+ // @formatter:on
+
+ @Test
+ public void read_table() {
+ Pattern line = Pattern.compile(
+ "\\(\\s*(?<ascii>\\d+)\\s*\\)\\s*(?<binary>(\\|(0|1)+)+)\\s*" +
+ "(?<hex>[0-9a-zA-Z]+)\\s*\\[\\s*(?<len>\\d+)\\s*\\]");
+ Matcher m = line.matcher(SPEC);
+ int i = 0;
+ while (m.find()) {
+ String ascii = m.group("ascii");
+ String binary = m.group("binary").replaceAll("\\|", "");
+ String hex = m.group("hex");
+ String len = m.group("len");
+
+ // Several sanity checks for the data read from the table, just to
+ // make sure what we read makes sense
+ assertEquals(parseInt(len), binary.length());
+ assertEquals(parseInt(binary, 2), parseInt(hex, 16));
+
+ int expected = parseInt(ascii);
+
+ // TODO: find actual eos, do not hardcode it!
+ byte[] bytes = intToBytes(0x3fffffff, 30,
+ parseInt(hex, 16), parseInt(len));
+
+ StringBuilder actual = new StringBuilder();
+ Huffman.Reader t = new Huffman.Reader();
+ t.read(ByteBuffer.wrap(bytes), actual, false, true);
+
+ // What has been read MUST represent a single symbol
+ assertEquals(actual.length(), 1, "ascii: " + ascii);
+
+ // It's a lot more visual to compare char as codes rather than
+ // characters (as some of them might not be visible)
+ assertEquals(actual.charAt(0), expected);
+ i++;
+ }
+ assertEquals(i, 257); // 256 + EOS
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.4.1
+ //
+ @Test
+ public void read_1() {
+ read("f1e3 c2e5 f23a 6ba0 ab90 f4ff", "www.example.com");
+ }
+
+ @Test
+ public void write_1() {
+ write("www.example.com", "f1e3 c2e5 f23a 6ba0 ab90 f4ff");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.4.2
+ //
+ @Test
+ public void read_2() {
+ read("a8eb 1064 9cbf", "no-cache");
+ }
+
+ @Test
+ public void write_2() {
+ write("no-cache", "a8eb 1064 9cbf");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.4.3
+ //
+ @Test
+ public void read_3() {
+ read("25a8 49e9 5ba9 7d7f", "custom-key");
+ }
+
+ @Test
+ public void write_3() {
+ write("custom-key", "25a8 49e9 5ba9 7d7f");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.4.3
+ //
+ @Test
+ public void read_4() {
+ read("25a8 49e9 5bb8 e8b4 bf", "custom-value");
+ }
+
+ @Test
+ public void write_4() {
+ write("custom-value", "25a8 49e9 5bb8 e8b4 bf");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+ //
+ @Test
+ public void read_5() {
+ read("6402", "302");
+ }
+
+ @Test
+ public void write_5() {
+ write("302", "6402");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+ //
+ @Test
+ public void read_6() {
+ read("aec3 771a 4b", "private");
+ }
+
+ @Test
+ public void write_6() {
+ write("private", "aec3 771a 4b");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+ //
+ @Test
+ public void read_7() {
+ read("d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff",
+ "Mon, 21 Oct 2013 20:13:21 GMT");
+ }
+
+ @Test
+ public void write_7() {
+ write("Mon, 21 Oct 2013 20:13:21 GMT",
+ "d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.1
+ //
+ @Test
+ public void read_8() {
+ read("9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3",
+ "https://www.example.com");
+ }
+
+ @Test
+ public void write_8() {
+ write("https://www.example.com",
+ "9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.2
+ //
+ @Test
+ public void read_9() {
+ read("640e ff", "307");
+ }
+
+ @Test
+ public void write_9() {
+ write("307", "640e ff");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
+ //
+ @Test
+ public void read_10() {
+ read("d07a be94 1054 d444 a820 0595 040b 8166 e084 a62d 1bff",
+ "Mon, 21 Oct 2013 20:13:22 GMT");
+ }
+
+ @Test
+ public void write_10() {
+ write("Mon, 21 Oct 2013 20:13:22 GMT",
+ "d07a be94 1054 d444 a820 0595 040b 8166 e084 a62d 1bff");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
+ //
+ @Test
+ public void read_11() {
+ read("9bd9 ab", "gzip");
+ }
+
+ @Test
+ public void write_11() {
+ write("gzip", "9bd9 ab");
+ }
+
+ //
+ // https://tools.ietf.org/html/rfc7541#appendix-C.6.3
+ //
+ @Test
+ public void read_12() {
+ read("94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 " +
+ "d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 " +
+ "3160 65c0 03ed 4ee5 b106 3d50 07",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1");
+ }
+
+ @Test
+ public void test_trie_has_no_empty_nodes() {
+ Huffman.Node root = Huffman.INSTANCE.getRoot();
+ Stack<Huffman.Node> backlog = new Stack<>();
+ backlog.push(root);
+ while (!backlog.isEmpty()) {
+ Huffman.Node n = backlog.pop();
+ // The only type of nodes we couldn't possibly catch during
+ // construction is an empty node: no children and no char
+ if (n.left != null) {
+ backlog.push(n.left);
+ }
+ if (n.right != null) {
+ backlog.push(n.right);
+ }
+ assertFalse(!n.charIsSet && n.left == null && n.right == null,
+ "Empty node in the trie");
+ }
+ }
+
+ @Test
+ public void test_trie_has_257_nodes() {
+ int count = 0;
+ Huffman.Node root = Huffman.INSTANCE.getRoot();
+ Stack<Huffman.Node> backlog = new Stack<>();
+ backlog.push(root);
+ while (!backlog.isEmpty()) {
+ Huffman.Node n = backlog.pop();
+ if (n.left != null) {
+ backlog.push(n.left);
+ }
+ if (n.right != null) {
+ backlog.push(n.right);
+ }
+ if (n.isLeaf()) {
+ count++;
+ }
+ }
+ assertEquals(count, 257);
+ }
+
+ @Test
+ public void cant_encode_outside_byte() {
+ TestHelper.Block<Object> coding =
+ () -> new Huffman.Writer()
+ .from(((char) 256) + "", 0, 1)
+ .write(ByteBuffer.allocate(1));
+ RuntimeException e =
+ TestHelper.assertVoidThrows(RuntimeException.class, coding);
+ TestHelper.assertExceptionMessageContains(e, "char");
+ }
+
+ private static void read(String hexdump, String decoded) {
+ ByteBuffer source = SpecHelper.toBytes(hexdump);
+ Appendable actual = new StringBuilder();
+ new Huffman.Reader().read(source, actual, true);
+ assertEquals(actual.toString(), decoded);
+ }
+
+ private static void write(String decoded, String hexdump) {
+ int n = Huffman.INSTANCE.lengthOf(decoded);
+ ByteBuffer destination = ByteBuffer.allocate(n); // Extra margin (1) to test having more bytes in the destination than needed is ok
+ Huffman.Writer writer = new Huffman.Writer();
+ BuffersTestingKit.forEachSplit(destination, byteBuffers -> {
+ writer.from(decoded, 0, decoded.length());
+ boolean written = false;
+ for (ByteBuffer b : byteBuffers) {
+ int pos = b.position();
+ written = writer.write(b);
+ b.position(pos);
+ }
+ assertTrue(written);
+ ByteBuffer concated = BuffersTestingKit.concat(byteBuffers);
+ String actual = SpecHelper.toHexdump(concated);
+ assertEquals(actual, hexdump);
+ writer.reset();
+ });
+ }
+
+ //
+ // It's not very pretty, yes I know that
+ //
+ // hex:
+ //
+ // |31|30|...|N-1|...|01|00|
+ // \ /
+ // codeLength
+ //
+ // hex <<= 32 - codeLength; (align to MSB):
+ //
+ // |31|30|...|32-N|...|01|00|
+ // \ /
+ // codeLength
+ //
+ // EOS:
+ //
+ // |31|30|...|M-1|...|01|00|
+ // \ /
+ // eosLength
+ //
+ // eos <<= 32 - eosLength; (align to MSB):
+ //
+ // pad with MSBs of EOS:
+ //
+ // |31|30|...|32-N|32-N-1|...|01|00|
+ // | 32|...|
+ //
+ // Finally, split into byte[]
+ //
+ private byte[] intToBytes(int eos, int eosLength, int hex, int codeLength) {
+ hex <<= 32 - codeLength;
+ eos >>= codeLength - (32 - eosLength);
+ hex |= eos;
+ int n = (int) Math.ceil(codeLength / 8.0);
+ byte[] result = new byte[n];
+ for (int i = 0; i < n; i++) {
+ result[i] = (byte) (hex >> (32 - 8 * (i + 1)));
+ }
+ return result;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/SpecHelper.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+//
+// THIS IS NOT A TEST
+//
+public final class SpecHelper {
+
+ private SpecHelper() {
+ throw new AssertionError();
+ }
+
+ public static ByteBuffer toBytes(String hexdump) {
+ Pattern hexByte = Pattern.compile("[0-9a-fA-F]{2}");
+ List<String> bytes = new ArrayList<>();
+ Matcher matcher = hexByte.matcher(hexdump);
+ while (matcher.find()) {
+ bytes.add(matcher.group(0));
+ }
+ ByteBuffer result = ByteBuffer.allocate(bytes.size());
+ for (String f : bytes) {
+ result.put((byte) Integer.parseInt(f, 16));
+ }
+ result.flip();
+ return result;
+ }
+
+ public static String toHexdump(ByteBuffer bb) {
+ List<String> words = new ArrayList<>();
+ int i = 0;
+ while (bb.hasRemaining()) {
+ if (i % 2 == 0) {
+ words.add("");
+ }
+ byte b = bb.get();
+ String hex = Integer.toHexString(256 + Byte.toUnsignedInt(b)).substring(1);
+ words.set(i / 2, words.get(i / 2) + hex);
+ i++;
+ }
+ return words.stream().collect(Collectors.joining(" "));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/http2/java.httpclient/sun/net/httpclient/hpack/TestHelper.java Mon Apr 18 19:40:48 2016 +0100
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.net.httpclient.hpack;
+
+import org.testng.annotations.Test;
+
+import java.util.Objects;
+import java.util.Random;
+
+public final class TestHelper {
+
+ public static Random newRandom() {
+ long seed = Long.getLong("jdk.test.lib.random.seed", System.currentTimeMillis());
+ System.out.println("new java.util.Random(" + seed + ")");
+ return new Random(seed);
+ }
+
+ public static <T extends Throwable> T assertVoidThrows(Class<T> clazz, Block<?> code) {
+ return assertThrows(clazz, () -> {
+ code.run();
+ return null;
+ });
+ }
+
+ public static <T extends Throwable> T assertThrows(Class<T> clazz, ReturningBlock<?> code) {
+ Objects.requireNonNull(clazz, "clazz == null");
+ Objects.requireNonNull(code, "code == null");
+ try {
+ code.run();
+ } catch (Throwable t) {
+ if (clazz.isInstance(t)) {
+ return clazz.cast(t);
+ }
+ throw new AssertionError("Expected to catch exception of type "
+ + clazz.getCanonicalName() + ", instead caught "
+ + t.getClass().getCanonicalName(), t);
+
+ }
+ throw new AssertionError(
+ "Expected to catch exception of type " + clazz.getCanonicalName()
+ + ", but caught nothing");
+ }
+
+ public static <T> T assertDoesNotThrow(ReturningBlock<T> code) {
+ Objects.requireNonNull(code, "code == null");
+ try {
+ return code.run();
+ } catch (Throwable t) {
+ throw new AssertionError(
+ "Expected code block to exit normally, instead " +
+ "caught " + t.getClass().getCanonicalName(), t);
+ }
+ }
+
+ public static void assertVoidDoesNotThrow(Block<?> code) {
+ Objects.requireNonNull(code, "code == null");
+ try {
+ code.run();
+ } catch (Throwable t) {
+ throw new AssertionError(
+ "Expected code block to exit normally, instead " +
+ "caught " + t.getClass().getCanonicalName(), t);
+ }
+ }
+
+
+ public static void assertExceptionMessageContains(Throwable t,
+ CharSequence firstSubsequence,
+ CharSequence... others) {
+ assertCharSequenceContains(t.getMessage(), firstSubsequence, others);
+ }
+
+ public static void assertCharSequenceContains(CharSequence s,
+ CharSequence firstSubsequence,
+ CharSequence... others) {
+ if (s == null) {
+ throw new NullPointerException("Exception message is null");
+ }
+ String str = s.toString();
+ String missing = null;
+ if (!str.contains(firstSubsequence.toString())) {
+ missing = firstSubsequence.toString();
+ } else {
+ for (CharSequence o : others) {
+ if (!str.contains(o.toString())) {
+ missing = o.toString();
+ break;
+ }
+ }
+ }
+ if (missing != null) {
+ throw new AssertionError("CharSequence '" + s + "'" + " does not "
+ + "contain subsequence '" + missing + "'");
+ }
+ }
+
+ public interface ReturningBlock<T> {
+ T run() throws Throwable;
+ }
+
+ public interface Block<T> {
+ void run() throws Throwable;
+ }
+
+ // tests
+
+ @Test
+ public void assertThrows() {
+ assertThrows(NullPointerException.class, () -> ((Object) null).toString());
+ }
+
+ @Test
+ public void assertThrowsWrongType() {
+ try {
+ assertThrows(IllegalArgumentException.class, () -> ((Object) null).toString());
+ } catch (AssertionError e) {
+ Throwable cause = e.getCause();
+ String message = e.getMessage();
+ if (cause != null
+ && cause instanceof NullPointerException
+ && message != null
+ && message.contains("instead caught")) {
+ return;
+ }
+ }
+ throw new AssertionError();
+ }
+
+ @Test
+ public void assertThrowsNoneCaught() {
+ try {
+ assertThrows(IllegalArgumentException.class, () -> null);
+ } catch (AssertionError e) {
+ Throwable cause = e.getCause();
+ String message = e.getMessage();
+ if (cause == null
+ && message != null
+ && message.contains("but caught nothing")) {
+ return;
+ }
+ }
+ throw new AssertionError();
+ }
+}