View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http2.hpack;
29  
30  import java.nio.ByteBuffer;
31  
32  import org.apache.hc.core5.util.ByteArrayBuffer;
33  
34  /**
35   * This Huffman codec implementation has been derived from Twitter HPack project
36   * (https://github.com/twitter/hpack)
37   */
38  final class HuffmanDecoder {
39  
40      private final HuffmanNode root;
41  
42      HuffmanDecoder(final int[] codes, final byte[] lengths) {
43          root = buildTree(codes, lengths);
44      }
45  
46      void decode(final ByteArrayBuffer out, final ByteBuffer src) throws HPackException {
47          HuffmanNode node = this.root;
48          int current = 0;
49          int bits = 0;
50          while (src.hasRemaining()) {
51              final int b = src.get() & 0xFF;
52              current = (current << 8) | b;
53              bits += 8;
54              while (bits >= 8) {
55                  final int c = (current >>> (bits - 8)) & 0xFF;
56                  node = node.getChild(c);
57                  bits -= node.getBits();
58                  if (node.isTerminal()) {
59                      if (node.getSymbol() == Huffman.EOS) {
60                          throw new HPackException("EOS decoded");
61                      }
62                      out.append(node.getSymbol());
63                      node = root;
64                  }
65              }
66          }
67  
68          while (bits > 0) {
69              final int c = (current << (8 - bits)) & 0xFF;
70              node = node.getChild(c);
71              if (node.isTerminal() && node.getBits() <= bits) {
72                  bits -= node.getBits();
73                  out.append(node.getSymbol());
74                  node = this.root;
75              } else {
76                  break;
77              }
78          }
79  
80          // Section 5.2. String Literal Representation
81          // Padding not corresponding to the most significant bits of the code
82          // for the EOS symbol (0xFF) MUST be treated as a decoding error.
83          final int mask = (1 << bits) - 1;
84          if ((current & mask) != mask) {
85              throw new HPackException("Invalid padding");
86          }
87      }
88  
89      private static HuffmanNode buildTree(final int[] codes, final byte[] lengths) {
90          final HuffmanNode/hpack/HuffmanNode.html#HuffmanNode">HuffmanNode root = new HuffmanNode();
91          for (int symbol = 0; symbol < codes.length; symbol++) {
92  
93              final int code = codes[symbol];
94              int length = lengths[symbol];
95  
96              HuffmanNode current = root;
97              while (length > 8) {
98                  if (current.isTerminal()) {
99                      throw new IllegalStateException("Invalid Huffman code: prefix not unique");
100                 }
101                 length -= 8;
102                 final int i = (code >>> length) & 0xFF;
103                 if (!current.hasChild(i)) {
104                     current.setChild(i, new HuffmanNode());
105                 }
106                 current = current.getChild(i);
107             }
108 
109             final HuffmanNodeck/HuffmanNode.html#HuffmanNode">HuffmanNode terminal = new HuffmanNode(symbol, length);
110             final int shift = 8 - length;
111             final int start = (code << shift) & 0xFF;
112             final int end = 1 << shift;
113             for (int i = start; i < start + end; i++) {
114                 current.setChild(i, terminal);
115             }
116         }
117         return root;
118     }
119 
120 }