com.neovisionaries.ws.client.DeflateDecompressor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nv-websocket-client Show documentation
Show all versions of nv-websocket-client Show documentation
WebSocket client implementation in Java.
/*
* 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);
}
}