
de.carne.nio.compression.deflate.DeflateDecoder Maven / Gradle / Ivy
/*
* Copyright (c) 2016 Holger de Carne and contributors, All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser Public License
* along with this program. If not, see .
*/
package de.carne.nio.compression.deflate;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import de.carne.nio.compression.InvalidDataException;
import de.carne.nio.compression.common.BitDecoder;
import de.carne.nio.compression.common.BitRegister;
import de.carne.nio.compression.common.HistoryBuffer;
import de.carne.nio.compression.common.HuffmanDecoder;
import de.carne.nio.compression.common.LSBBitstreamBitRegister;
import de.carne.nio.compression.common.LSBBytesBitRegister;
import de.carne.nio.compression.spi.Decoder;
/**
* Deflate decoder:
* https://en.wikipedia.org/wiki
* /DEFLATE
*/
public class DeflateDecoder extends Decoder implements DeflateName {
private final HashSet modes;
private final BitDecoder bitDecoder = new BitDecoder(new BitRegister[] {
new LSBBitstreamBitRegister(),
new LSBBytesBitRegister()
}, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff);
private final HuffmanDecoder mainDecoder = new HuffmanDecoder(Deflate.HUFFMAN_BITS, Deflate.FIXED_MAIN_TABLE_SIZE);
private final HuffmanDecoder distDecoder = new HuffmanDecoder(Deflate.HUFFMAN_BITS, Deflate.FIXED_DIST_TABLE_SIZE);
private final HuffmanDecoder levelDecoder = new HuffmanDecoder(Deflate.HUFFMAN_BITS, Deflate.LEVEL_TABLE_SIZE);
private final HistoryBuffer historyBuffer;
private int blockRemaining;
private boolean readTables;
private boolean finalBlock;
private int rep0Dist;
private boolean storedMode;
private int storedBlockSize;
private int numDistLevels;
/**
* Construct {@code DeflateDecoder}.
*/
public DeflateDecoder() {
this(Collections.EMPTY_SET);
}
/**
* Construct {@code DeflateDecoder}.
*
* @param modes Decoder modes to use.
*/
public DeflateDecoder(Set modes) {
assert modes != null;
this.modes = new HashSet<>(modes);
this.historyBuffer = new HistoryBuffer(
this.modes.contains(DeflateMode.OPTION_HISTORY64K) ? Deflate.HISTORY_SIZE_64 : Deflate.HISTORY_SIZE_32);
init();
}
private void init() {
this.bitDecoder.reset();
this.historyBuffer.clear();
this.blockRemaining = -2;
this.readTables = true;
this.finalBlock = false;
this.blockRemaining = -2;
this.rep0Dist = -1;
this.storedMode = false;
this.storedBlockSize = 0;
this.numDistLevels = 0;
}
@Override
public synchronized void reset() {
super.reset();
init();
}
@Override
public int decode(ByteBuffer dst, ReadableByteChannel src) throws IOException {
long beginTime = beginProcessing();
int decoded = -1;
int emitted = 0;
try {
if (this.blockRemaining != -1) {
long decodeStart = this.bitDecoder.totalIn();
if (decodeStart == 0L && this.modes.contains(DeflateMode.FORMAT_ZLIB)) {
processZLibHeader(src);
}
emitted += this.historyBuffer.flush(dst);
int decodeRemaining = dst.remaining();
while (decodeRemaining > 0 && this.blockRemaining != -1) {
decodeBlock(src, Math.min(decodeRemaining, this.historyBuffer.getSize() >>> 1));
emitted += this.historyBuffer.flush(dst);
decodeRemaining = dst.remaining();
}
emitted += this.historyBuffer.flush(dst);
if (this.blockRemaining == -1) {
if (this.modes.contains(DeflateMode.FORMAT_ZLIB)) {
processZLibTrailer(src);
}
}
decoded = (int) (this.bitDecoder.totalIn() - decodeStart);
} else if (this.modes.contains(DeflateMode.OPTION_RESTART_AFTER_EOS)) {
this.blockRemaining = -2;
this.bitDecoder.clear();
}
} finally {
endProcessing(beginTime, Math.max(decoded, 0), emitted);
}
return decoded;
}
private void processZLibHeader(ReadableByteChannel src) throws IOException {
this.bitDecoder.decodeBits(src, 8, 1);
this.bitDecoder.decodeBits(src, 4, 1);
this.bitDecoder.decodeBits(src, 4, 1);
}
private void processZLibTrailer(ReadableByteChannel src) throws IOException {
this.bitDecoder.alignToByte();
this.bitDecoder.decodeBits(src, 8, 1);
this.bitDecoder.decodeBits(src, 8, 1);
this.bitDecoder.decodeBits(src, 8, 1);
this.bitDecoder.decodeBits(src, 8, 1);
}
private void decodeBlock(ReadableByteChannel src, int len) throws IOException {
int decodeRemaining = len;
if (this.blockRemaining == -2) {
if (!this.modes.contains(DeflateMode.OPTION_KEEP_HISTORY)) {
this.historyBuffer.clear();
}
this.readTables = true;
this.finalBlock = false;
this.blockRemaining = 0;
} else if (this.blockRemaining > 0) {
final int decodeLen = Math.min(this.blockRemaining, decodeRemaining);
this.historyBuffer.copyBlock(this.rep0Dist, decodeLen);
this.blockRemaining -= decodeLen;
decodeRemaining -= decodeLen;
}
boolean done1 = !(decodeRemaining > 0);
while (!done1) {
if (this.readTables) {
if (!this.finalBlock) {
readTables(src);
this.readTables = false;
} else {
this.blockRemaining = -1;
done1 = true;
}
}
if (!done1) {
if (this.storedMode) {
final int writeBufferLen = Math.min(decodeRemaining, this.storedBlockSize);
this.historyBuffer.putBytes(this.bitDecoder, src, writeBufferLen);
this.storedBlockSize -= writeBufferLen;
this.readTables = this.storedBlockSize == 0;
decodeRemaining -= writeBufferLen;
done1 = !(decodeRemaining > 0);
} else {
boolean done2 = !(decodeRemaining > 0);
while (!done2) {
int symbol = this.mainDecoder.decodeSymbol(src, this.bitDecoder, 0);
if (symbol < 0) {
throw new InvalidDataException(symbol);
} else if (symbol < 0x100) {
this.historyBuffer.putByte((byte) symbol);
decodeRemaining--;
done2 = !(decodeRemaining > 0);
} else if (symbol == Deflate.SYMBOL_END_OF_BLOCK) {
this.readTables = true;
done2 = true;
} else if (symbol < Deflate.MAIN_TABLE_SIZE) {
symbol -= Deflate.SYMBOL_MATCH;
int decodeLen1;
if (this.modes.contains(DeflateMode.OPTION_HISTORY64K)) {
decodeLen1 = (Deflate.LEN_START_64[symbol] & 0xff) + Deflate.MATCH_MIN_LEN
+ this.bitDecoder.decodeBits(src, Deflate.LEN_DIRECT_BITS_64[symbol] & 0xff, 1);
} else {
decodeLen1 = (Deflate.LEN_START_32[symbol] & 0xff) + Deflate.MATCH_MIN_LEN
+ this.bitDecoder.decodeBits(src, Deflate.LEN_DIRECT_BITS_32[symbol] & 0xff, 1);
}
final int decodeLen2 = Math.min(decodeLen1, decodeRemaining);
symbol = this.distDecoder.decodeSymbol(src, this.bitDecoder, 0);
if (symbol >= this.numDistLevels) {
throw new InvalidDataException(symbol);
}
final int dist = Deflate.DIST_START[symbol]
+ this.bitDecoder.decodeBits(src, Deflate.DIST_DIRECT_BITS[symbol], 1);
this.historyBuffer.copyBlock(dist, decodeLen2);
decodeRemaining -= decodeLen2;
decodeLen1 -= decodeLen2;
if (decodeLen1 == 0) {
done2 = !(decodeRemaining > 0);
} else {
this.blockRemaining = decodeLen1;
this.rep0Dist = dist;
done2 = true;
}
} else {
throw new InvalidDataException(symbol);
}
}
done1 = !(decodeRemaining > 0);
}
}
}
}
private void readTables(ReadableByteChannel src) throws IOException {
this.finalBlock = (this.bitDecoder.decodeBits(src, Deflate.FINAL_BLOCK_FIELD_SIZE, 1) != 0);
int blockType = this.bitDecoder.decodeBits(src, Deflate.BLOCK_TYPE_FIELD_SIZE, 1);
DeflateLevels levels;
switch (blockType) {
case Deflate.BLOCK_TYPE_STORED:
this.storedMode = true;
this.bitDecoder.alignToByte();
this.storedBlockSize = this.bitDecoder.decodeBits(src, Deflate.STORED_BLOCK_LENGTH_FIELD_SIZE, 1);
if (!this.modes.contains(DeflateMode.FORMAT_NSIS)) {
int storedBlockSizeCheck = this.bitDecoder.decodeBits(src, Deflate.STORED_BLOCK_LENGTH_FIELD_SIZE, 1);
if (((this.storedBlockSize ^ storedBlockSizeCheck) & 0xffff) != 0xffff) {
throw new InvalidDataException(this.storedBlockSize, storedBlockSizeCheck);
}
}
break;
case Deflate.BLOCK_TYPE_FIXED_HUFFMAN:
this.storedMode = false;
levels = new DeflateLevels();
levels.setFixedLevels();
this.numDistLevels = (this.modes.contains(DeflateMode.OPTION_HISTORY64K) ? Deflate.DIST_TABLE_SIZE_64
: Deflate.DIST_TABLE_SIZE_32);
this.mainDecoder.setCodeLengths(levels.litLenLevels);
this.distDecoder.setCodeLengths(levels.distLevels);
break;
case Deflate.BLOCK_TYPE_DYNAMIC_HUFFMAN:
this.storedMode = false;
final int numLitLenLevels = this.bitDecoder.decodeBits(src, Deflate.NUM_LEN_CODES_FIELD_SIZE, 1)
+ Deflate.NUM_LIT_LEN_CODES_MIN;
this.numDistLevels = this.bitDecoder.decodeBits(src, Deflate.NUM_DIST_CODES_FIELD_SIZE, 1)
+ Deflate.NUM_DIST_CODES_MIN;
if (!this.modes.contains(DeflateMode.OPTION_HISTORY64K)
&& this.numDistLevels > Deflate.DIST_TABLE_SIZE_32) {
throw new InvalidDataException(this.numDistLevels);
}
final int numLevelCodes = this.bitDecoder.decodeBits(src, Deflate.NUM_LEVEL_CODES_FIELD_SIZE, 1)
+ Deflate.NUM_LEVEL_CODES_MIN;
final byte[] levelLevels = new byte[Deflate.LEVEL_TABLE_SIZE];
for (int levelIndex = 0; levelIndex < levelLevels.length; levelIndex++) {
final int position = Deflate.CODE_LENGTH_ALPHABET_ORDER[levelIndex] & 0xff;
if (levelIndex < numLevelCodes) {
levelLevels[position] = (byte) this.bitDecoder.decodeBits(src, Deflate.LEVEL_FIELD_SIZE, 1);
} else {
levelLevels[position] = 0;
}
}
this.levelDecoder.setCodeLengths(levelLevels);
levels = new DeflateLevels();
decodeLevels(src, levels, numLitLenLevels + this.numDistLevels);
levels.subClear();
levels.setLevels(numLitLenLevels, this.numDistLevels);
this.mainDecoder.setCodeLengths(levels.litLenLevels);
this.distDecoder.setCodeLengths(levels.distLevels);
break;
default:
throw new InvalidDataException(blockType);
}
}
private void decodeLevels(ReadableByteChannel src, DeflateLevels levels, int numSymbols) throws IOException {
int levelIndex = 0;
while (levelIndex < numSymbols) {
final int symbol = this.levelDecoder.decodeSymbol(src, this.bitDecoder, 0);
if (symbol < 0 || Deflate.LEVEL_TABLE_SIZE <= symbol) {
throw new InvalidDataException(symbol);
} else if (symbol < Deflate.TABLE_DIRECT_LEVELS) {
levels.levels[levelIndex] = (byte) symbol;
levelIndex++;
} else {
if (symbol == Deflate.TABLE_LEVEL_REP_NUMBER) {
if (levelIndex == 0) {
throw new InvalidDataException();
}
int repNum = this.bitDecoder.decodeBits(src, 2, 1) + 3;
while (repNum > 0 && levelIndex < numSymbols) {
levels.levels[levelIndex] = levels.levels[levelIndex - 1];
levelIndex++;
repNum--;
}
} else {
int repNum = (symbol == Deflate.TABLE_LEVEL0_NUMBER ? this.bitDecoder.decodeBits(src, 3, 1) + 3
: this.bitDecoder.decodeBits(src, 7, 1) + 11);
while (repNum > 0 && levelIndex < numSymbols) {
levels.levels[levelIndex] = 0;
levelIndex++;
repNum--;
}
}
}
}
}
@Override
public String name() {
return NAME;
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append(name()).append(' ').append(this.modes);
return buffer.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy