All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.http4s.hpack.HuffmanDecoder.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2022 http4s.org
 *
 * 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.
 */

/*
 * 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.
 */
package org.http4s.hpack;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import HuffmanDecoder._

private[http4s] final class HuffmanDecoder(root: Node) {

  private[this] val EOS_DECODED = new IOException("EOS Decoded");
  private[this] val INVALID_PADDING = new IOException("Invalid Padding");

  /** Creates a new Huffman decoder with the specified Huffman coding.
    * @param codes   the Huffman codes indexed by symbol
    * @param lengths the length of each Huffman code
    */
  def this(codes: Array[Int], lengths: Array[Byte]) =
    this {
      if (codes.length != 257 || codes.length != lengths.length) {
        throw new IllegalArgumentException("invalid Huffman coding");
      }
      buildTree(codes, lengths);
    }

  /** Decompresses the given Huffman coded string literal.
    * @param  buf the string literal to be decoded
    * @return the output stream for the compressed data
    * @throws IOException if an I/O error occurs. In particular,
    *         an IOException may be thrown if the
    *         output stream has been closed.
    */
  @throws[IOException]
  def decode(buf: Array[Byte]): Array[Byte] = {
    val baos = new ByteArrayOutputStream();

    var node = root;
    var current = 0;
    var bits = 0;
    var i = 0
    while (i < buf.length) {
      val b = buf(i) & 0xff;
      current = (current << 8) | b;
      bits += 8;
      while (bits >= 8) {
        val c = (current >>> (bits - 8)) & 0xff;
        node = node.children(c);
        bits -= node.bits;
        if (node.isTerminal()) {
          if (node.symbol == HpackUtil.HUFFMAN_EOS) {
            throw EOS_DECODED;
          }
          baos.write(node.symbol);
          node = root;
        }
      }
      i += 1
    }

    var break = false
    while (!break && bits > 0) {
      val c = (current << (8 - bits)) & 0xff;
      node = node.children(c);
      if (node.isTerminal() && node.bits <= bits) {
        bits -= node.bits;
        baos.write(node.symbol);
        node = root;
      } else {
        break = true;
      }
    }

    // Section 5.2. String Literal Representation
    // Padding not corresponding to the most significant bits of the code
    // for the EOS symbol (0xFF) MUST be treated as a decoding error.
    val mask = (1 << bits) - 1;
    if ((current & mask) != mask) {
      throw INVALID_PADDING;
    }

    return baos.toByteArray();
  }

}

private[http4s] object HuffmanDecoder {

  private[hpack] final class Node(
      val symbol: Int, // terminal nodes have a symbol
      val bits: Int, // number of bits matched by the node
      val children: Array[Node], // internal nodes have children
  ) {

    /** Construct an internal node
      */
    private[HuffmanDecoder] def this() =
      this(0, 8, new Array[Node](256))

    /** Construct a terminal node
      * @param symbol the symbol the node represents
      * @param bits   the number of bits matched by this node
      */
    private[HuffmanDecoder] def this(symbol: Int, bits: Int) {
      this(symbol, bits, null)
      assert(bits > 0 && bits <= 8);
    }

    private[HuffmanDecoder] def isTerminal(): Boolean =
      return children == null;
  }

  private def buildTree(codes: Array[Int], lengths: Array[Byte]): Node = {
    val root = new Node();
    var i = 0
    while (i < codes.length) {
      insert(root, i, codes(i), lengths(i));
      i += 1
    }
    return root;
  }

  private[this] def insert(root: Node, symbol: Int, code: Int, _length: Byte): Unit = {
    var length = _length
    // traverse tree using the most significant bytes of code
    var current = root;
    while (length > 8) {
      if (current.isTerminal()) {
        throw new IllegalStateException("invalid Huffman code: prefix not unique");
      }
      length = (length - 8).toByte;
      val i = (code >>> length) & 0xff;
      if (current.children(i) == null) {
        current.children(i) = new Node();
      }
      current = current.children(i);
    }

    val terminal = new Node(symbol, length);
    val shift = 8 - length;
    val start = (code << shift) & 0xff;
    val end = 1 << shift;
    var i = start
    while (i < start + end) {
      current.children(i) = terminal;
      i += 1
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy