src/java.net.http/share/classes/java/net/http/internal/hpack/Decoder.java
branchhttp-client-branch
changeset 56089 42208b2f224e
parent 56088 38fac6d0521d
equal deleted inserted replaced
56088:38fac6d0521d 56089:42208b2f224e
       
     1 /*
       
     2  * Copyright (c) 2014, 2018, 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 java.net.http.internal.hpack;
       
    26 
       
    27 import java.net.http.internal.hpack.HPACK.Logger;
       
    28 import jdk.internal.vm.annotation.Stable;
       
    29 
       
    30 import java.io.IOException;
       
    31 import java.nio.ByteBuffer;
       
    32 import java.util.concurrent.atomic.AtomicLong;
       
    33 
       
    34 import static java.net.http.internal.hpack.HPACK.Logger.Level.EXTRA;
       
    35 import static java.net.http.internal.hpack.HPACK.Logger.Level.NORMAL;
       
    36 import static java.lang.String.format;
       
    37 import static java.util.Objects.requireNonNull;
       
    38 
       
    39 /**
       
    40  * Decodes headers from their binary representation.
       
    41  *
       
    42  * <p> Typical lifecycle looks like this:
       
    43  *
       
    44  * <p> {@link #Decoder(int) new Decoder}
       
    45  * ({@link #setMaxCapacity(int) setMaxCapacity}?
       
    46  * {@link #decode(ByteBuffer, boolean, DecodingCallback) decode})*
       
    47  *
       
    48  * @apiNote
       
    49  *
       
    50  * <p> The design intentions behind Decoder were to facilitate flexible and
       
    51  * incremental style of processing.
       
    52  *
       
    53  * <p> {@code Decoder} does not require a complete header block in a single
       
    54  * {@code ByteBuffer}. The header block can be spread across many buffers of any
       
    55  * size and decoded one-by-one the way it makes most sense for the user. This
       
    56  * way also allows not to limit the size of the header block.
       
    57  *
       
    58  * <p> Headers are delivered to the {@linkplain DecodingCallback callback} as
       
    59  * soon as they become decoded. Using the callback also gives the user a freedom
       
    60  * to decide how headers are processed. The callback does not limit the number
       
    61  * of headers decoded during single decoding operation.
       
    62  *
       
    63  * @since 9
       
    64  */
       
    65 public final class Decoder {
       
    66 
       
    67     private final Logger logger;
       
    68     private static final AtomicLong DECODERS_IDS = new AtomicLong();
       
    69 
       
    70     @Stable
       
    71     private static final State[] states = new State[256];
       
    72 
       
    73     static {
       
    74         // To be able to do a quick lookup, each of 256 possibilities are mapped
       
    75         // to corresponding states.
       
    76         //
       
    77         // We can safely do this since patterns 1, 01, 001, 0001, 0000 are
       
    78         // Huffman prefixes and therefore are inherently not ambiguous.
       
    79         //
       
    80         // I do it mainly for better debugging (to not go each time step by step
       
    81         // through if...else tree). As for performance win for the decoding, I
       
    82         // believe is negligible.
       
    83         for (int i = 0; i < states.length; i++) {
       
    84             if ((i & 0b1000_0000) == 0b1000_0000) {
       
    85                 states[i] = State.INDEXED;
       
    86             } else if ((i & 0b1100_0000) == 0b0100_0000) {
       
    87                 states[i] = State.LITERAL_WITH_INDEXING;
       
    88             } else if ((i & 0b1110_0000) == 0b0010_0000) {
       
    89                 states[i] = State.SIZE_UPDATE;
       
    90             } else if ((i & 0b1111_0000) == 0b0001_0000) {
       
    91                 states[i] = State.LITERAL_NEVER_INDEXED;
       
    92             } else if ((i & 0b1111_0000) == 0b0000_0000) {
       
    93                 states[i] = State.LITERAL;
       
    94             } else {
       
    95                 throw new InternalError(String.valueOf(i));
       
    96             }
       
    97         }
       
    98     }
       
    99 
       
   100     private final long id;
       
   101     private final HeaderTable table;
       
   102 
       
   103     private State state = State.READY;
       
   104     private final IntegerReader integerReader;
       
   105     private final StringReader stringReader;
       
   106     private final StringBuilder name;
       
   107     private final StringBuilder value;
       
   108     private int intValue;
       
   109     private boolean firstValueRead;
       
   110     private boolean firstValueIndex;
       
   111     private boolean nameHuffmanEncoded;
       
   112     private boolean valueHuffmanEncoded;
       
   113     private int capacity;
       
   114 
       
   115     /**
       
   116      * Constructs a {@code Decoder} with the specified initial capacity of the
       
   117      * header table.
       
   118      *
       
   119      * <p> The value has to be agreed between decoder and encoder out-of-band,
       
   120      * e.g. by a protocol that uses HPACK
       
   121      * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
       
   122      *
       
   123      * @param capacity
       
   124      *         a non-negative integer
       
   125      *
       
   126      * @throws IllegalArgumentException
       
   127      *         if capacity is negative
       
   128      */
       
   129     public Decoder(int capacity) {
       
   130         id = DECODERS_IDS.incrementAndGet();
       
   131         logger = HPACK.getLogger().subLogger("Decoder#" + id);
       
   132         if (logger.isLoggable(NORMAL)) {
       
   133             logger.log(NORMAL, () -> format("new decoder with maximum table size %s",
       
   134                                             capacity));
       
   135         }
       
   136         if (logger.isLoggable(NORMAL)) {
       
   137             /* To correlate with logging outside HPACK, knowing
       
   138                hashCode/toString is important */
       
   139             logger.log(NORMAL, () -> {
       
   140                 String hashCode = Integer.toHexString(
       
   141                         System.identityHashCode(this));
       
   142                 return format("toString='%s', identityHashCode=%s",
       
   143                               toString(), hashCode);
       
   144             });
       
   145         }
       
   146         setMaxCapacity0(capacity);
       
   147         table = new HeaderTable(capacity, logger.subLogger("HeaderTable"));
       
   148         integerReader = new IntegerReader();
       
   149         stringReader = new StringReader();
       
   150         name = new StringBuilder(512);
       
   151         value = new StringBuilder(1024);
       
   152     }
       
   153 
       
   154     /**
       
   155      * Sets a maximum capacity of the header table.
       
   156      *
       
   157      * <p> The value has to be agreed between decoder and encoder out-of-band,
       
   158      * e.g. by a protocol that uses HPACK
       
   159      * (see <a href="https://tools.ietf.org/html/rfc7541#section-4.2">4.2. Maximum Table Size</a>).
       
   160      *
       
   161      * @param capacity
       
   162      *         a non-negative integer
       
   163      *
       
   164      * @throws IllegalArgumentException
       
   165      *         if capacity is negative
       
   166      */
       
   167     public void setMaxCapacity(int capacity) {
       
   168         if (logger.isLoggable(NORMAL)) {
       
   169             logger.log(NORMAL, () -> format("setting maximum table size to %s",
       
   170                                             capacity));
       
   171         }
       
   172         setMaxCapacity0(capacity);
       
   173     }
       
   174 
       
   175     private void setMaxCapacity0(int capacity) {
       
   176         if (capacity < 0) {
       
   177             throw new IllegalArgumentException("capacity >= 0: " + capacity);
       
   178         }
       
   179         // FIXME: await capacity update if less than what was prior to it
       
   180         this.capacity = capacity;
       
   181     }
       
   182 
       
   183     /**
       
   184      * Decodes a header block from the given buffer to the given callback.
       
   185      *
       
   186      * <p> Suppose a header block is represented by a sequence of
       
   187      * {@code ByteBuffer}s in the form of {@code Iterator<ByteBuffer>}. And the
       
   188      * consumer of decoded headers is represented by the callback. Then to
       
   189      * decode the header block, the following approach might be used:
       
   190      *
       
   191      * <pre>{@code
       
   192      * while (buffers.hasNext()) {
       
   193      *     ByteBuffer input = buffers.next();
       
   194      *     decoder.decode(input, callback, !buffers.hasNext());
       
   195      * }
       
   196      * }</pre>
       
   197      *
       
   198      * <p> The decoder reads as much as possible of the header block from the
       
   199      * given buffer, starting at the buffer's position, and increments its
       
   200      * position to reflect the bytes read. The buffer's mark and limit will not
       
   201      * be modified.
       
   202      *
       
   203      * <p> Once the method is invoked with {@code endOfHeaderBlock == true}, the
       
   204      * current header block is deemed ended, and inconsistencies, if any, are
       
   205      * reported immediately by throwing an {@code IOException}.
       
   206      *
       
   207      * <p> Each callback method is called only after the implementation has
       
   208      * processed the corresponding bytes. If the bytes revealed a decoding
       
   209      * error, the callback method is not called.
       
   210      *
       
   211      * <p> In addition to exceptions thrown directly by the method, any
       
   212      * exceptions thrown from the {@code callback} will bubble up.
       
   213      *
       
   214      * @apiNote The method asks for {@code endOfHeaderBlock} flag instead of
       
   215      * returning it for two reasons. The first one is that the user of the
       
   216      * decoder always knows which chunk is the last. The second one is to throw
       
   217      * the most detailed exception possible, which might be useful for
       
   218      * diagnosing issues.
       
   219      *
       
   220      * @implNote This implementation is not atomic in respect to decoding
       
   221      * errors. In other words, if the decoding operation has thrown a decoding
       
   222      * error, the decoder is no longer usable.
       
   223      *
       
   224      * @param headerBlock
       
   225      *         the chunk of the header block, may be empty
       
   226      * @param endOfHeaderBlock
       
   227      *         true if the chunk is the final (or the only one) in the sequence
       
   228      *
       
   229      * @param consumer
       
   230      *         the callback
       
   231      * @throws IOException
       
   232      *         in case of a decoding error
       
   233      * @throws NullPointerException
       
   234      *         if either headerBlock or consumer are null
       
   235      */
       
   236     public void decode(ByteBuffer headerBlock,
       
   237                        boolean endOfHeaderBlock,
       
   238                        DecodingCallback consumer) throws IOException {
       
   239         requireNonNull(headerBlock, "headerBlock");
       
   240         requireNonNull(consumer, "consumer");
       
   241         if (logger.isLoggable(NORMAL)) {
       
   242             logger.log(NORMAL, () -> format("reading %s, end of header block? %s",
       
   243                                             headerBlock, endOfHeaderBlock));
       
   244         }
       
   245         while (headerBlock.hasRemaining()) {
       
   246             proceed(headerBlock, consumer);
       
   247         }
       
   248         if (endOfHeaderBlock && state != State.READY) {
       
   249             logger.log(NORMAL, () -> format("unexpected end of %s representation",
       
   250                                             state));
       
   251             throw new IOException("Unexpected end of header block");
       
   252         }
       
   253     }
       
   254 
       
   255     private void proceed(ByteBuffer input, DecodingCallback action)
       
   256             throws IOException {
       
   257         switch (state) {
       
   258             case READY:
       
   259                 resumeReady(input);
       
   260                 break;
       
   261             case INDEXED:
       
   262                 resumeIndexed(input, action);
       
   263                 break;
       
   264             case LITERAL:
       
   265                 resumeLiteral(input, action);
       
   266                 break;
       
   267             case LITERAL_WITH_INDEXING:
       
   268                 resumeLiteralWithIndexing(input, action);
       
   269                 break;
       
   270             case LITERAL_NEVER_INDEXED:
       
   271                 resumeLiteralNeverIndexed(input, action);
       
   272                 break;
       
   273             case SIZE_UPDATE:
       
   274                 resumeSizeUpdate(input, action);
       
   275                 break;
       
   276             default:
       
   277                 throw new InternalError("Unexpected decoder state: " + state);
       
   278         }
       
   279     }
       
   280 
       
   281     private void resumeReady(ByteBuffer input) {
       
   282         int b = input.get(input.position()) & 0xff; // absolute read
       
   283         State s = states[b];
       
   284         if (logger.isLoggable(EXTRA)) {
       
   285             logger.log(EXTRA, () -> format("next binary representation %s (first byte 0x%02x)",
       
   286                                            s, b));
       
   287         }
       
   288         switch (s) {
       
   289             case INDEXED:
       
   290                 integerReader.configure(7);
       
   291                 state = State.INDEXED;
       
   292                 firstValueIndex = true;
       
   293                 break;
       
   294             case LITERAL:
       
   295                 state = State.LITERAL;
       
   296                 firstValueIndex = (b & 0b0000_1111) != 0;
       
   297                 if (firstValueIndex) {
       
   298                     integerReader.configure(4);
       
   299                 }
       
   300                 break;
       
   301             case LITERAL_WITH_INDEXING:
       
   302                 state = State.LITERAL_WITH_INDEXING;
       
   303                 firstValueIndex = (b & 0b0011_1111) != 0;
       
   304                 if (firstValueIndex) {
       
   305                     integerReader.configure(6);
       
   306                 }
       
   307                 break;
       
   308             case LITERAL_NEVER_INDEXED:
       
   309                 state = State.LITERAL_NEVER_INDEXED;
       
   310                 firstValueIndex = (b & 0b0000_1111) != 0;
       
   311                 if (firstValueIndex) {
       
   312                     integerReader.configure(4);
       
   313                 }
       
   314                 break;
       
   315             case SIZE_UPDATE:
       
   316                 integerReader.configure(5);
       
   317                 state = State.SIZE_UPDATE;
       
   318                 firstValueIndex = true;
       
   319                 break;
       
   320             default:
       
   321                 throw new InternalError(String.valueOf(s));
       
   322         }
       
   323         if (!firstValueIndex) {
       
   324             input.get(); // advance, next stop: "String Literal"
       
   325         }
       
   326     }
       
   327 
       
   328     //              0   1   2   3   4   5   6   7
       
   329     //            +---+---+---+---+---+---+---+---+
       
   330     //            | 1 |        Index (7+)         |
       
   331     //            +---+---------------------------+
       
   332     //
       
   333     private void resumeIndexed(ByteBuffer input, DecodingCallback action)
       
   334             throws IOException {
       
   335         if (!integerReader.read(input)) {
       
   336             return;
       
   337         }
       
   338         intValue = integerReader.get();
       
   339         integerReader.reset();
       
   340         if (logger.isLoggable(NORMAL)) {
       
   341             logger.log(NORMAL, () -> format("indexed %s", intValue));
       
   342         }
       
   343         try {
       
   344             HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
       
   345             action.onIndexed(intValue, f.name, f.value);
       
   346         } finally {
       
   347             state = State.READY;
       
   348         }
       
   349     }
       
   350 
       
   351     private HeaderTable.HeaderField getHeaderFieldAt(int index)
       
   352             throws IOException
       
   353     {
       
   354         HeaderTable.HeaderField f;
       
   355         try {
       
   356             f = table.get(index);
       
   357         } catch (IndexOutOfBoundsException e) {
       
   358             throw new IOException("header fields table index", e);
       
   359         }
       
   360         return f;
       
   361     }
       
   362 
       
   363     //              0   1   2   3   4   5   6   7
       
   364     //            +---+---+---+---+---+---+---+---+
       
   365     //            | 0 | 0 | 0 | 0 |  Index (4+)   |
       
   366     //            +---+---+-----------------------+
       
   367     //            | H |     Value Length (7+)     |
       
   368     //            +---+---------------------------+
       
   369     //            | Value String (Length octets)  |
       
   370     //            +-------------------------------+
       
   371     //
       
   372     //              0   1   2   3   4   5   6   7
       
   373     //            +---+---+---+---+---+---+---+---+
       
   374     //            | 0 | 0 | 0 | 0 |       0       |
       
   375     //            +---+---+-----------------------+
       
   376     //            | H |     Name Length (7+)      |
       
   377     //            +---+---------------------------+
       
   378     //            |  Name String (Length octets)  |
       
   379     //            +---+---------------------------+
       
   380     //            | H |     Value Length (7+)     |
       
   381     //            +---+---------------------------+
       
   382     //            | Value String (Length octets)  |
       
   383     //            +-------------------------------+
       
   384     //
       
   385     private void resumeLiteral(ByteBuffer input, DecodingCallback action)
       
   386             throws IOException {
       
   387         if (!completeReading(input)) {
       
   388             return;
       
   389         }
       
   390         try {
       
   391             if (firstValueIndex) {
       
   392                 if (logger.isLoggable(NORMAL)) {
       
   393                     logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
       
   394                                                     intValue, value));
       
   395                 }
       
   396                 HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
       
   397                 action.onLiteral(intValue, f.name, value, valueHuffmanEncoded);
       
   398             } else {
       
   399                 if (logger.isLoggable(NORMAL)) {
       
   400                     logger.log(NORMAL, () -> format("literal without indexing ('%s', '%s')",
       
   401                                                     name, value));
       
   402                 }
       
   403                 action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
       
   404             }
       
   405         } finally {
       
   406             cleanUpAfterReading();
       
   407         }
       
   408     }
       
   409 
       
   410     //
       
   411     //              0   1   2   3   4   5   6   7
       
   412     //            +---+---+---+---+---+---+---+---+
       
   413     //            | 0 | 1 |      Index (6+)       |
       
   414     //            +---+---+-----------------------+
       
   415     //            | H |     Value Length (7+)     |
       
   416     //            +---+---------------------------+
       
   417     //            | Value String (Length octets)  |
       
   418     //            +-------------------------------+
       
   419     //
       
   420     //              0   1   2   3   4   5   6   7
       
   421     //            +---+---+---+---+---+---+---+---+
       
   422     //            | 0 | 1 |           0           |
       
   423     //            +---+---+-----------------------+
       
   424     //            | H |     Name Length (7+)      |
       
   425     //            +---+---------------------------+
       
   426     //            |  Name String (Length octets)  |
       
   427     //            +---+---------------------------+
       
   428     //            | H |     Value Length (7+)     |
       
   429     //            +---+---------------------------+
       
   430     //            | Value String (Length octets)  |
       
   431     //            +-------------------------------+
       
   432     //
       
   433     private void resumeLiteralWithIndexing(ByteBuffer input,
       
   434                                            DecodingCallback action)
       
   435             throws IOException {
       
   436         if (!completeReading(input)) {
       
   437             return;
       
   438         }
       
   439         try {
       
   440             //
       
   441             // 1. (name, value) will be stored in the table as strings
       
   442             // 2. Most likely the callback will also create strings from them
       
   443             // ------------------------------------------------------------------------
       
   444             //    Let's create those string beforehand (and only once!) to benefit everyone
       
   445             //
       
   446             String n;
       
   447             String v = value.toString();
       
   448             if (firstValueIndex) {
       
   449                 if (logger.isLoggable(NORMAL)) {
       
   450                     logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
       
   451                                                     intValue, value));
       
   452                 }
       
   453                 HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
       
   454                 n = f.name;
       
   455                 action.onLiteralWithIndexing(intValue, n, v, valueHuffmanEncoded);
       
   456             } else {
       
   457                 n = name.toString();
       
   458                 if (logger.isLoggable(NORMAL)) {
       
   459                     logger.log(NORMAL, () -> format("literal with incremental indexing ('%s', '%s')",
       
   460                                                     n, value));
       
   461                 }
       
   462                 action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded);
       
   463             }
       
   464             table.put(n, v);
       
   465         } finally {
       
   466             cleanUpAfterReading();
       
   467         }
       
   468     }
       
   469 
       
   470     //              0   1   2   3   4   5   6   7
       
   471     //            +---+---+---+---+---+---+---+---+
       
   472     //            | 0 | 0 | 0 | 1 |  Index (4+)   |
       
   473     //            +---+---+-----------------------+
       
   474     //            | H |     Value Length (7+)     |
       
   475     //            +---+---------------------------+
       
   476     //            | Value String (Length octets)  |
       
   477     //            +-------------------------------+
       
   478     //
       
   479     //              0   1   2   3   4   5   6   7
       
   480     //            +---+---+---+---+---+---+---+---+
       
   481     //            | 0 | 0 | 0 | 1 |       0       |
       
   482     //            +---+---+-----------------------+
       
   483     //            | H |     Name Length (7+)      |
       
   484     //            +---+---------------------------+
       
   485     //            |  Name String (Length octets)  |
       
   486     //            +---+---------------------------+
       
   487     //            | H |     Value Length (7+)     |
       
   488     //            +---+---------------------------+
       
   489     //            | Value String (Length octets)  |
       
   490     //            +-------------------------------+
       
   491     //
       
   492     private void resumeLiteralNeverIndexed(ByteBuffer input,
       
   493                                            DecodingCallback action)
       
   494             throws IOException {
       
   495         if (!completeReading(input)) {
       
   496             return;
       
   497         }
       
   498         try {
       
   499             if (firstValueIndex) {
       
   500                 if (logger.isLoggable(NORMAL)) {
       
   501                     logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
       
   502                                                     intValue, value));
       
   503                 }
       
   504                 HeaderTable.HeaderField f = getHeaderFieldAt(intValue);
       
   505                 action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded);
       
   506             } else {
       
   507                 if (logger.isLoggable(NORMAL)) {
       
   508                     logger.log(NORMAL, () -> format("literal never indexed ('%s', '%s')",
       
   509                                                     name, value));
       
   510                 }
       
   511                 action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded);
       
   512             }
       
   513         } finally {
       
   514             cleanUpAfterReading();
       
   515         }
       
   516     }
       
   517 
       
   518     //              0   1   2   3   4   5   6   7
       
   519     //            +---+---+---+---+---+---+---+---+
       
   520     //            | 0 | 0 | 1 |   Max size (5+)   |
       
   521     //            +---+---------------------------+
       
   522     //
       
   523     private void resumeSizeUpdate(ByteBuffer input,
       
   524                                   DecodingCallback action) throws IOException {
       
   525         if (!integerReader.read(input)) {
       
   526             return;
       
   527         }
       
   528         intValue = integerReader.get();
       
   529         if (logger.isLoggable(NORMAL)) {
       
   530             logger.log(NORMAL, () -> format("dynamic table size update %s",
       
   531                                             intValue));
       
   532         }
       
   533         assert intValue >= 0;
       
   534         if (intValue > capacity) {
       
   535             throw new IOException(
       
   536                     format("Received capacity exceeds expected: capacity=%s, expected=%s",
       
   537                            intValue, capacity));
       
   538         }
       
   539         integerReader.reset();
       
   540         try {
       
   541             action.onSizeUpdate(intValue);
       
   542             table.setMaxSize(intValue);
       
   543         } finally {
       
   544             state = State.READY;
       
   545         }
       
   546     }
       
   547 
       
   548     private boolean completeReading(ByteBuffer input) throws IOException {
       
   549         if (!firstValueRead) {
       
   550             if (firstValueIndex) {
       
   551                 if (!integerReader.read(input)) {
       
   552                     return false;
       
   553                 }
       
   554                 intValue = integerReader.get();
       
   555                 integerReader.reset();
       
   556             } else {
       
   557                 if (!stringReader.read(input, name)) {
       
   558                     return false;
       
   559                 }
       
   560                 nameHuffmanEncoded = stringReader.isHuffmanEncoded();
       
   561                 stringReader.reset();
       
   562             }
       
   563             firstValueRead = true;
       
   564             return false;
       
   565         } else {
       
   566             if (!stringReader.read(input, value)) {
       
   567                 return false;
       
   568             }
       
   569         }
       
   570         valueHuffmanEncoded = stringReader.isHuffmanEncoded();
       
   571         stringReader.reset();
       
   572         return true;
       
   573     }
       
   574 
       
   575     private void cleanUpAfterReading() {
       
   576         name.setLength(0);
       
   577         value.setLength(0);
       
   578         firstValueRead = false;
       
   579         state = State.READY;
       
   580     }
       
   581 
       
   582     private enum State {
       
   583         READY,
       
   584         INDEXED,
       
   585         LITERAL_NEVER_INDEXED,
       
   586         LITERAL,
       
   587         LITERAL_WITH_INDEXING,
       
   588         SIZE_UPDATE
       
   589     }
       
   590 
       
   591     HeaderTable getTable() {
       
   592         return table;
       
   593     }
       
   594 }