Improve Bzip2BitReader/Writer
Motivation: Before this changes Bzip2BitReader and Bzip2BitWriter accessed to ByteBuf byte by byte. So tests for Bzip2 compression codec takes a lot of time if we ran them with paranoid level of resource leak detection. For more information see comments to #2681 and #2689. Modifications: - Increased size of bit buffers from 8 to 64 bits. - Improved reading and writing operations. - Save link to incoming ByteBuf inside Bzip2BitReader. - Added methods to check possible readable bits and bytes in Bzip2BitReader. - Updated Bzip2 classes to use new API of Bzip2BitReader. - Added new constants to Bzip2Constants. Result: Increased size of bit buffers and improved performance of Bzip2 compression codec (for general work by 13% and for tests with paranoid level of resource leak detection by 55%).
This commit is contained in:
parent
ff9cc74bf6
commit
c13419750d
@ -19,14 +19,24 @@ import io.netty.buffer.ByteBuf;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An bit reader that allows the reading of single bit booleans, bit strings of
|
* An bit reader that allows the reading of single bit booleans, bit strings of
|
||||||
* arbitrary length (up to 24 bits), and bit aligned 32-bit integers. A single byte
|
* arbitrary length (up to 32 bits), and bit aligned 32-bit integers. A single byte
|
||||||
* at a time is read from the {@link ByteBuf} when more bits are required.
|
* at a time is read from the {@link ByteBuf} when more bits are required.
|
||||||
*/
|
*/
|
||||||
class Bzip2BitReader {
|
class Bzip2BitReader {
|
||||||
|
/**
|
||||||
|
* Maximum count of possible readable bytes to check.
|
||||||
|
*/
|
||||||
|
private static final int MAX_COUNT_OF_READABLE_BYTES = Integer.MAX_VALUE >>> 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ByteBuf} from which to read data.
|
||||||
|
*/
|
||||||
|
private ByteBuf in;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A buffer of bits read from the input stream that have not yet been returned.
|
* A buffer of bits read from the input stream that have not yet been returned.
|
||||||
*/
|
*/
|
||||||
private int bitBuffer;
|
private long bitBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of bits currently buffered in {@link #bitBuffer}.
|
* The number of bits currently buffered in {@link #bitBuffer}.
|
||||||
@ -34,53 +44,114 @@ class Bzip2BitReader {
|
|||||||
private int bitCount;
|
private int bitCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads up to 24 bits from the {@link ByteBuf}.
|
* Set the {@link ByteBuf} from which to read data.
|
||||||
* @param count The number of bits to read (maximum {@code 24}, because the {@link #bitBuffer}
|
*/
|
||||||
* is {@code int} and it can store up to {@code 8} bits before calling)
|
void setByteBuf(ByteBuf in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads up to 32 bits from the {@link ByteBuf}.
|
||||||
|
* @param count The number of bits to read (maximum {@code 32} as a size of {@code int})
|
||||||
* @return The bits requested, right-aligned within the integer
|
* @return The bits requested, right-aligned within the integer
|
||||||
*/
|
*/
|
||||||
int readBits(ByteBuf in, final int count) {
|
int readBits(final int count) {
|
||||||
if (count < 0 || count > 24) {
|
if (count < 0 || count > 32) {
|
||||||
throw new IllegalArgumentException("count: " + count + " (expected: 0-24)");
|
throw new IllegalArgumentException("count: " + count + " (expected: 0-32 )");
|
||||||
}
|
}
|
||||||
int bitCount = this.bitCount;
|
int bitCount = this.bitCount;
|
||||||
int bitBuffer = this.bitBuffer;
|
long bitBuffer = this.bitBuffer;
|
||||||
|
|
||||||
if (bitCount < count) {
|
if (bitCount < count) {
|
||||||
do {
|
long readData;
|
||||||
int uByte = in.readUnsignedByte();
|
int offset;
|
||||||
bitBuffer = bitBuffer << 8 | uByte;
|
switch (in.readableBytes()) {
|
||||||
bitCount += 8;
|
case 1: {
|
||||||
} while (bitCount < count);
|
readData = in.readUnsignedByte();
|
||||||
|
offset = 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
readData = in.readUnsignedShort();
|
||||||
|
offset = 16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
readData = in.readUnsignedMedium();
|
||||||
|
offset = 24;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
readData = in.readUnsignedInt();
|
||||||
|
offset = 32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitBuffer = bitBuffer << offset | readData;
|
||||||
|
bitCount += offset;
|
||||||
this.bitBuffer = bitBuffer;
|
this.bitBuffer = bitBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bitCount = bitCount -= count;
|
this.bitCount = bitCount -= count;
|
||||||
return (bitBuffer >>> bitCount) & ((1 << count) - 1);
|
return (int) (bitBuffer >>> bitCount & (count != 32 ? (1 << count) - 1 : 0xFFFFFFFFL));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a single bit from the {@link ByteBuf}.
|
* Reads a single bit from the {@link ByteBuf}.
|
||||||
* @return {@code true} if the bit read was {@code 1}, otherwise {@code false}
|
* @return {@code true} if the bit read was {@code 1}, otherwise {@code false}
|
||||||
*/
|
*/
|
||||||
boolean readBoolean(ByteBuf in) {
|
boolean readBoolean() {
|
||||||
return readBits(in, 1) != 0;
|
return readBits(1) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads 32 bits of input as an integer.
|
* Reads 32 bits of input as an integer.
|
||||||
* @return The integer read
|
* @return The integer read
|
||||||
*/
|
*/
|
||||||
int readInt(ByteBuf in) {
|
int readInt() {
|
||||||
return readBits(in, 16) << 16 | readBits(in, 16);
|
return readBits(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refill the {@link ByteBuf} by one byte.
|
||||||
|
*/
|
||||||
|
void refill() {
|
||||||
|
int readData = in.readUnsignedByte();
|
||||||
|
bitBuffer = bitBuffer << 8 | readData;
|
||||||
|
bitCount += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that at least one bit is available for reading.
|
* Checks that at least one bit is available for reading.
|
||||||
* @return {@code true} if one bit is available for reading, otherwise {@code false}
|
* @return {@code true} if one bit is available for reading, otherwise {@code false}
|
||||||
*/
|
*/
|
||||||
boolean hasBit(ByteBuf in) {
|
boolean isReadable() {
|
||||||
return bitCount > 0 || in.isReadable();
|
return bitCount > 0 || in.isReadable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the specified number of bits available for reading.
|
||||||
|
* @param count The number of bits to check
|
||||||
|
* @return {@code true} if {@code count} bits are available for reading, otherwise {@code false}
|
||||||
|
*/
|
||||||
|
boolean hasReadableBits(int count) {
|
||||||
|
if (count < 0) {
|
||||||
|
throw new IllegalArgumentException("count: " + count + " (expected value greater than 0)");
|
||||||
|
}
|
||||||
|
return bitCount >= count || (in.readableBytes() << 3 & Integer.MAX_VALUE) >= count - bitCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the specified number of bytes available for reading.
|
||||||
|
* @param count The number of bytes to check
|
||||||
|
* @return {@code true} if {@code count} bytes are available for reading, otherwise {@code false}
|
||||||
|
*/
|
||||||
|
boolean hasReadableBytes(int count) {
|
||||||
|
if (count < 0 || count > MAX_COUNT_OF_READABLE_BYTES) {
|
||||||
|
throw new IllegalArgumentException("count: " + count
|
||||||
|
+ " (expected: 0-" + MAX_COUNT_OF_READABLE_BYTES + ')');
|
||||||
|
}
|
||||||
|
return hasReadableBits(count << 3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,14 @@ import io.netty.buffer.ByteBuf;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A bit writer that allows the writing of single bit booleans, unary numbers, bit strings
|
* A bit writer that allows the writing of single bit booleans, unary numbers, bit strings
|
||||||
* of arbitrary length (up to 24 bits), and bit aligned 32-bit integers. A single byte at a
|
* of arbitrary length (up to 32 bits), and bit aligned 32-bit integers. A single byte at a
|
||||||
* time is written to the {@link ByteBuf} when sufficient bits have been accumulated.
|
* time is written to the {@link ByteBuf} when sufficient bits have been accumulated.
|
||||||
*/
|
*/
|
||||||
final class Bzip2BitWriter {
|
final class Bzip2BitWriter {
|
||||||
/**
|
/**
|
||||||
* A buffer of bits waiting to be written to the output stream.
|
* A buffer of bits waiting to be written to the output stream.
|
||||||
*/
|
*/
|
||||||
private int bitBuffer;
|
private long bitBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of bits currently buffered in {@link #bitBuffer}.
|
* The number of bits currently buffered in {@link #bitBuffer}.
|
||||||
@ -34,23 +34,22 @@ final class Bzip2BitWriter {
|
|||||||
private int bitCount;
|
private int bitCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes up to 24 bits to the output {@link ByteBuf}.
|
* Writes up to 32 bits to the output {@link ByteBuf}.
|
||||||
* @param count The number of bits to write (maximum {@code 24}, because the {@link #bitBuffer}
|
* @param count The number of bits to write (maximum {@code 32} as a size of {@code int})
|
||||||
* is {@code int} and it can store up to {@code 8} bits before calling)
|
|
||||||
* @param value The bits to write
|
* @param value The bits to write
|
||||||
*/
|
*/
|
||||||
void writeBits(ByteBuf out, final int count, final int value) {
|
void writeBits(ByteBuf out, final int count, final long value) {
|
||||||
if (count < 0 || count > 24) {
|
if (count < 0 || count > 32) {
|
||||||
throw new IllegalArgumentException("count: " + count + " (expected: 0-24)");
|
throw new IllegalArgumentException("count: " + count + " (expected: 0-32)");
|
||||||
}
|
}
|
||||||
int bitCount = this.bitCount;
|
int bitCount = this.bitCount;
|
||||||
int bitBuffer = this.bitBuffer | (value << (32 - count)) >>> bitCount;
|
long bitBuffer = this.bitBuffer | value << 64 - count >>> bitCount;
|
||||||
bitCount += count;
|
bitCount += count;
|
||||||
|
|
||||||
while (bitCount >= 8) {
|
if (bitCount >= 32) {
|
||||||
out.writeByte(bitBuffer >>> 24);
|
out.writeInt((int) (bitBuffer >>> 32));
|
||||||
bitBuffer <<= 8;
|
bitBuffer <<= 32;
|
||||||
bitCount -= 8;
|
bitCount -= 32;
|
||||||
}
|
}
|
||||||
this.bitBuffer = bitBuffer;
|
this.bitBuffer = bitBuffer;
|
||||||
this.bitCount = bitCount;
|
this.bitCount = bitCount;
|
||||||
@ -62,10 +61,10 @@ final class Bzip2BitWriter {
|
|||||||
*/
|
*/
|
||||||
void writeBoolean(ByteBuf out, final boolean value) {
|
void writeBoolean(ByteBuf out, final boolean value) {
|
||||||
int bitCount = this.bitCount + 1;
|
int bitCount = this.bitCount + 1;
|
||||||
int bitBuffer = this.bitBuffer | (value ? 1 : 0) << (32 - bitCount);
|
long bitBuffer = this.bitBuffer | (value ? 1L << 64 - bitCount : 0L);
|
||||||
|
|
||||||
if (bitCount == 8) {
|
if (bitCount == 32) {
|
||||||
out.writeByte(bitBuffer >>> 24);
|
out.writeInt((int) (bitBuffer >>> 32));
|
||||||
bitBuffer = 0;
|
bitBuffer = 0;
|
||||||
bitCount = 0;
|
bitCount = 0;
|
||||||
}
|
}
|
||||||
@ -93,8 +92,7 @@ final class Bzip2BitWriter {
|
|||||||
* @param value The integer to write
|
* @param value The integer to write
|
||||||
*/
|
*/
|
||||||
void writeInt(ByteBuf out, final int value) {
|
void writeInt(ByteBuf out, final int value) {
|
||||||
writeBits(out, 16, (value >>> 16) & 0xffff);
|
writeBits(out, 32, value);
|
||||||
writeBits(out, 16, value & 0xffff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,8 +100,21 @@ final class Bzip2BitWriter {
|
|||||||
* zero padding to a whole byte as required.
|
* zero padding to a whole byte as required.
|
||||||
*/
|
*/
|
||||||
void flush(ByteBuf out) {
|
void flush(ByteBuf out) {
|
||||||
|
final int bitCount = this.bitCount;
|
||||||
|
|
||||||
if (bitCount > 0) {
|
if (bitCount > 0) {
|
||||||
writeBits(out, 8 - bitCount, 0);
|
final long bitBuffer = this.bitBuffer;
|
||||||
|
final int shiftToRight = 64 - bitCount;
|
||||||
|
|
||||||
|
if (bitCount <= 8) {
|
||||||
|
out.writeByte((int) (bitBuffer >>> shiftToRight << 8 - bitCount));
|
||||||
|
} else if (bitCount <= 16) {
|
||||||
|
out.writeShort((int) (bitBuffer >>> shiftToRight << 16 - bitCount));
|
||||||
|
} else if (bitCount <= 24) {
|
||||||
|
out.writeMedium((int) (bitBuffer >>> shiftToRight << 24 - bitCount));
|
||||||
|
} else {
|
||||||
|
out.writeInt((int) (bitBuffer >>> shiftToRight << 32 - bitCount));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ final class Bzip2BlockCompressor {
|
|||||||
final boolean[] condensedInUse = new boolean[16];
|
final boolean[] condensedInUse = new boolean[16];
|
||||||
|
|
||||||
for (int i = 0; i < condensedInUse.length; i++) {
|
for (int i = 0; i < condensedInUse.length; i++) {
|
||||||
for (int j = 0, k = i << 4; j < 16; j++, k++) {
|
for (int j = 0, k = i << 4; j < HUFFMAN_SYMBOL_RANGE_SIZE; j++, k++) {
|
||||||
if (blockValuesPresent[k]) {
|
if (blockValuesPresent[k]) {
|
||||||
condensedInUse[i] = true;
|
condensedInUse[i] = true;
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ final class Bzip2BlockCompressor {
|
|||||||
|
|
||||||
for (int i = 0; i < condensedInUse.length; i++) {
|
for (int i = 0; i < condensedInUse.length; i++) {
|
||||||
if (condensedInUse[i]) {
|
if (condensedInUse[i]) {
|
||||||
for (int j = 0, k = i << 4; j < 16; j++, k++) {
|
for (int j = 0, k = i << 4; j < HUFFMAN_SYMBOL_RANGE_SIZE; j++, k++) {
|
||||||
writer.writeBoolean(out, blockValuesPresent[k]);
|
writer.writeBoolean(out, blockValuesPresent[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.compression;
|
package io.netty.handler.codec.compression;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.compression.Bzip2Constants.*;
|
import static io.netty.handler.codec.compression.Bzip2Constants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,14 +23,19 @@ import static io.netty.handler.codec.compression.Bzip2Constants.*;
|
|||||||
* Block decoding consists of the following stages:<br>
|
* Block decoding consists of the following stages:<br>
|
||||||
* 1. Read block header<br>
|
* 1. Read block header<br>
|
||||||
* 2. Read Huffman tables<br>
|
* 2. Read Huffman tables<br>
|
||||||
* 3. Read and decode Huffman encoded data - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder, ByteBuf)}<br>
|
* 3. Read and decode Huffman encoded data - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder)}<br>
|
||||||
* 4. Run-Length Decoding[2] - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder, ByteBuf)}<br>
|
* 4. Run-Length Decoding[2] - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder)}<br>
|
||||||
* 5. Inverse Move To Front Transform - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder, ByteBuf)}<br>
|
* 5. Inverse Move To Front Transform - {@link #decodeHuffmanData(Bzip2HuffmanStageDecoder)}<br>
|
||||||
* 6. Inverse Burrows Wheeler Transform - {@link #initialiseInverseBWT()}<br>
|
* 6. Inverse Burrows Wheeler Transform - {@link #initialiseInverseBWT()}<br>
|
||||||
* 7. Run-Length Decoding[1] - {@link #read()}<br>
|
* 7. Run-Length Decoding[1] - {@link #read()}<br>
|
||||||
* 8. Optional Block De-Randomisation - {@link #read()} (through {@link #decodeNextBWTByte()})
|
* 8. Optional Block De-Randomisation - {@link #read()} (through {@link #decodeNextBWTByte()})
|
||||||
*/
|
*/
|
||||||
final class Bzip2BlockDecompressor {
|
final class Bzip2BlockDecompressor {
|
||||||
|
/**
|
||||||
|
* A reader that provides bit-level reads.
|
||||||
|
*/
|
||||||
|
private final Bzip2BitReader reader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the block CRC from the fully decoded bytes of the block.
|
* Calculates the block CRC from the fully decoded bytes of the block.
|
||||||
*/
|
*/
|
||||||
@ -142,25 +145,31 @@ final class Bzip2BlockDecompressor {
|
|||||||
/**
|
/**
|
||||||
* Table for Move To Front transformations.
|
* Table for Move To Front transformations.
|
||||||
*/
|
*/
|
||||||
final Bzip2MoveToFrontTable symbolMTF = new Bzip2MoveToFrontTable();
|
private final Bzip2MoveToFrontTable symbolMTF = new Bzip2MoveToFrontTable();
|
||||||
|
|
||||||
int repeatCount;
|
// This variables is used to save current state if we haven't got enough readable bits
|
||||||
int repeatIncrement = 1;
|
private int repeatCount;
|
||||||
int mtfValue;
|
private int repeatIncrement = 1;
|
||||||
|
private int mtfValue;
|
||||||
|
|
||||||
|
Bzip2BlockDecompressor(final int blockSize, final int blockCRC, final boolean blockRandomised,
|
||||||
|
final int bwtStartPointer, final Bzip2BitReader reader) {
|
||||||
|
|
||||||
Bzip2BlockDecompressor(int blockSize, int blockCRC, boolean blockRandomised, int bwtStartPointer) {
|
|
||||||
bwtBlock = new byte[blockSize];
|
bwtBlock = new byte[blockSize];
|
||||||
|
|
||||||
this.blockCRC = blockCRC;
|
this.blockCRC = blockCRC;
|
||||||
this.blockRandomised = blockRandomised;
|
this.blockRandomised = blockRandomised;
|
||||||
this.bwtStartPointer = bwtStartPointer;
|
this.bwtStartPointer = bwtStartPointer;
|
||||||
|
|
||||||
|
this.reader = reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the Huffman encoded data from the input stream, performs Run-Length Decoding and
|
* Reads the Huffman encoded data from the input stream, performs Run-Length Decoding and
|
||||||
* applies the Move To Front transform to reconstruct the Burrows-Wheeler Transform array.
|
* applies the Move To Front transform to reconstruct the Burrows-Wheeler Transform array.
|
||||||
*/
|
*/
|
||||||
boolean decodeHuffmanData(final Bzip2HuffmanStageDecoder huffmanDecoder, ByteBuf in) {
|
boolean decodeHuffmanData(final Bzip2HuffmanStageDecoder huffmanDecoder) {
|
||||||
|
final Bzip2BitReader reader = this.reader;
|
||||||
final byte[] bwtBlock = this.bwtBlock;
|
final byte[] bwtBlock = this.bwtBlock;
|
||||||
final byte[] huffmanSymbolMap = this.huffmanSymbolMap;
|
final byte[] huffmanSymbolMap = this.huffmanSymbolMap;
|
||||||
final int streamBlockSize = this.bwtBlock.length;
|
final int streamBlockSize = this.bwtBlock.length;
|
||||||
@ -174,14 +183,14 @@ final class Bzip2BlockDecompressor {
|
|||||||
int mtfValue = this.mtfValue;
|
int mtfValue = this.mtfValue;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (in.readableBytes() < 3) { // 3 = (HUFFMAN_DECODE_MAX_CODE_LENGTH + 1) bits / 8
|
if (!reader.hasReadableBits(HUFFMAN_DECODE_MAX_CODE_LENGTH)) {
|
||||||
this.bwtBlockLength = bwtBlockLength;
|
this.bwtBlockLength = bwtBlockLength;
|
||||||
this.repeatCount = repeatCount;
|
this.repeatCount = repeatCount;
|
||||||
this.repeatIncrement = repeatIncrement;
|
this.repeatIncrement = repeatIncrement;
|
||||||
this.mtfValue = mtfValue;
|
this.mtfValue = mtfValue;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final int nextSymbol = huffmanDecoder.nextSymbol(in);
|
final int nextSymbol = huffmanDecoder.nextSymbol();
|
||||||
|
|
||||||
if (nextSymbol == HUFFMAN_SYMBOL_RUNA) {
|
if (nextSymbol == HUFFMAN_SYMBOL_RUNA) {
|
||||||
repeatCount += repeatIncrement;
|
repeatCount += repeatIncrement;
|
||||||
|
@ -70,6 +70,16 @@ final class Bzip2Constants {
|
|||||||
static final int HUFFMAN_SYMBOL_RUNA = 0;
|
static final int HUFFMAN_SYMBOL_RUNA = 0;
|
||||||
static final int HUFFMAN_SYMBOL_RUNB = 1;
|
static final int HUFFMAN_SYMBOL_RUNB = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Huffman symbols range size for Huffman used map.
|
||||||
|
*/
|
||||||
|
static final int HUFFMAN_SYMBOL_RANGE_SIZE = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum length of zero-terminated bit runs of MTF'ed Huffman table.
|
||||||
|
*/
|
||||||
|
static final int HUFFMAN_SELECTOR_LIST_MAX_LENGTH = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of symbols decoded after which a new Huffman table is selected.
|
* Number of symbols decoded after which a new Huffman table is selected.
|
||||||
*/
|
*/
|
||||||
|
@ -42,7 +42,6 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
RECEIVE_SELECTORS,
|
RECEIVE_SELECTORS,
|
||||||
RECEIVE_HUFFMAN_LENGTH,
|
RECEIVE_HUFFMAN_LENGTH,
|
||||||
DECODE_HUFFMAN_DATA,
|
DECODE_HUFFMAN_DATA,
|
||||||
END_BLOCK,
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
private State currentState = State.INIT;
|
private State currentState = State.INIT;
|
||||||
@ -82,6 +81,8 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
if (!in.isReadable()) {
|
if (!in.isReadable()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final Bzip2BitReader reader = this.reader;
|
||||||
|
reader.setByteBuf(in);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
switch (currentState) {
|
switch (currentState) {
|
||||||
@ -103,16 +104,15 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
streamCRC = 0;
|
streamCRC = 0;
|
||||||
currentState = State.INIT_BLOCK;
|
currentState = State.INIT_BLOCK;
|
||||||
case INIT_BLOCK:
|
case INIT_BLOCK:
|
||||||
if (in.readableBytes() < 10) {
|
if (!reader.hasReadableBytes(10)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Bzip2BitReader reader = this.reader;
|
|
||||||
// Get the block magic bytes.
|
// Get the block magic bytes.
|
||||||
final int magic1 = reader.readBits(in, 24);
|
final int magic1 = reader.readBits(24);
|
||||||
final int magic2 = reader.readBits(in, 24);
|
final int magic2 = reader.readBits(24);
|
||||||
if (magic1 == END_OF_STREAM_MAGIC_1 && magic2 == END_OF_STREAM_MAGIC_2) {
|
if (magic1 == END_OF_STREAM_MAGIC_1 && magic2 == END_OF_STREAM_MAGIC_2) {
|
||||||
// End of stream was reached. Check the combined CRC.
|
// End of stream was reached. Check the combined CRC.
|
||||||
final int storedCombinedCRC = reader.readInt(in);
|
final int storedCombinedCRC = reader.readInt();
|
||||||
if (storedCombinedCRC != streamCRC) {
|
if (storedCombinedCRC != streamCRC) {
|
||||||
throw new DecompressionException("stream CRC error");
|
throw new DecompressionException("stream CRC error");
|
||||||
}
|
}
|
||||||
@ -122,25 +122,23 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
if (magic1 != BLOCK_HEADER_MAGIC_1 || magic2 != BLOCK_HEADER_MAGIC_2) {
|
if (magic1 != BLOCK_HEADER_MAGIC_1 || magic2 != BLOCK_HEADER_MAGIC_2) {
|
||||||
throw new DecompressionException("bad block header");
|
throw new DecompressionException("bad block header");
|
||||||
}
|
}
|
||||||
blockCRC = reader.readInt(in);
|
blockCRC = reader.readInt();
|
||||||
currentState = State.INIT_BLOCK_PARAMS;
|
currentState = State.INIT_BLOCK_PARAMS;
|
||||||
case INIT_BLOCK_PARAMS:
|
case INIT_BLOCK_PARAMS:
|
||||||
if (in.readableBytes() < 4) {
|
if (!reader.hasReadableBits(25)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reader = this.reader;
|
final boolean blockRandomised = reader.readBoolean();
|
||||||
final boolean blockRandomised = reader.readBoolean(in);
|
final int bwtStartPointer = reader.readBits(24);
|
||||||
final int bwtStartPointer = reader.readBits(in, 24);
|
|
||||||
|
|
||||||
blockDecompressor = new Bzip2BlockDecompressor(this.blockSize, blockCRC,
|
blockDecompressor = new Bzip2BlockDecompressor(this.blockSize, blockCRC,
|
||||||
blockRandomised, bwtStartPointer);
|
blockRandomised, bwtStartPointer, reader);
|
||||||
currentState = State.RECEIVE_HUFFMAN_USED_MAP;
|
currentState = State.RECEIVE_HUFFMAN_USED_MAP;
|
||||||
case RECEIVE_HUFFMAN_USED_MAP:
|
case RECEIVE_HUFFMAN_USED_MAP:
|
||||||
if (in.readableBytes() < 2) {
|
if (!reader.hasReadableBits(16)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reader = this.reader;
|
blockDecompressor.huffmanInUse16 = reader.readBits(16);
|
||||||
blockDecompressor.huffmanInUse16 = reader.readBits(in, 16);
|
|
||||||
currentState = State.RECEIVE_HUFFMAN_USED_BITMAPS;
|
currentState = State.RECEIVE_HUFFMAN_USED_BITMAPS;
|
||||||
case RECEIVE_HUFFMAN_USED_BITMAPS:
|
case RECEIVE_HUFFMAN_USED_BITMAPS:
|
||||||
Bzip2BlockDecompressor blockDecompressor = this.blockDecompressor;
|
Bzip2BlockDecompressor blockDecompressor = this.blockDecompressor;
|
||||||
@ -148,17 +146,16 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
final int bitNumber = Integer.bitCount(inUse16);
|
final int bitNumber = Integer.bitCount(inUse16);
|
||||||
final byte[] huffmanSymbolMap = blockDecompressor.huffmanSymbolMap;
|
final byte[] huffmanSymbolMap = blockDecompressor.huffmanSymbolMap;
|
||||||
|
|
||||||
if (in.readableBytes() < bitNumber * 16 / 8 + 1) {
|
if (!reader.hasReadableBits(bitNumber * HUFFMAN_SYMBOL_RANGE_SIZE + 3)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reader = this.reader;
|
|
||||||
|
|
||||||
int huffmanSymbolCount = 0;
|
int huffmanSymbolCount = 0;
|
||||||
if (bitNumber > 0) {
|
if (bitNumber > 0) {
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
if ((inUse16 & 1 << 15 >>> i) != 0) {
|
if ((inUse16 & 1 << 15 >>> i) != 0) {
|
||||||
for (int j = 0, k = i << 4; j < 16; j++, k++) {
|
for (int j = 0, k = i << 4; j < HUFFMAN_SYMBOL_RANGE_SIZE; j++, k++) {
|
||||||
if (reader.readBoolean(in)) {
|
if (reader.readBoolean()) {
|
||||||
huffmanSymbolMap[huffmanSymbolCount++] = (byte) k;
|
huffmanSymbolMap[huffmanSymbolCount++] = (byte) k;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +164,7 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
}
|
}
|
||||||
blockDecompressor.huffmanEndOfBlockSymbol = huffmanSymbolCount + 1;
|
blockDecompressor.huffmanEndOfBlockSymbol = huffmanSymbolCount + 1;
|
||||||
|
|
||||||
int totalTables = reader.readBits(in, 3);
|
int totalTables = reader.readBits(3);
|
||||||
if (totalTables < HUFFMAN_MINIMUM_TABLES || totalTables > HUFFMAN_MAXIMUM_TABLES) {
|
if (totalTables < HUFFMAN_MINIMUM_TABLES || totalTables > HUFFMAN_MAXIMUM_TABLES) {
|
||||||
throw new DecompressionException("incorrect huffman groups number");
|
throw new DecompressionException("incorrect huffman groups number");
|
||||||
}
|
}
|
||||||
@ -178,11 +175,10 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
huffmanStageDecoder = new Bzip2HuffmanStageDecoder(reader, totalTables, alphaSize);
|
huffmanStageDecoder = new Bzip2HuffmanStageDecoder(reader, totalTables, alphaSize);
|
||||||
currentState = State.RECEIVE_SELECTORS_NUMBER;
|
currentState = State.RECEIVE_SELECTORS_NUMBER;
|
||||||
case RECEIVE_SELECTORS_NUMBER:
|
case RECEIVE_SELECTORS_NUMBER:
|
||||||
if (in.readableBytes() < 2) {
|
if (!reader.hasReadableBits(15)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reader = this.reader;
|
int totalSelectors = reader.readBits(15);
|
||||||
int totalSelectors = reader.readBits(in, 15);
|
|
||||||
if (totalSelectors < 1 || totalSelectors > MAX_SELECTORS) {
|
if (totalSelectors < 1 || totalSelectors > MAX_SELECTORS) {
|
||||||
throw new DecompressionException("incorrect selectors number");
|
throw new DecompressionException("incorrect selectors number");
|
||||||
}
|
}
|
||||||
@ -194,19 +190,18 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
byte[] selectors = huffmanStageDecoder.selectors;
|
byte[] selectors = huffmanStageDecoder.selectors;
|
||||||
totalSelectors = selectors.length;
|
totalSelectors = selectors.length;
|
||||||
final Bzip2MoveToFrontTable tableMtf = huffmanStageDecoder.tableMTF;
|
final Bzip2MoveToFrontTable tableMtf = huffmanStageDecoder.tableMTF;
|
||||||
reader = this.reader;
|
|
||||||
|
|
||||||
int currSelector;
|
int currSelector;
|
||||||
// Get zero-terminated bit runs (0..62) of MTF'ed Huffman table. length = 1..6
|
// Get zero-terminated bit runs (0..62) of MTF'ed Huffman table. length = 1..6
|
||||||
for (currSelector = huffmanStageDecoder.currentSelector;
|
for (currSelector = huffmanStageDecoder.currentSelector;
|
||||||
currSelector < totalSelectors; currSelector++) {
|
currSelector < totalSelectors; currSelector++) {
|
||||||
if (!in.isReadable()) {
|
if (!reader.hasReadableBits(HUFFMAN_SELECTOR_LIST_MAX_LENGTH)) {
|
||||||
// Save state if end of current ByteBuf was reached
|
// Save state if end of current ByteBuf was reached
|
||||||
huffmanStageDecoder.currentSelector = currSelector;
|
huffmanStageDecoder.currentSelector = currSelector;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int index = 0;
|
int index = 0;
|
||||||
while (reader.readBoolean(in)) {
|
while (reader.readBoolean()) {
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
selectors[currSelector] = tableMtf.indexToFront(index);
|
selectors[currSelector] = tableMtf.indexToFront(index);
|
||||||
@ -218,7 +213,6 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
totalTables = huffmanStageDecoder.totalTables;
|
totalTables = huffmanStageDecoder.totalTables;
|
||||||
final byte[][] codeLength = huffmanStageDecoder.tableCodeLengths;
|
final byte[][] codeLength = huffmanStageDecoder.tableCodeLengths;
|
||||||
alphaSize = huffmanStageDecoder.alphabetSize;
|
alphaSize = huffmanStageDecoder.alphabetSize;
|
||||||
reader = this.reader;
|
|
||||||
|
|
||||||
/* Now the coding tables */
|
/* Now the coding tables */
|
||||||
int currGroup;
|
int currGroup;
|
||||||
@ -228,29 +222,29 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
boolean saveStateAndReturn = false;
|
boolean saveStateAndReturn = false;
|
||||||
loop: for (currGroup = huffmanStageDecoder.currentGroup; currGroup < totalTables; currGroup++) {
|
loop: for (currGroup = huffmanStageDecoder.currentGroup; currGroup < totalTables; currGroup++) {
|
||||||
// start_huffman_length
|
// start_huffman_length
|
||||||
if (!in.isReadable()) {
|
if (!reader.hasReadableBits(5)) {
|
||||||
saveStateAndReturn = true;
|
saveStateAndReturn = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (currLength < 0) {
|
if (currLength < 0) {
|
||||||
currLength = reader.readBits(in, 5);
|
currLength = reader.readBits(5);
|
||||||
}
|
}
|
||||||
for (currAlpha = huffmanStageDecoder.currentAlpha; currAlpha < alphaSize; currAlpha++) {
|
for (currAlpha = huffmanStageDecoder.currentAlpha; currAlpha < alphaSize; currAlpha++) {
|
||||||
// delta_bit_length: 1..40
|
// delta_bit_length: 1..40
|
||||||
if (!reader.hasBit(in)) {
|
if (!reader.isReadable()) {
|
||||||
saveStateAndReturn = true;
|
saveStateAndReturn = true;
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
while (modifyLength || reader.readBoolean(in)) { // 0=>next symbol; 1=>alter length
|
while (modifyLength || reader.readBoolean()) { // 0=>next symbol; 1=>alter length
|
||||||
if (!reader.hasBit(in)) {
|
if (!reader.isReadable()) {
|
||||||
modifyLength = true;
|
modifyLength = true;
|
||||||
saveStateAndReturn = true;
|
saveStateAndReturn = true;
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
// 1=>decrement length; 0=>increment length
|
// 1=>decrement length; 0=>increment length
|
||||||
currLength += reader.readBoolean(in) ? -1 : 1;
|
currLength += reader.readBoolean() ? -1 : 1;
|
||||||
modifyLength = false;
|
modifyLength = false;
|
||||||
if (!reader.hasBit(in)) {
|
if (!reader.isReadable()) {
|
||||||
saveStateAndReturn = true;
|
saveStateAndReturn = true;
|
||||||
break loop;
|
break loop;
|
||||||
}
|
}
|
||||||
@ -275,10 +269,17 @@ public class Bzip2Decoder extends ByteToMessageDecoder {
|
|||||||
currentState = State.DECODE_HUFFMAN_DATA;
|
currentState = State.DECODE_HUFFMAN_DATA;
|
||||||
case DECODE_HUFFMAN_DATA:
|
case DECODE_HUFFMAN_DATA:
|
||||||
blockDecompressor = this.blockDecompressor;
|
blockDecompressor = this.blockDecompressor;
|
||||||
final boolean decoded = blockDecompressor.decodeHuffmanData(this.huffmanStageDecoder, in);
|
final int oldReaderIndex = in.readerIndex();
|
||||||
|
final boolean decoded = blockDecompressor.decodeHuffmanData(this.huffmanStageDecoder);
|
||||||
if (!decoded) {
|
if (!decoded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// It used to avoid "Bzip2Decoder.decode() did not read anything but decoded a message" exception.
|
||||||
|
// Because previous operation may read only a few bits from Bzip2BitReader.bitBuffer and
|
||||||
|
// don't read incomming ByteBuf.
|
||||||
|
if (in.readerIndex() == oldReaderIndex && in.isReadable()) {
|
||||||
|
reader.refill();
|
||||||
|
}
|
||||||
|
|
||||||
final int blockLength = blockDecompressor.blockLength();
|
final int blockLength = blockDecompressor.blockLength();
|
||||||
final ByteBuf uncompressed = ctx.alloc().buffer(blockLength);
|
final ByteBuf uncompressed = ctx.alloc().buffer(blockLength);
|
||||||
|
@ -42,8 +42,7 @@ public class Bzip2Encoder extends MessageToByteEncoder<ByteBuf> {
|
|||||||
INIT,
|
INIT,
|
||||||
INIT_BLOCK,
|
INIT_BLOCK,
|
||||||
WRITE_DATA,
|
WRITE_DATA,
|
||||||
CLOSE_BLOCK,
|
CLOSE_BLOCK
|
||||||
EOF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private State currentState = State.INIT;
|
private State currentState = State.INIT;
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.netty.handler.codec.compression;
|
package io.netty.handler.codec.compression;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.compression.Bzip2Constants.*;
|
import static io.netty.handler.codec.compression.Bzip2Constants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,7 +168,7 @@ final class Bzip2HuffmanStageDecoder {
|
|||||||
* Decodes and returns the next symbol.
|
* Decodes and returns the next symbol.
|
||||||
* @return The decoded symbol
|
* @return The decoded symbol
|
||||||
*/
|
*/
|
||||||
int nextSymbol(ByteBuf in) {
|
int nextSymbol() {
|
||||||
// Move to next group selector if required
|
// Move to next group selector if required
|
||||||
if (++groupPosition % HUFFMAN_GROUP_RUN_LENGTH == 0) {
|
if (++groupPosition % HUFFMAN_GROUP_RUN_LENGTH == 0) {
|
||||||
groupIndex++;
|
groupIndex++;
|
||||||
@ -189,13 +187,13 @@ final class Bzip2HuffmanStageDecoder {
|
|||||||
|
|
||||||
// Starting with the minimum bit length for the table, read additional bits one at a time
|
// Starting with the minimum bit length for the table, read additional bits one at a time
|
||||||
// until a complete code is recognised
|
// until a complete code is recognised
|
||||||
int codeBits = reader.readBits(in, codeLength);
|
int codeBits = reader.readBits(codeLength);
|
||||||
for (; codeLength <= HUFFMAN_DECODE_MAX_CODE_LENGTH; codeLength++) {
|
for (; codeLength <= HUFFMAN_DECODE_MAX_CODE_LENGTH; codeLength++) {
|
||||||
if (codeBits <= tableLimits[codeLength]) {
|
if (codeBits <= tableLimits[codeLength]) {
|
||||||
// Convert the code to a symbol index and return
|
// Convert the code to a symbol index and return
|
||||||
return tableSymbols[codeBits - tableBases[codeLength]];
|
return tableSymbols[codeBits - tableBases[codeLength]];
|
||||||
}
|
}
|
||||||
codeBits = codeBits << 1 | reader.readBits(in, 1);
|
codeBits = codeBits << 1 | reader.readBits(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new DecompressionException("a valid code was not recognised");
|
throw new DecompressionException("a valid code was not recognised");
|
||||||
|
Loading…
Reference in New Issue
Block a user