8153353: HPACK implementation

Reviewed-by: chegar, rriggs
This commit is contained in:
Pavel Rappo 2016-04-18 19:40:48 +01:00
parent c536913c21
commit 1b0e8a4e73
29 changed files with 6682 additions and 0 deletions

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) { }
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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&nbsp;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;

View File

@ -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 { }

View File

@ -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);
}
}

View File

@ -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);
// }
}

View File

@ -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);
}
}
}
}

View File

@ -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) -> { };
}
}

View File

@ -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);
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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(" "));
}
}

View File

@ -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();
}
}