8153353: HPACK implementation
Reviewed-by: chegar, rriggs
This commit is contained in:
parent
c536913c21
commit
1b0e8a4e73
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) { }
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
/**
|
||||
* HPACK (Header Compression for HTTP/2) implementation conforming to
|
||||
* <a href="https://tools.ietf.org/html/rfc7541">RFC 7541</a>.
|
||||
*
|
||||
* <p> Headers can be decoded and encoded by {@link sun.net.httpclient.hpack.Decoder}
|
||||
* and {@link sun.net.httpclient.hpack.Encoder} respectively.
|
||||
*
|
||||
* <p> Instances of these classes are not safe for use by multiple threads.
|
||||
*/
|
||||
package sun.net.httpclient.hpack;
|
40
jdk/test/java/net/httpclient/http2/HpackDriver.java
Normal file
40
jdk/test/java/net/httpclient/http2/HpackDriver.java
Normal 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 { }
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
// }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) -> { };
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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(" "));
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user