blob: 7dd4d095142ca41441634f10a86f2bb74f630449 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library http2.src.hpack.huffman;
import 'dart:io';
import 'huffman_table.dart';
class HuffmanDecodingException implements Exception {
final String _message;
HuffmanDecodingException(this._message);
String toString() => 'HuffmanDecodingException: $_message';
}
/// A codec used for encoding/decoding using a huffman codec.
class HuffmanCodec {
final HuffmanEncoder _encoder;
final HuffmanDecoder _decoder;
HuffmanCodec(this._encoder, this._decoder);
List<int> decode(List<int> bytes) => _decoder.decode(bytes);
List<int> encode(List<int> bytes) => _encoder.encode(bytes);
}
/// A huffman decoder based on a [HuffmanTreeNode].
class HuffmanDecoder {
final HuffmanTreeNode _root;
HuffmanDecoder(this._root);
/// Decodes [bytes] using a huffman tree.
List<int> decode(List<int> bytes) {
var buffer = new BytesBuilder();
int currentByteOffset = 0;
HuffmanTreeNode node = _root;
int currentDepth = 0;
while (currentByteOffset < bytes.length) {
var byte = bytes[currentByteOffset];
for (int currentBit = 7; currentBit >= 0; currentBit--) {
bool right = (byte >> currentBit) & 1 == 1;
if (right) {
node = node.right;
} else {
node = node.left;
}
currentDepth++;
if (node.value != null) {
if (node.value == EOS_BYTE) {
throw new HuffmanDecodingException(
'More than 7 bit padding is not allowed. Found entire EOS '
'encoding');
}
buffer.addByte(node.value);
node = _root;
currentDepth = 0;
}
}
currentByteOffset++;
}
if (node != _root) {
if (currentDepth > 7) {
throw new HuffmanDecodingException(
'Incomplete encoding of a byte or more than 7 bit padding.');
}
while (node.right != null) node = node.right;
if (node.value != 256) {
throw new HuffmanDecodingException('Incomplete encoding of a byte.');
}
}
return buffer.takeBytes();
}
}
/// A huffman encoder based on a list of codewords.
class HuffmanEncoder {
final List<EncodedHuffmanValue> _codewords;
HuffmanEncoder(this._codewords);
/// Encodes [bytes] using a list of codewords.
List<int> encode(List<int> bytes) {
var buffer = new BytesBuilder();
int currentByte = 0;
int currentBitOffset = 7;
writeValue(int value, int numBits) {
int i = numBits - 1;
while (i >= 0) {
if (currentBitOffset == 7 && i >= 7) {
assert(currentByte == 0);
buffer.addByte((value >> (i - 7)) & 0xff);
currentBitOffset = 7;
currentByte = 0;
i -= 8;
} else {
currentByte |= ((value >> i) & 1) << currentBitOffset;
currentBitOffset--;
if (currentBitOffset == -1) {
buffer.addByte(currentByte);
currentBitOffset = 7;
currentByte = 0;
}
i--;
}
}
}
for (int i = 0; i < bytes.length; i++) {
var byte = bytes[i];
var value = _codewords[byte];
writeValue(value.encodedBytes, value.numBits);
}
if (currentBitOffset < 7) {
writeValue(0xff, 1 + currentBitOffset);
}
return buffer.takeBytes();
}
}
/// Specifies the encoding of a specific value using huffman encoding.
class EncodedHuffmanValue {
/// An integer representation of the encoded bit-string.
final int encodedBytes;
/// The number of bits in [encodedBytes].
final int numBits;
const EncodedHuffmanValue(this.encodedBytes, this.numBits);
}
/// A node in the huffman tree.
class HuffmanTreeNode {
HuffmanTreeNode left;
HuffmanTreeNode right;
int value;
}
/// Generates a huffman decoding tree.
HuffmanTreeNode generateHuffmanTree(List<EncodedHuffmanValue> valueEncodings) {
HuffmanTreeNode root = new HuffmanTreeNode();
for (int byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) {
var entry = valueEncodings[byteOffset];
HuffmanTreeNode current = root;
for (int bitNr = 0; bitNr < entry.numBits; bitNr++) {
bool right =
((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1;
if (right) {
if (current.right == null) {
current.right = new HuffmanTreeNode();
}
current = current.right;
} else {
if (current.left == null) {
current.left = new HuffmanTreeNode();
}
current = current.left;
}
}
current.value = byteOffset;
}
return root;
}