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

com.neovisionaries.ws.client.DeflateDecompressor Maven / Gradle / Ivy

There is a newer version: 2.14
Show newest version
/*
 * Copyright (C) 2015 Neo Visionaries 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 com.neovisionaries.ws.client;


/**
 * DEFLATE (RFC 1951)
 * decompressor implementation from scratch.
 */
class DeflateDecompressor
{
    public static void decompress(ByteArray input, ByteArray output) throws FormatException
    {
        decompress(input, 0, output);
    }


    private static void decompress(ByteArray input, int index, ByteArray output) throws FormatException
    {
        // The data is compressed on a bit basis, so use a bit index.
        int[] bitIndex = new int[1];
        bitIndex[0] = index * 8;

        // Process all blocks one by one until the end.
        // inflateBlock() returns false if no more block exists.
        while (inflateBlock(input, bitIndex, output)) {}
    }


    private static boolean inflateBlock(
            ByteArray input, int[] bitIndex, ByteArray output) throws FormatException
    {
        // Each block has a block header which consists of 3 bits.
        // See 3.2.3. of RFC 1951.

        // The first bit indicates whether the block is the last one or not.
        boolean last = input.readBit(bitIndex);

        // The combination of the second and the third bits indicate the
        // compression type of the block. Compression types are as follows:
        //
        //     00: No compression.
        //     01: Compressed with fixed Huffman codes
        //     10: Compressed with dynamic Huffman codes
        //     11: Reserved (error)
        //
        int type = input.readBits(bitIndex, 2);

        switch (type)
        {
            // No compression
            case 0:
                inflatePlainBlock(input, bitIndex, output);
                break;

            // Compressed with fixed Huffman codes
            case 1:
                inflateFixedBlock(input, bitIndex, output);
                break;

            // Compressed with dynamic Huffman codes
            case 2:
                inflateDynamicBlock(input, bitIndex, output);
                break;

            // Bad format
            default:
                // Bad compression type at the bit index.
                String message = String.format(
                        "[%s] Bad compression type '11' at the bit index '%d'.",
                        DeflateDecompressor.class.getSimpleName(), bitIndex[0]);

                throw new FormatException(message);
        }

        // If no more data are available.
        if (input.length() <= (bitIndex[0] / 8))
        {
            // Last even if BFINAL bit is false.
            last = true;
        }

        // Return true if this block is not the last one.
        return !last;
    }


    private static void inflatePlainBlock(ByteArray input, int[] bitIndex, ByteArray output)
    {
        // 3.2.4 Non-compressed blocks (BTYPE=00)

        // Skip any remaining bits in current partially processed byte.
        int bi = (bitIndex[0] + 7) & ~7;

        // Data copy is performed on a byte basis, so convert the bit index
        // to a byte index.
        int index = bi / 8;

        // LEN: 2 bytes. The data length.
        int len = (input.get(index) & 0xFF) + (input.get(index + 1) & 0xFF) * 256;

        // NLEN: 2 bytes. The one's complement of LEN.

        // Skip LEN and NLEN.
        index += 4;

        // Copy the data to the output.
        output.put(input, index, len);

        // Make the bitIndex point to the bit next to
        // the end of the copied data.
        bitIndex[0] = (index + len) * 8;
    }


    private static void inflateFixedBlock(
            ByteArray input, int[] bitIndex, ByteArray output) throws FormatException
    {
        // 3.2.6 Compression with fixed Huffman codes (BTYPE=01)

        // Inflate the compressed data using the pre-defined
        // conversion tables. The specification says in 3.2.2
        // as follows.
        //
        //   The only differences between the two compressed
        //   cases is how the Huffman codes for the literal/
        //   length and distance alphabets are defined.
        //
        // The "two compressed cases" in the above sentence are
        // "fixed Huffman codes" and "dynamic Huffman codes".
        inflateData(input, bitIndex, output,
                FixedLiteralLengthHuffman.getInstance(),
                FixedDistanceHuffman.getInstance());
    }


    private static void inflateDynamicBlock(
            ByteArray input, int[] bitIndex, ByteArray output) throws FormatException
    {
        // 3.2.7 Compression with dynamic Huffman codes (BTYPE=10)

        // Read 2 tables. One is a table to convert "code value of literal/length
        // alphabet" into "literal/length symbol". The other is a table to convert
        // "code value of distance alphabet" into "distance symbol".
        Huffman[] tables = new Huffman[2];
        DeflateUtil.readDynamicTables(input, bitIndex, tables);

        // The actual compressed data of this block. The data are encoded using
        // the literal/length and distance Huffman codes that were parsed above.
        inflateData(input, bitIndex, output, tables[0], tables[1]);
    }


    private static void inflateData(
            ByteArray input, int[] bitIndex, ByteArray output,
            Huffman literalLengthHuffman, Huffman distanceHuffman) throws FormatException
    {
        // 3.2.5 Compressed blocks (length and distance codes)

        while (true)
        {
            // Read a literal/length symbol from the input.
            int literalLength = literalLengthHuffman.readSym(input, bitIndex);

            // Symbol value '256' indicates the end.
            if (literalLength == 256)
            {
                // End of this data.
                break;
            }

            // Symbol values from 0 to 255 represent literal values.
            if (0 <= literalLength && literalLength <= 255)
            {
                // Output as is.
                output.put(literalLength);
                continue;
            }

            // Symbol values from 257 to 285 represent  pairs.
            // Depending on symbol values, some extra bits in the input may be
            // consumed to compute the length.
            int length = DeflateUtil.readLength(input, bitIndex, literalLength);

            // Read the distance from the input.
            int distance = DeflateUtil.readDistance(input, bitIndex, distanceHuffman);

            // Extract some data from the output buffer and copy them.
            duplicate(length, distance, output);
        }
    }


    private static void duplicate(int length, int distance, ByteArray output)
    {
        // Get the number of bytes written so far.
        int sourceLength = output.length();

        // An array to finally append to the output.
        byte[] target = new byte[length];

        // The position from which to start copying data.
        int initialPosition = sourceLength - distance;
        int sourceIndex = initialPosition;

        for (int targetIndex = 0; targetIndex < length; ++targetIndex, ++sourceIndex)
        {
            if (sourceLength <= sourceIndex)
            {
                // Reached the end of the current output buffer.
                // The specification says as follows in 3.2.3.
                //
                //   Note also that the referenced string may
                //   overlap the current position; for example,
                //   if the last 2 bytes decoded have values X
                //   and Y, a string reference with  adds X,Y,X,Y,X to the output
                //   stream.

                // repeat.
                sourceIndex = initialPosition;
            }

            target[targetIndex] = output.get(sourceIndex);
        }

        // Append the duplicated bytes to the output.
        output.put(target);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy