jdk/src/java.httpclient/share/classes/sun/net/httpclient/hpack/Encoder.java
changeset 42489 a9e4de33da2e
parent 42488 6bf61c516415
parent 42483 3850c235c3fb
child 42491 57c19149b60b
equal deleted inserted replaced
42488:6bf61c516415 42489:a9e4de33da2e
     1 /*
       
     2  * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package sun.net.httpclient.hpack;
       
    26 
       
    27 import java.nio.ByteBuffer;
       
    28 import java.nio.ReadOnlyBufferException;
       
    29 import java.util.LinkedList;
       
    30 import java.util.List;
       
    31 
       
    32 import static java.lang.String.format;
       
    33 import static java.util.Objects.requireNonNull;
       
    34 
       
    35 /**
       
    36  * Encodes headers to their binary representation.
       
    37  *
       
    38  * <p>Typical lifecycle looks like this:
       
    39  *
       
    40  * <p> {@link #Encoder(int) new Encoder}
       
    41  * ({@link #setMaxCapacity(int) setMaxCapacity}?
       
    42  * {@link #encode(ByteBuffer) encode})*
       
    43  *
       
    44  * <p> Suppose headers are represented by {@code Map<String, List<String>>}. A
       
    45  * supplier and a consumer of {@link ByteBuffer}s in forms of {@code
       
    46  * Supplier<ByteBuffer>} and {@code Consumer<ByteBuffer>} respectively. Then to
       
    47  * encode headers, the following approach might be used:
       
    48  *
       
    49  * <pre>{@code
       
    50  *     for (Map.Entry<String, List<String>> h : headers.entrySet()) {
       
    51  *         String name = h.getKey();
       
    52  *         for (String value : h.getValue()) {
       
    53  *             encoder.header(name, value);        // Set up header
       
    54  *             boolean encoded;
       
    55  *             do {
       
    56  *                 ByteBuffer b = buffersSupplier.get();
       
    57  *                 encoded = encoder.encode(b);    // Encode the header
       
    58  *                 buffersConsumer.accept(b);
       
    59  *             } while (!encoded);
       
    60  *         }
       
    61  *     }
       
    62  * }</pre>
       
    63  *
       
    64  * <p> Though the specification <a
       
    65  * href="https://tools.ietf.org/html/rfc7541#section-2"> does not define</a> how
       
    66  * an encoder is to be implemented, a default implementation is provided by the
       
    67  * method {@link #header(CharSequence, CharSequence, boolean)}.
       
    68  *
       
    69  * <p> To provide a custom encoding implementation, {@code Encoder} has to be
       
    70  * extended. A subclass then can access methods for encoding using specific
       
    71  * representations (e.g. {@link #literal(int, CharSequence, boolean) literal},
       
    72  * {@link #indexed(int) indexed}, etc.)
       
    73  *
       
    74  * @apiNote
       
    75  *
       
    76  * <p> An Encoder provides an incremental way of encoding headers.
       
    77  * {@link #encode(ByteBuffer)} takes a buffer a returns a boolean indicating
       
    78  * whether, or not, the buffer was sufficiently sized to hold the
       
    79  * remaining of the encoded representation.
       
    80  *
       
    81  * <p> This way, there's no need to provide a buffer of a specific size, or to
       
    82  * resize (and copy) the buffer on demand, when the remaining encoded
       
    83  * representation will not fit in the buffer's remaining space. Instead, an
       
    84  * array of existing buffers can be used, prepended with a frame that encloses
       
    85  * the resulting header block afterwards.
       
    86  *
       
    87  * <p> Splitting the encoding operation into header set up and header encoding,
       
    88  * separates long lived arguments ({@code name}, {@code value}, {@code
       
    89  * sensitivity}, etc.) from the short lived ones (e.g. {@code buffer}),
       
    90  * simplifying each operation itself.
       
    91  *
       
    92  * @implNote
       
    93  *
       
    94  * <p> The default implementation does not use dynamic table. It reports to a
       
    95  * coupled Decoder a size update with the value of {@code 0}, and never changes
       
    96  * it afterwards.
       
    97  *
       
    98  * @since 9
       
    99  */
       
   100 public class Encoder {
       
   101 
       
   102     // TODO: enum: no huffman/smart huffman/always huffman
       
   103     private static final boolean DEFAULT_HUFFMAN = true;
       
   104 
       
   105     private final IndexedWriter indexedWriter = new IndexedWriter();
       
   106     private final LiteralWriter literalWriter = new LiteralWriter();
       
   107     private final LiteralNeverIndexedWriter literalNeverIndexedWriter
       
   108             = new LiteralNeverIndexedWriter();
       
   109     private final LiteralWithIndexingWriter literalWithIndexingWriter
       
   110             = new LiteralWithIndexingWriter();
       
   111     private final SizeUpdateWriter sizeUpdateWriter = new SizeUpdateWriter();
       
   112     private final BulkSizeUpdateWriter bulkSizeUpdateWriter
       
   113             = new BulkSizeUpdateWriter();
       
   114 
       
   115     private BinaryRepresentationWriter writer;
       
   116     private final HeaderTable headerTable;
       
   117 
       
   118     private boolean encoding;
       
   119 
       
   120     private int maxCapacity;
       
   121     private int currCapacity;
       
   122     private int lastCapacity;
       
   123     private long minCapacity;
       
   124     private boolean capacityUpdate;
       
   125     private boolean configuredCapacityUpdate;
       
   126 
       
   127     /**
       
   128      * Constructs an {@code Encoder} with the specified maximum capacity of the
       
   129      * header table.
       
   130      *
       
   131      * <p> The value has to be agreed between decoder and encoder out-of-band,
       
   132      * e.g. by a protocol that uses HPACK (see <a
       
   133      * href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table
       
   134      * Size</a>).
       
   135      *
       
   136      * @param maxCapacity
       
   137      *         a non-negative integer
       
   138      *
       
   139      * @throws IllegalArgumentException
       
   140      *         if maxCapacity is negative
       
   141      */
       
   142     public Encoder(int maxCapacity) {
       
   143         if (maxCapacity < 0) {
       
   144             throw new IllegalArgumentException("maxCapacity >= 0: " + maxCapacity);
       
   145         }
       
   146         // Initial maximum capacity update mechanics
       
   147         minCapacity = Long.MAX_VALUE;
       
   148         currCapacity = -1;
       
   149         setMaxCapacity(maxCapacity);
       
   150         headerTable = new HeaderTable(lastCapacity);
       
   151     }
       
   152 
       
   153     /**
       
   154      * Sets up the given header {@code (name, value)}.
       
   155      *
       
   156      * <p> Fixates {@code name} and {@code value} for the duration of encoding.
       
   157      *
       
   158      * @param name
       
   159      *         the name
       
   160      * @param value
       
   161      *         the value
       
   162      *
       
   163      * @throws NullPointerException
       
   164      *         if any of the arguments are {@code null}
       
   165      * @throws IllegalStateException
       
   166      *         if the encoder hasn't fully encoded the previous header, or
       
   167      *         hasn't yet started to encode it
       
   168      * @see #header(CharSequence, CharSequence, boolean)
       
   169      */
       
   170     public void header(CharSequence name, CharSequence value)
       
   171             throws IllegalStateException {
       
   172         header(name, value, false);
       
   173     }
       
   174 
       
   175     /**
       
   176      * Sets up the given header {@code (name, value)} with possibly sensitive
       
   177      * value.
       
   178      *
       
   179      * <p> Fixates {@code name} and {@code value} for the duration of encoding.
       
   180      *
       
   181      * @param name
       
   182      *         the name
       
   183      * @param value
       
   184      *         the value
       
   185      * @param sensitive
       
   186      *         whether or not the value is sensitive
       
   187      *
       
   188      * @throws NullPointerException
       
   189      *         if any of the arguments are {@code null}
       
   190      * @throws IllegalStateException
       
   191      *         if the encoder hasn't fully encoded the previous header, or
       
   192      *         hasn't yet started to encode it
       
   193      * @see #header(CharSequence, CharSequence)
       
   194      * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean)
       
   195      */
       
   196     public void header(CharSequence name, CharSequence value,
       
   197                        boolean sensitive) throws IllegalStateException {
       
   198         // Arguably a good balance between complexity of implementation and
       
   199         // efficiency of encoding
       
   200         requireNonNull(name, "name");
       
   201         requireNonNull(value, "value");
       
   202         HeaderTable t = getHeaderTable();
       
   203         int index = t.indexOf(name, value);
       
   204         if (index > 0) {
       
   205             indexed(index);
       
   206         } else if (index < 0) {
       
   207             if (sensitive) {
       
   208                 literalNeverIndexed(-index, value, DEFAULT_HUFFMAN);
       
   209             } else {
       
   210                 literal(-index, value, DEFAULT_HUFFMAN);
       
   211             }
       
   212         } else {
       
   213             if (sensitive) {
       
   214                 literalNeverIndexed(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
       
   215             } else {
       
   216                 literal(name, DEFAULT_HUFFMAN, value, DEFAULT_HUFFMAN);
       
   217             }
       
   218         }
       
   219     }
       
   220 
       
   221     /**
       
   222      * Sets a maximum capacity of the header table.
       
   223      *
       
   224      * <p> The value has to be agreed between decoder and encoder out-of-band,
       
   225      * e.g. by a protocol that uses HPACK (see <a
       
   226      * href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table
       
   227      * Size</a>).
       
   228      *
       
   229      * <p> May be called any number of times after or before a complete header
       
   230      * has been encoded.
       
   231      *
       
   232      * <p> If the encoder decides to change the actual capacity, an update will
       
   233      * be encoded before a new encoding operation starts.
       
   234      *
       
   235      * @param capacity
       
   236      *         a non-negative integer
       
   237      *
       
   238      * @throws IllegalArgumentException
       
   239      *         if capacity is negative
       
   240      * @throws IllegalStateException
       
   241      *         if the encoder hasn't fully encoded the previous header, or
       
   242      *         hasn't yet started to encode it
       
   243      */
       
   244     public void setMaxCapacity(int capacity) {
       
   245         checkEncoding();
       
   246         if (capacity < 0) {
       
   247             throw new IllegalArgumentException("capacity >= 0: " + capacity);
       
   248         }
       
   249         int calculated = calculateCapacity(capacity);
       
   250         if (calculated < 0 || calculated > capacity) {
       
   251             throw new IllegalArgumentException(
       
   252                     format("0 <= calculated <= capacity: calculated=%s, capacity=%s",
       
   253                             calculated, capacity));
       
   254         }
       
   255         capacityUpdate = true;
       
   256         // maxCapacity needs to be updated unconditionally, so the encoder
       
   257         // always has the newest one (in case it decides to update it later
       
   258         // unsolicitedly)
       
   259         // Suppose maxCapacity = 4096, and the encoder has decided to use only
       
   260         // 2048. It later can choose anything else from the region [0, 4096].
       
   261         maxCapacity = capacity;
       
   262         lastCapacity = calculated;
       
   263         minCapacity = Math.min(minCapacity, lastCapacity);
       
   264     }
       
   265 
       
   266     protected int calculateCapacity(int maxCapacity) {
       
   267         // Default implementation of the Encoder won't add anything to the
       
   268         // table, therefore no need for a table space
       
   269         return 0;
       
   270     }
       
   271 
       
   272     /**
       
   273      * Encodes the {@linkplain #header(CharSequence, CharSequence) set up}
       
   274      * header into the given buffer.
       
   275      *
       
   276      * <p> The encoder writes as much as possible of the header's binary
       
   277      * representation into the given buffer, starting at the buffer's position,
       
   278      * and increments its position to reflect the bytes written. The buffer's
       
   279      * mark and limit will not be modified.
       
   280      *
       
   281      * <p> Once the method has returned {@code true}, the current header is
       
   282      * deemed encoded. A new header may be set up.
       
   283      *
       
   284      * @param headerBlock
       
   285      *         the buffer to encode the header into, may be empty
       
   286      *
       
   287      * @return {@code true} if the current header has been fully encoded,
       
   288      *         {@code false} otherwise
       
   289      *
       
   290      * @throws NullPointerException
       
   291      *         if the buffer is {@code null}
       
   292      * @throws ReadOnlyBufferException
       
   293      *         if this buffer is read-only
       
   294      * @throws IllegalStateException
       
   295      *         if there is no set up header
       
   296      */
       
   297     public final boolean encode(ByteBuffer headerBlock) {
       
   298         if (!encoding) {
       
   299             throw new IllegalStateException("A header hasn't been set up");
       
   300         }
       
   301         if (!prependWithCapacityUpdate(headerBlock)) {
       
   302             return false;
       
   303         }
       
   304         boolean done = writer.write(headerTable, headerBlock);
       
   305         if (done) {
       
   306             writer.reset(); // FIXME: WHY?
       
   307             encoding = false;
       
   308         }
       
   309         return done;
       
   310     }
       
   311 
       
   312     private boolean prependWithCapacityUpdate(ByteBuffer headerBlock) {
       
   313         if (capacityUpdate) {
       
   314             if (!configuredCapacityUpdate) {
       
   315                 List<Integer> sizes = new LinkedList<>();
       
   316                 if (minCapacity < currCapacity) {
       
   317                     sizes.add((int) minCapacity);
       
   318                     if (minCapacity != lastCapacity) {
       
   319                         sizes.add(lastCapacity);
       
   320                     }
       
   321                 } else if (lastCapacity != currCapacity) {
       
   322                     sizes.add(lastCapacity);
       
   323                 }
       
   324                 bulkSizeUpdateWriter.maxHeaderTableSizes(sizes);
       
   325                 configuredCapacityUpdate = true;
       
   326             }
       
   327             boolean done = bulkSizeUpdateWriter.write(headerTable, headerBlock);
       
   328             if (done) {
       
   329                 minCapacity = lastCapacity;
       
   330                 currCapacity = lastCapacity;
       
   331                 bulkSizeUpdateWriter.reset();
       
   332                 capacityUpdate = false;
       
   333                 configuredCapacityUpdate = false;
       
   334             }
       
   335             return done;
       
   336         }
       
   337         return true;
       
   338     }
       
   339 
       
   340     protected final void indexed(int index) throws IndexOutOfBoundsException {
       
   341         checkEncoding();
       
   342         encoding = true;
       
   343         writer = indexedWriter.index(index);
       
   344     }
       
   345 
       
   346     protected final void literal(int index, CharSequence value,
       
   347                                  boolean useHuffman)
       
   348             throws IndexOutOfBoundsException {
       
   349         checkEncoding();
       
   350         encoding = true;
       
   351         writer = literalWriter
       
   352                 .index(index).value(value, useHuffman);
       
   353     }
       
   354 
       
   355     protected final void literal(CharSequence name, boolean nameHuffman,
       
   356                                  CharSequence value, boolean valueHuffman) {
       
   357         checkEncoding();
       
   358         encoding = true;
       
   359         writer = literalWriter
       
   360                 .name(name, nameHuffman).value(value, valueHuffman);
       
   361     }
       
   362 
       
   363     protected final void literalNeverIndexed(int index,
       
   364                                              CharSequence value,
       
   365                                              boolean valueHuffman)
       
   366             throws IndexOutOfBoundsException {
       
   367         checkEncoding();
       
   368         encoding = true;
       
   369         writer = literalNeverIndexedWriter
       
   370                 .index(index).value(value, valueHuffman);
       
   371     }
       
   372 
       
   373     protected final void literalNeverIndexed(CharSequence name,
       
   374                                              boolean nameHuffman,
       
   375                                              CharSequence value,
       
   376                                              boolean valueHuffman) {
       
   377         checkEncoding();
       
   378         encoding = true;
       
   379         writer = literalNeverIndexedWriter
       
   380                 .name(name, nameHuffman).value(value, valueHuffman);
       
   381     }
       
   382 
       
   383     protected final void literalWithIndexing(int index,
       
   384                                              CharSequence value,
       
   385                                              boolean valueHuffman)
       
   386             throws IndexOutOfBoundsException {
       
   387         checkEncoding();
       
   388         encoding = true;
       
   389         writer = literalWithIndexingWriter
       
   390                 .index(index).value(value, valueHuffman);
       
   391     }
       
   392 
       
   393     protected final void literalWithIndexing(CharSequence name,
       
   394                                              boolean nameHuffman,
       
   395                                              CharSequence value,
       
   396                                              boolean valueHuffman) {
       
   397         checkEncoding();
       
   398         encoding = true;
       
   399         writer = literalWithIndexingWriter
       
   400                 .name(name, nameHuffman).value(value, valueHuffman);
       
   401     }
       
   402 
       
   403     protected final void sizeUpdate(int capacity)
       
   404             throws IllegalArgumentException {
       
   405         checkEncoding();
       
   406         // Ensure subclass follows the contract
       
   407         if (capacity > this.maxCapacity) {
       
   408             throw new IllegalArgumentException(
       
   409                     format("capacity <= maxCapacity: capacity=%s, maxCapacity=%s",
       
   410                             capacity, maxCapacity));
       
   411         }
       
   412         writer = sizeUpdateWriter.maxHeaderTableSize(capacity);
       
   413     }
       
   414 
       
   415     protected final int getMaxCapacity() {
       
   416         return maxCapacity;
       
   417     }
       
   418 
       
   419     protected final HeaderTable getHeaderTable() {
       
   420         return headerTable;
       
   421     }
       
   422 
       
   423     protected final void checkEncoding() {
       
   424         if (encoding) {
       
   425             throw new IllegalStateException(
       
   426                     "Previous encoding operation hasn't finished yet");
       
   427         }
       
   428     }
       
   429 }