diff -r 3b5a2cf988d2 -r 57c19149b60b jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/Encoder.java --- a/jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/Encoder.java Thu Dec 08 18:03:35 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,429 +0,0 @@ -/* - * 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. - * - *

Typical lifecycle looks like this: - * - *

{@link #Encoder(int) new Encoder} - * ({@link #setMaxCapacity(int) setMaxCapacity}? - * {@link #encode(ByteBuffer) encode})* - * - *

Suppose headers are represented by {@code Map>}. A - * supplier and a consumer of {@link ByteBuffer}s in forms of {@code - * Supplier} and {@code Consumer} respectively. Then to - * encode headers, the following approach might be used: - * - *

{@code
- *     for (Map.Entry> 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);
- *         }
- *     }
- * }
- * - *

Though the specification does not define how - * an encoder is to be implemented, a default implementation is provided by the - * method {@link #header(CharSequence, CharSequence, boolean)}. - * - *

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 - * - *

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. - * - *

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. - * - *

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 - * - *

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. - * - *

The value has to be agreed between decoder and encoder out-of-band, - * e.g. by a protocol that uses HPACK (see 4.2. Maximum Table - * Size). - * - * @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)}. - * - *

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. - * - *

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. - * - *

The value has to be agreed between decoder and encoder out-of-band, - * e.g. by a protocol that uses HPACK (see 4.2. Maximum Table - * Size). - * - *

May be called any number of times after or before a complete header - * has been encoded. - * - *

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. - * - *

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. - * - *

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