2015-11-04 21:46:03 +01:00
|
|
|
/*
|
|
|
|
* Copyright 2015 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright 2014 Twitter, Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
2016-04-12 14:22:41 +02:00
|
|
|
package io.netty.handler.codec.http2.internal.hpack;
|
2015-11-04 21:46:03 +01:00
|
|
|
|
2016-05-30 13:40:08 +02:00
|
|
|
import io.netty.buffer.ByteBuf;
|
2016-08-05 09:09:43 +02:00
|
|
|
import io.netty.handler.codec.http2.Http2Exception;
|
2016-05-30 13:40:08 +02:00
|
|
|
import io.netty.util.AsciiString;
|
|
|
|
import io.netty.util.ByteProcessor;
|
|
|
|
import io.netty.util.internal.ObjectUtil;
|
2016-08-05 09:09:43 +02:00
|
|
|
import io.netty.util.internal.ThrowableUtil;
|
2016-05-10 18:09:37 +02:00
|
|
|
|
2016-08-05 09:09:43 +02:00
|
|
|
import static io.netty.handler.codec.http2.Http2Error.COMPRESSION_ERROR;
|
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
2016-05-30 13:40:08 +02:00
|
|
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODES;
|
|
|
|
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.HUFFMAN_CODE_LENGTHS;
|
|
|
|
|
2015-11-04 21:46:03 +01:00
|
|
|
final class HuffmanDecoder {
|
|
|
|
|
2016-08-05 09:09:43 +02:00
|
|
|
private static final Http2Exception EOS_DECODED = ThrowableUtil.unknownStackTrace(
|
|
|
|
connectionError(COMPRESSION_ERROR, "HPACK - EOS Decoded"), HuffmanDecoder.class, "decode(...)");
|
|
|
|
private static final Http2Exception INVALID_PADDING = ThrowableUtil.unknownStackTrace(
|
|
|
|
connectionError(COMPRESSION_ERROR, "HPACK - Invalid Padding"), HuffmanDecoder.class, "decode(...)");
|
2015-11-04 21:46:03 +01:00
|
|
|
|
2016-05-30 13:40:08 +02:00
|
|
|
private static final Node ROOT = buildTree(HUFFMAN_CODES, HUFFMAN_CODE_LENGTHS);
|
2015-11-04 21:46:03 +01:00
|
|
|
|
2016-05-30 13:40:08 +02:00
|
|
|
private final DecoderProcessor processor;
|
|
|
|
|
|
|
|
HuffmanDecoder(int initialCapacity) {
|
|
|
|
processor = new DecoderProcessor(initialCapacity);
|
2015-11-04 21:46:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decompresses the given Huffman coded string literal.
|
|
|
|
*
|
|
|
|
* @param buf the string literal to be decoded
|
|
|
|
* @return the output stream for the compressed data
|
2017-02-21 21:27:23 +01:00
|
|
|
* @throws Http2Exception EOS Decoded
|
2015-11-04 21:46:03 +01:00
|
|
|
*/
|
2016-08-05 09:09:43 +02:00
|
|
|
public AsciiString decode(ByteBuf buf, int length) throws Http2Exception {
|
2016-05-30 13:40:08 +02:00
|
|
|
processor.reset();
|
|
|
|
buf.forEachByte(buf.readerIndex(), length, processor);
|
|
|
|
buf.skipBytes(length);
|
|
|
|
return processor.end();
|
2015-11-04 21:46:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private static final class Node {
|
|
|
|
|
|
|
|
private final int symbol; // terminal nodes have a symbol
|
|
|
|
private final int bits; // number of bits matched by the node
|
|
|
|
private final Node[] children; // internal nodes have children
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct an internal node
|
|
|
|
*/
|
2016-05-30 13:40:08 +02:00
|
|
|
Node() {
|
2015-11-04 21:46:03 +01:00
|
|
|
symbol = 0;
|
|
|
|
bits = 8;
|
|
|
|
children = new Node[256];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a terminal node
|
|
|
|
*
|
|
|
|
* @param symbol the symbol the node represents
|
|
|
|
* @param bits the number of bits matched by this node
|
|
|
|
*/
|
2016-05-30 13:40:08 +02:00
|
|
|
Node(int symbol, int bits) {
|
2015-11-04 21:46:03 +01:00
|
|
|
assert bits > 0 && bits <= 8;
|
|
|
|
this.symbol = symbol;
|
|
|
|
this.bits = bits;
|
|
|
|
children = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean isTerminal() {
|
|
|
|
return children == null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Node buildTree(int[] codes, byte[] lengths) {
|
|
|
|
Node root = new Node();
|
|
|
|
for (int i = 0; i < codes.length; i++) {
|
|
|
|
insert(root, i, codes[i], lengths[i]);
|
|
|
|
}
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void insert(Node root, int symbol, int code, byte length) {
|
|
|
|
// traverse tree using the most significant bytes of code
|
|
|
|
Node current = root;
|
|
|
|
while (length > 8) {
|
|
|
|
if (current.isTerminal()) {
|
|
|
|
throw new IllegalStateException("invalid Huffman code: prefix not unique");
|
|
|
|
}
|
|
|
|
length -= 8;
|
|
|
|
int i = (code >>> length) & 0xFF;
|
|
|
|
if (current.children[i] == null) {
|
|
|
|
current.children[i] = new Node();
|
|
|
|
}
|
|
|
|
current = current.children[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
Node terminal = new Node(symbol, length);
|
|
|
|
int shift = 8 - length;
|
|
|
|
int start = (code << shift) & 0xFF;
|
|
|
|
int end = 1 << shift;
|
|
|
|
for (int i = start; i < start + end; i++) {
|
|
|
|
current.children[i] = terminal;
|
|
|
|
}
|
|
|
|
}
|
2016-05-30 13:40:08 +02:00
|
|
|
|
|
|
|
private static final class DecoderProcessor implements ByteProcessor {
|
|
|
|
private final int initialCapacity;
|
|
|
|
private byte[] bytes;
|
|
|
|
private int index;
|
|
|
|
private Node node;
|
|
|
|
private int current;
|
|
|
|
private int currentBits;
|
|
|
|
private int symbolBits;
|
|
|
|
|
|
|
|
DecoderProcessor(int initialCapacity) {
|
|
|
|
this.initialCapacity = ObjectUtil.checkPositive(initialCapacity, "initialCapacity");
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset() {
|
|
|
|
node = ROOT;
|
|
|
|
current = 0;
|
|
|
|
currentBits = 0;
|
|
|
|
symbolBits = 0;
|
|
|
|
bytes = new byte[initialCapacity];
|
|
|
|
index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The idea here is to consume whole bytes at a time rather than individual bits. node
|
|
|
|
* represents the Huffman tree, with all bit patterns denormalized as 256 children. Each
|
|
|
|
* child represents the last 8 bits of the huffman code. The parents of each child each
|
|
|
|
* represent the successive 8 bit chunks that lead up to the last most part. 8 bit bytes
|
|
|
|
* from buf are used to traverse these tree until a terminal node is found.
|
|
|
|
*
|
|
|
|
* current is a bit buffer. The low order bits represent how much of the huffman code has
|
|
|
|
* not been used to traverse the tree. Thus, the high order bits are just garbage.
|
|
|
|
* currentBits represents how many of the low order bits of current are actually valid.
|
|
|
|
* currentBits will vary between 0 and 15.
|
|
|
|
*
|
|
|
|
* symbolBits is the number of bits of the the symbol being decoded, *including* all those
|
|
|
|
* of the parent nodes. symbolBits tells how far down the tree we are. For example, when
|
|
|
|
* decoding the invalid sequence {0xff, 0xff}, currentBits will be 0, but symbolBits will be
|
|
|
|
* 16. This is used to know if buf ended early (before consuming a whole symbol) or if
|
|
|
|
* there is too much padding.
|
|
|
|
*/
|
|
|
|
@Override
|
2016-08-05 09:09:43 +02:00
|
|
|
public boolean process(byte value) throws Http2Exception {
|
2016-05-30 13:40:08 +02:00
|
|
|
current = (current << 8) | (value & 0xFF);
|
|
|
|
currentBits += 8;
|
|
|
|
symbolBits += 8;
|
|
|
|
// While there are unconsumed bits in current, keep consuming symbols.
|
|
|
|
do {
|
|
|
|
node = node.children[(current >>> (currentBits - 8)) & 0xFF];
|
|
|
|
currentBits -= node.bits;
|
|
|
|
if (node.isTerminal()) {
|
|
|
|
if (node.symbol == HpackUtil.HUFFMAN_EOS) {
|
|
|
|
throw EOS_DECODED;
|
|
|
|
}
|
|
|
|
append(node.symbol);
|
|
|
|
node = ROOT;
|
|
|
|
// Upon consuming a whole symbol, reset the symbol bits to the number of bits
|
|
|
|
// left over in the byte.
|
|
|
|
symbolBits = currentBits;
|
|
|
|
}
|
|
|
|
} while (currentBits >= 8);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-08-05 09:09:43 +02:00
|
|
|
AsciiString end() throws Http2Exception {
|
2016-05-30 13:40:08 +02:00
|
|
|
/*
|
|
|
|
* We have consumed all the bytes in buf, but haven't consumed all the symbols. We may be on
|
|
|
|
* a partial symbol, so consume until there is nothing left. This will loop at most 2 times.
|
|
|
|
*/
|
|
|
|
while (currentBits > 0) {
|
|
|
|
node = node.children[(current << (8 - currentBits)) & 0xFF];
|
|
|
|
if (node.isTerminal() && node.bits <= currentBits) {
|
|
|
|
if (node.symbol == HpackUtil.HUFFMAN_EOS) {
|
|
|
|
throw EOS_DECODED;
|
|
|
|
}
|
|
|
|
currentBits -= node.bits;
|
|
|
|
append(node.symbol);
|
|
|
|
node = ROOT;
|
|
|
|
symbolBits = currentBits;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Section 5.2. String Literal Representation
|
|
|
|
// A padding strictly longer than 7 bits MUST be treated as a decoding error.
|
|
|
|
// Padding not corresponding to the most significant bits of the code
|
|
|
|
// for the EOS symbol (0xFF) MUST be treated as a decoding error.
|
|
|
|
int mask = (1 << symbolBits) - 1;
|
|
|
|
if (symbolBits > 7 || (current & mask) != mask) {
|
|
|
|
throw INVALID_PADDING;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new AsciiString(bytes, 0, index, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void append(int i) {
|
|
|
|
try {
|
|
|
|
bytes[index] = (byte) i;
|
|
|
|
} catch (IndexOutOfBoundsException ignore) {
|
|
|
|
// Always just expand by INITIAL_SIZE
|
|
|
|
byte[] newBytes = new byte[bytes.length + initialCapacity];
|
|
|
|
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
|
|
|
|
bytes = newBytes;
|
|
|
|
bytes[index] = (byte) i;
|
|
|
|
}
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
2015-11-04 21:46:03 +01:00
|
|
|
}
|