aboutsummaryrefslogtreecommitdiff
path: root/java/org/brotli/dec/Decode.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/org/brotli/dec/Decode.java')
-rwxr-xr-xjava/org/brotli/dec/Decode.java839
1 files changed, 839 insertions, 0 deletions
diff --git a/java/org/brotli/dec/Decode.java b/java/org/brotli/dec/Decode.java
new file mode 100755
index 0000000..d7d3bf0
--- /dev/null
+++ b/java/org/brotli/dec/Decode.java
@@ -0,0 +1,839 @@
+/* Copyright 2015 Google Inc. All Rights Reserved.
+
+ Distributed under MIT license.
+ See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
+*/
+
+package org.brotli.dec;
+
+import static org.brotli.dec.RunningState.BLOCK_START;
+import static org.brotli.dec.RunningState.CLOSED;
+import static org.brotli.dec.RunningState.COMPRESSED_BLOCK_START;
+import static org.brotli.dec.RunningState.COPY_LOOP;
+import static org.brotli.dec.RunningState.COPY_UNCOMPRESSED;
+import static org.brotli.dec.RunningState.COPY_WRAP_BUFFER;
+import static org.brotli.dec.RunningState.FINISHED;
+import static org.brotli.dec.RunningState.INSERT_LOOP;
+import static org.brotli.dec.RunningState.MAIN_LOOP;
+import static org.brotli.dec.RunningState.READ_METADATA;
+import static org.brotli.dec.RunningState.TRANSFORM;
+import static org.brotli.dec.RunningState.UNINITIALIZED;
+import static org.brotli.dec.RunningState.WRITE;
+
+/**
+ * API for Brotli decompression.
+ */
+final class Decode {
+
+ private static final int DEFAULT_CODE_LENGTH = 8;
+ private static final int CODE_LENGTH_REPEAT_CODE = 16;
+ private static final int NUM_LITERAL_CODES = 256;
+ private static final int NUM_INSERT_AND_COPY_CODES = 704;
+ private static final int NUM_BLOCK_LENGTH_CODES = 26;
+ private static final int LITERAL_CONTEXT_BITS = 6;
+ private static final int DISTANCE_CONTEXT_BITS = 2;
+
+ private static final int HUFFMAN_TABLE_BITS = 8;
+ private static final int HUFFMAN_TABLE_MASK = 0xFF;
+
+ private static final int CODE_LENGTH_CODES = 18;
+ private static final int[] CODE_LENGTH_CODE_ORDER = {
+ 1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ };
+
+ private static final int NUM_DISTANCE_SHORT_CODES = 16;
+ private static final int[] DISTANCE_SHORT_CODE_INDEX_OFFSET = {
+ 3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2
+ };
+
+ private static final int[] DISTANCE_SHORT_CODE_VALUE_OFFSET = {
+ 0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3
+ };
+
+ /**
+ * Static Huffman code for the code length code lengths.
+ */
+ private static final int[] FIXED_TABLE = {
+ 0x020000, 0x020004, 0x020003, 0x030002, 0x020000, 0x020004, 0x020003, 0x040001,
+ 0x020000, 0x020004, 0x020003, 0x030002, 0x020000, 0x020004, 0x020003, 0x040005
+ };
+
+ /**
+ * Decodes a number in the range [0..255], by reading 1 - 11 bits.
+ */
+ private static int decodeVarLenUnsignedByte(BitReader br) {
+ if (BitReader.readBits(br, 1) != 0) {
+ int n = BitReader.readBits(br, 3);
+ if (n == 0) {
+ return 1;
+ } else {
+ return BitReader.readBits(br, n) + (1 << n);
+ }
+ }
+ return 0;
+ }
+
+ private static void decodeMetaBlockLength(BitReader br, State state) {
+ state.inputEnd = BitReader.readBits(br, 1) == 1;
+ state.metaBlockLength = 0;
+ state.isUncompressed = false;
+ state.isMetadata = false;
+ if (state.inputEnd && BitReader.readBits(br, 1) != 0) {
+ return;
+ }
+ int sizeNibbles = BitReader.readBits(br, 2) + 4;
+ if (sizeNibbles == 7) {
+ state.isMetadata = true;
+ if (BitReader.readBits(br, 1) != 0) {
+ throw new BrotliRuntimeException("Corrupted reserved bit");
+ }
+ int sizeBytes = BitReader.readBits(br, 2);
+ if (sizeBytes == 0) {
+ return;
+ }
+ for (int i = 0; i < sizeBytes; i++) {
+ int bits = BitReader.readBits(br, 8);
+ if (bits == 0 && i + 1 == sizeBytes && sizeBytes > 1) {
+ throw new BrotliRuntimeException("Exuberant nibble");
+ }
+ state.metaBlockLength |= bits << (i * 8);
+ }
+ } else {
+ for (int i = 0; i < sizeNibbles; i++) {
+ int bits = BitReader.readBits(br, 4);
+ if (bits == 0 && i + 1 == sizeNibbles && sizeNibbles > 4) {
+ throw new BrotliRuntimeException("Exuberant nibble");
+ }
+ state.metaBlockLength |= bits << (i * 4);
+ }
+ }
+ state.metaBlockLength++;
+ if (!state.inputEnd) {
+ state.isUncompressed = BitReader.readBits(br, 1) == 1;
+ }
+ }
+
+ /**
+ * Decodes the next Huffman code from bit-stream.
+ */
+ private static int readSymbol(int[] table, int offset, BitReader br) {
+ BitReader.fillBitWindow(br);
+ offset += (int) (br.accumulator >>> br.bitOffset) & HUFFMAN_TABLE_MASK;
+ int n = (table[offset] >> 16) - HUFFMAN_TABLE_BITS;
+ if (n > 0) {
+ br.bitOffset += HUFFMAN_TABLE_BITS;
+ offset += table[offset] & 0xFFFF;
+ offset += (br.accumulator >>> br.bitOffset) & ((1 << n) - 1);
+ }
+ br.bitOffset += table[offset] >> 16;
+ return table[offset] & 0xFFFF;
+ }
+
+ private static int readBlockLength(int[] table, int offset, BitReader br) {
+ int code = readSymbol(table, offset, br);
+ int n = Prefix.BLOCK_LENGTH_N_BITS[code];
+ return Prefix.BLOCK_LENGTH_OFFSET[code] + BitReader.readBits(br, n);
+ }
+
+ private static int translateShortCodes(int code, int[] ringBuffer, int index) {
+ if (code < NUM_DISTANCE_SHORT_CODES) {
+ index += DISTANCE_SHORT_CODE_INDEX_OFFSET[code];
+ index &= 3;
+ return ringBuffer[index] + DISTANCE_SHORT_CODE_VALUE_OFFSET[code];
+ }
+ return code - NUM_DISTANCE_SHORT_CODES + 1;
+ }
+
+ private static void moveToFront(int[] v, int index) {
+ int value = v[index];
+ for (; index > 0; index--) {
+ v[index] = v[index - 1];
+ }
+ v[0] = value;
+ }
+
+ private static void inverseMoveToFrontTransform(byte[] v, int vLen) {
+ int[] mtf = new int[256];
+ for (int i = 0; i < 256; i++) {
+ mtf[i] = i;
+ }
+ for (int i = 0; i < vLen; i++) {
+ int index = v[i] & 0xFF;
+ v[i] = (byte) mtf[index];
+ if (index != 0) {
+ moveToFront(mtf, index);
+ }
+ }
+ }
+
+ private static void readHuffmanCodeLengths(
+ int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths, BitReader br) {
+ int symbol = 0;
+ int prevCodeLen = DEFAULT_CODE_LENGTH;
+ int repeat = 0;
+ int repeatCodeLen = 0;
+ int space = 32768;
+ int[] table = new int[32];
+
+ Huffman.buildHuffmanTable(table, 0, 5, codeLengthCodeLengths, CODE_LENGTH_CODES);
+
+ while (symbol < numSymbols && space > 0) {
+ BitReader.readMoreInput(br);
+ BitReader.fillBitWindow(br);
+ int p = (int) ((br.accumulator >>> br.bitOffset)) & 31;
+ br.bitOffset += table[p] >> 16;
+ int codeLen = table[p] & 0xFFFF;
+ if (codeLen < CODE_LENGTH_REPEAT_CODE) {
+ repeat = 0;
+ codeLengths[symbol++] = codeLen;
+ if (codeLen != 0) {
+ prevCodeLen = codeLen;
+ space -= 32768 >> codeLen;
+ }
+ } else {
+ int extraBits = codeLen - 14;
+ int newLen = 0;
+ if (codeLen == CODE_LENGTH_REPEAT_CODE) {
+ newLen = prevCodeLen;
+ }
+ if (repeatCodeLen != newLen) {
+ repeat = 0;
+ repeatCodeLen = newLen;
+ }
+ int oldRepeat = repeat;
+ if (repeat > 0) {
+ repeat -= 2;
+ repeat <<= extraBits;
+ }
+ repeat += BitReader.readBits(br, extraBits) + 3;
+ int repeatDelta = repeat - oldRepeat;
+ if (symbol + repeatDelta > numSymbols) {
+ throw new BrotliRuntimeException("symbol + repeatDelta > numSymbols"); // COV_NF_LINE
+ }
+ for (int i = 0; i < repeatDelta; i++) {
+ codeLengths[symbol++] = repeatCodeLen;
+ }
+ if (repeatCodeLen != 0) {
+ space -= repeatDelta << (15 - repeatCodeLen);
+ }
+ }
+ }
+ if (space != 0) {
+ throw new BrotliRuntimeException("Unused space"); // COV_NF_LINE
+ }
+ // TODO: Pass max_symbol to Huffman table builder instead?
+ Utils.fillWithZeroes(codeLengths, symbol, numSymbols - symbol);
+ }
+
+ // TODO: Use specialized versions for smaller tables.
+ static void readHuffmanCode(int alphabetSize, int[] table, int offset, BitReader br) {
+ boolean ok = true;
+ int simpleCodeOrSkip;
+ BitReader.readMoreInput(br);
+ // TODO: Avoid allocation.
+ int[] codeLengths = new int[alphabetSize];
+ simpleCodeOrSkip = BitReader.readBits(br, 2);
+ if (simpleCodeOrSkip == 1) { // Read symbols, codes & code lengths directly.
+ int maxBitsCounter = alphabetSize - 1;
+ int maxBits = 0;
+ int[] symbols = new int[4];
+ int numSymbols = BitReader.readBits(br, 2) + 1;
+ while (maxBitsCounter != 0) {
+ maxBitsCounter >>= 1;
+ maxBits++;
+ }
+ Utils.fillWithZeroes(codeLengths, 0, alphabetSize);
+ for (int i = 0; i < numSymbols; i++) {
+ symbols[i] = BitReader.readBits(br, maxBits) % alphabetSize;
+ codeLengths[symbols[i]] = 2;
+ }
+ codeLengths[symbols[0]] = 1;
+ switch (numSymbols) {
+ case 1:
+ break;
+ case 2:
+ ok = symbols[0] != symbols[1];
+ codeLengths[symbols[1]] = 1;
+ break;
+ case 3:
+ ok = symbols[0] != symbols[1] && symbols[0] != symbols[2] && symbols[1] != symbols[2];
+ break;
+ case 4:
+ ok = symbols[0] != symbols[1] && symbols[0] != symbols[2] && symbols[0] != symbols[3]
+ && symbols[1] != symbols[2] && symbols[1] != symbols[3] && symbols[2] != symbols[3];
+ if (BitReader.readBits(br, 1) == 1) {
+ codeLengths[symbols[2]] = 3;
+ codeLengths[symbols[3]] = 3;
+ } else {
+ codeLengths[symbols[0]] = 2;
+ }
+ break;
+ }
+ } else { // Decode Huffman-coded code lengths.
+ int[] codeLengthCodeLengths = new int[CODE_LENGTH_CODES];
+ int space = 32;
+ int numCodes = 0;
+ for (int i = simpleCodeOrSkip; i < CODE_LENGTH_CODES && space > 0; i++) {
+ int codeLenIdx = CODE_LENGTH_CODE_ORDER[i];
+ BitReader.fillBitWindow(br);
+ int p = (int) (br.accumulator >>> br.bitOffset) & 15;
+ // TODO: Demultiplex FIXED_TABLE.
+ br.bitOffset += FIXED_TABLE[p] >> 16;
+ int v = FIXED_TABLE[p] & 0xFFFF;
+ codeLengthCodeLengths[codeLenIdx] = v;
+ if (v != 0) {
+ space -= (32 >> v);
+ numCodes++;
+ }
+ }
+ ok = (numCodes == 1 || space == 0);
+ readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths, br);
+ }
+ if (!ok) {
+ throw new BrotliRuntimeException("Can't readHuffmanCode"); // COV_NF_LINE
+ }
+ Huffman.buildHuffmanTable(table, offset, HUFFMAN_TABLE_BITS, codeLengths, alphabetSize);
+ }
+
+ private static int decodeContextMap(int contextMapSize, byte[] contextMap, BitReader br) {
+ BitReader.readMoreInput(br);
+ int numTrees = decodeVarLenUnsignedByte(br) + 1;
+
+ if (numTrees == 1) {
+ Utils.fillWithZeroes(contextMap, 0, contextMapSize);
+ return numTrees;
+ }
+
+ boolean useRleForZeros = BitReader.readBits(br, 1) == 1;
+ int maxRunLengthPrefix = 0;
+ if (useRleForZeros) {
+ maxRunLengthPrefix = BitReader.readBits(br, 4) + 1;
+ }
+ int[] table = new int[Huffman.HUFFMAN_MAX_TABLE_SIZE];
+ readHuffmanCode(numTrees + maxRunLengthPrefix, table, 0, br);
+ for (int i = 0; i < contextMapSize; ) {
+ BitReader.readMoreInput(br);
+ int code = readSymbol(table, 0, br);
+ if (code == 0) {
+ contextMap[i] = 0;
+ i++;
+ } else if (code <= maxRunLengthPrefix) {
+ int reps = (1 << code) + BitReader.readBits(br, code);
+ while (reps != 0) {
+ if (i >= contextMapSize) {
+ throw new BrotliRuntimeException("Corrupted context map"); // COV_NF_LINE
+ }
+ contextMap[i] = 0;
+ i++;
+ reps--;
+ }
+ } else {
+ contextMap[i] = (byte) (code - maxRunLengthPrefix);
+ i++;
+ }
+ }
+ if (BitReader.readBits(br, 1) == 1) {
+ inverseMoveToFrontTransform(contextMap, contextMapSize);
+ }
+ return numTrees;
+ }
+
+ private static void decodeBlockTypeAndLength(State state, int treeType) {
+ final BitReader br = state.br;
+ final int[] ringBuffers = state.blockTypeRb;
+ final int offset = treeType * 2;
+ int blockType = readSymbol(
+ state.blockTypeTrees, treeType * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
+ state.blockLength[treeType] = readBlockLength(state.blockLenTrees,
+ treeType * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
+
+ if (blockType == 1) {
+ blockType = ringBuffers[offset + 1] + 1;
+ } else if (blockType == 0) {
+ blockType = ringBuffers[offset];
+ } else {
+ blockType -= 2;
+ }
+ if (blockType >= state.numBlockTypes[treeType]) {
+ blockType -= state.numBlockTypes[treeType];
+ }
+ ringBuffers[offset] = ringBuffers[offset + 1];
+ ringBuffers[offset + 1] = blockType;
+ }
+
+ private static void decodeLiteralBlockSwitch(State state) {
+ decodeBlockTypeAndLength(state, 0);
+ int literalBlockType = state.blockTypeRb[1];
+ state.contextMapSlice = literalBlockType << LITERAL_CONTEXT_BITS;
+ state.literalTreeIndex = state.contextMap[state.contextMapSlice] & 0xFF;
+ state.literalTree = state.hGroup0.trees[state.literalTreeIndex];
+ int contextMode = state.contextModes[literalBlockType];
+ state.contextLookupOffset1 = Context.LOOKUP_OFFSETS[contextMode];
+ state.contextLookupOffset2 = Context.LOOKUP_OFFSETS[contextMode + 1];
+ }
+
+ private static void decodeCommandBlockSwitch(State state) {
+ decodeBlockTypeAndLength(state, 1);
+ state.treeCommandOffset = state.hGroup1.trees[state.blockTypeRb[3]];
+ }
+
+ private static void decodeDistanceBlockSwitch(State state) {
+ decodeBlockTypeAndLength(state, 2);
+ state.distContextMapSlice = state.blockTypeRb[5] << DISTANCE_CONTEXT_BITS;
+ }
+
+ private static void maybeReallocateRingBuffer(State state) {
+ int newSize = state.maxRingBufferSize;
+ if ((long) newSize > state.expectedTotalSize) {
+ /* TODO: Handle 2GB+ cases more gracefully. */
+ int minimalNewSize = (int) state.expectedTotalSize + state.customDictionary.length;
+ while ((newSize >> 1) > minimalNewSize) {
+ newSize >>= 1;
+ }
+ if (!state.inputEnd && newSize < 16384 && state.maxRingBufferSize >= 16384) {
+ newSize = 16384;
+ }
+ }
+ if (newSize <= state.ringBufferSize) {
+ return;
+ }
+ int ringBufferSizeWithSlack = newSize + Dictionary.MAX_TRANSFORMED_WORD_LENGTH;
+ byte[] newBuffer = new byte[ringBufferSizeWithSlack];
+ if (state.ringBuffer != null) {
+ System.arraycopy(state.ringBuffer, 0, newBuffer, 0, state.ringBufferSize);
+ } else {
+ /* Prepend custom dictionary, if any. */
+ if (state.customDictionary.length != 0) {
+ int length = state.customDictionary.length;
+ int offset = 0;
+ if (length > state.maxBackwardDistance) {
+ offset = length - state.maxBackwardDistance;
+ length = state.maxBackwardDistance;
+ }
+ System.arraycopy(state.customDictionary, offset, newBuffer, 0, length);
+ state.pos = length;
+ state.bytesToIgnore = length;
+ }
+ }
+ state.ringBuffer = newBuffer;
+ state.ringBufferSize = newSize;
+ }
+
+ /**
+ * Reads next metablock header.
+ *
+ * @param state decoding state
+ */
+ private static void readMetablockInfo(State state) {
+ final BitReader br = state.br;
+
+ if (state.inputEnd) {
+ state.nextRunningState = FINISHED;
+ state.bytesToWrite = state.pos & (state.ringBufferSize - 1);
+ state.bytesWritten = 0;
+ state.runningState = WRITE;
+ return;
+ }
+ // TODO: Reset? Do we need this?
+ state.hGroup0.codes = null;
+ state.hGroup0.trees = null;
+ state.hGroup1.codes = null;
+ state.hGroup1.trees = null;
+ state.hGroup2.codes = null;
+ state.hGroup2.trees = null;
+
+ BitReader.readMoreInput(br);
+ decodeMetaBlockLength(br, state);
+ if (state.metaBlockLength == 0 && !state.isMetadata) {
+ return;
+ }
+ if (state.isUncompressed || state.isMetadata) {
+ BitReader.jumpToByteBoundary(br);
+ state.runningState = state.isMetadata ? READ_METADATA : COPY_UNCOMPRESSED;
+ } else {
+ state.runningState = COMPRESSED_BLOCK_START;
+ }
+
+ if (state.isMetadata) {
+ return;
+ }
+ state.expectedTotalSize += state.metaBlockLength;
+ if (state.ringBufferSize < state.maxRingBufferSize) {
+ maybeReallocateRingBuffer(state);
+ }
+ }
+
+ private static void readMetablockHuffmanCodesAndContextMaps(State state) {
+ final BitReader br = state.br;
+
+ for (int i = 0; i < 3; i++) {
+ state.numBlockTypes[i] = decodeVarLenUnsignedByte(br) + 1;
+ state.blockLength[i] = 1 << 28;
+ if (state.numBlockTypes[i] > 1) {
+ readHuffmanCode(state.numBlockTypes[i] + 2, state.blockTypeTrees,
+ i * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
+ readHuffmanCode(NUM_BLOCK_LENGTH_CODES, state.blockLenTrees,
+ i * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
+ state.blockLength[i] = readBlockLength(state.blockLenTrees,
+ i * Huffman.HUFFMAN_MAX_TABLE_SIZE, br);
+ }
+ }
+
+ BitReader.readMoreInput(br);
+ state.distancePostfixBits = BitReader.readBits(br, 2);
+ state.numDirectDistanceCodes =
+ NUM_DISTANCE_SHORT_CODES + (BitReader.readBits(br, 4) << state.distancePostfixBits);
+ state.distancePostfixMask = (1 << state.distancePostfixBits) - 1;
+ int numDistanceCodes = state.numDirectDistanceCodes + (48 << state.distancePostfixBits);
+ // TODO: Reuse?
+ state.contextModes = new byte[state.numBlockTypes[0]];
+ for (int i = 0; i < state.numBlockTypes[0];) {
+ /* Ensure that less than 256 bits read between readMoreInput. */
+ int limit = Math.min(i + 96, state.numBlockTypes[0]);
+ for (; i < limit; ++i) {
+ state.contextModes[i] = (byte) (BitReader.readBits(br, 2) << 1);
+ }
+ BitReader.readMoreInput(br);
+ }
+
+ // TODO: Reuse?
+ state.contextMap = new byte[state.numBlockTypes[0] << LITERAL_CONTEXT_BITS];
+ int numLiteralTrees = decodeContextMap(state.numBlockTypes[0] << LITERAL_CONTEXT_BITS,
+ state.contextMap, br);
+ state.trivialLiteralContext = true;
+ for (int j = 0; j < state.numBlockTypes[0] << LITERAL_CONTEXT_BITS; j++) {
+ if (state.contextMap[j] != j >> LITERAL_CONTEXT_BITS) {
+ state.trivialLiteralContext = false;
+ break;
+ }
+ }
+
+ // TODO: Reuse?
+ state.distContextMap = new byte[state.numBlockTypes[2] << DISTANCE_CONTEXT_BITS];
+ int numDistTrees = decodeContextMap(state.numBlockTypes[2] << DISTANCE_CONTEXT_BITS,
+ state.distContextMap, br);
+
+ HuffmanTreeGroup.init(state.hGroup0, NUM_LITERAL_CODES, numLiteralTrees);
+ HuffmanTreeGroup.init(state.hGroup1, NUM_INSERT_AND_COPY_CODES, state.numBlockTypes[1]);
+ HuffmanTreeGroup.init(state.hGroup2, numDistanceCodes, numDistTrees);
+
+ HuffmanTreeGroup.decode(state.hGroup0, br);
+ HuffmanTreeGroup.decode(state.hGroup1, br);
+ HuffmanTreeGroup.decode(state.hGroup2, br);
+
+ state.contextMapSlice = 0;
+ state.distContextMapSlice = 0;
+ state.contextLookupOffset1 = Context.LOOKUP_OFFSETS[state.contextModes[0]];
+ state.contextLookupOffset2 = Context.LOOKUP_OFFSETS[state.contextModes[0] + 1];
+ state.literalTreeIndex = 0;
+ state.literalTree = state.hGroup0.trees[0];
+ state.treeCommandOffset = state.hGroup1.trees[0]; // TODO: == 0?
+
+ state.blockTypeRb[0] = state.blockTypeRb[2] = state.blockTypeRb[4] = 1;
+ state.blockTypeRb[1] = state.blockTypeRb[3] = state.blockTypeRb[5] = 0;
+ }
+
+ private static void copyUncompressedData(State state) {
+ final BitReader br = state.br;
+ final byte[] ringBuffer = state.ringBuffer;
+ final int ringBufferMask = state.ringBufferSize - 1;
+
+ while (state.metaBlockLength > 0) {
+ BitReader.readMoreInput(br);
+ // Optimize
+ ringBuffer[state.pos & ringBufferMask] = (byte) (BitReader.readBits(br, 8));
+ state.metaBlockLength--;
+ if ((state.pos++ & ringBufferMask) == ringBufferMask) {
+ state.nextRunningState = COPY_UNCOMPRESSED;
+ state.bytesToWrite = state.ringBufferSize;
+ state.bytesWritten = 0;
+ state.runningState = WRITE;
+ return;
+ }
+ }
+ state.runningState = BLOCK_START;
+ }
+
+ private static boolean writeRingBuffer(State state) {
+ /* Ignore custom dictionary bytes. */
+ if (state.bytesToIgnore != 0) {
+ state.bytesWritten += state.bytesToIgnore;
+ state.bytesToIgnore = 0;
+ }
+ int toWrite = Math.min(state.outputLength - state.outputUsed,
+ state.bytesToWrite - state.bytesWritten);
+ if (toWrite != 0) {
+ System.arraycopy(state.ringBuffer, state.bytesWritten, state.output,
+ state.outputOffset + state.outputUsed, toWrite);
+ state.outputUsed += toWrite;
+ state.bytesWritten += toWrite;
+ }
+
+ return state.outputUsed < state.outputLength;
+ }
+
+ static void setCustomDictionary(State state, byte[] data) {
+ state.customDictionary = (data == null) ? new byte[0] : data;
+ }
+
+ /**
+ * Actual decompress implementation.
+ */
+ static void decompress(State state) {
+ if (state.runningState == UNINITIALIZED) {
+ throw new IllegalStateException("Can't decompress until initialized");
+ }
+ if (state.runningState == CLOSED) {
+ throw new IllegalStateException("Can't decompress after close");
+ }
+ final BitReader br = state.br;
+ int ringBufferMask = state.ringBufferSize - 1;
+ byte[] ringBuffer = state.ringBuffer;
+
+ while (state.runningState != FINISHED) {
+ // TODO: extract cases to methods for the better readability.
+ switch (state.runningState) {
+ case BLOCK_START:
+ if (state.metaBlockLength < 0) {
+ throw new BrotliRuntimeException("Invalid metablock length");
+ }
+ readMetablockInfo(state);
+ /* Ring-buffer would be reallocated here. */
+ ringBufferMask = state.ringBufferSize - 1;
+ ringBuffer = state.ringBuffer;
+ continue;
+
+ case COMPRESSED_BLOCK_START:
+ readMetablockHuffmanCodesAndContextMaps(state);
+ state.runningState = MAIN_LOOP;
+ // Fall through
+
+ case MAIN_LOOP:
+ if (state.metaBlockLength <= 0) {
+ // Protect pos from overflow, wrap it around at every GB of input data.
+ state.pos &= 0x3fffffff;
+ state.runningState = BLOCK_START;
+ continue;
+ }
+ BitReader.readMoreInput(br);
+ if (state.blockLength[1] == 0) {
+ decodeCommandBlockSwitch(state);
+ }
+ state.blockLength[1]--;
+ int cmdCode = readSymbol(state.hGroup1.codes, state.treeCommandOffset, br);
+ int rangeIdx = cmdCode >>> 6;
+ state.distanceCode = 0;
+ if (rangeIdx >= 2) {
+ rangeIdx -= 2;
+ state.distanceCode = -1;
+ }
+ int insertCode = Prefix.INSERT_RANGE_LUT[rangeIdx] + ((cmdCode >>> 3) & 7);
+ int copyCode = Prefix.COPY_RANGE_LUT[rangeIdx] + (cmdCode & 7);
+ state.insertLength = Prefix.INSERT_LENGTH_OFFSET[insertCode] + BitReader
+ .readBits(br, Prefix.INSERT_LENGTH_N_BITS[insertCode]);
+ state.copyLength = Prefix.COPY_LENGTH_OFFSET[copyCode] + BitReader
+ .readBits(br, Prefix.COPY_LENGTH_N_BITS[copyCode]);
+
+ state.j = 0;
+ state.runningState = INSERT_LOOP;
+
+ // Fall through
+ case INSERT_LOOP:
+ if (state.trivialLiteralContext) {
+ while (state.j < state.insertLength) {
+ BitReader.readMoreInput(br);
+ if (state.blockLength[0] == 0) {
+ decodeLiteralBlockSwitch(state);
+ }
+ state.blockLength[0]--;
+ ringBuffer[state.pos & ringBufferMask] = (byte) readSymbol(
+ state.hGroup0.codes, state.literalTree, br);
+ state.j++;
+ if ((state.pos++ & ringBufferMask) == ringBufferMask) {
+ state.nextRunningState = INSERT_LOOP;
+ state.bytesToWrite = state.ringBufferSize;
+ state.bytesWritten = 0;
+ state.runningState = WRITE;
+ break;
+ }
+ }
+ } else {
+ int prevByte1 = ringBuffer[(state.pos - 1) & ringBufferMask] & 0xFF;
+ int prevByte2 = ringBuffer[(state.pos - 2) & ringBufferMask] & 0xFF;
+ while (state.j < state.insertLength) {
+ BitReader.readMoreInput(br);
+ if (state.blockLength[0] == 0) {
+ decodeLiteralBlockSwitch(state);
+ }
+ int literalTreeIndex = state.contextMap[state.contextMapSlice
+ + (Context.LOOKUP[state.contextLookupOffset1 + prevByte1]
+ | Context.LOOKUP[state.contextLookupOffset2 + prevByte2])] & 0xFF;
+ state.blockLength[0]--;
+ prevByte2 = prevByte1;
+ prevByte1 = readSymbol(
+ state.hGroup0.codes, state.hGroup0.trees[literalTreeIndex], br);
+ ringBuffer[state.pos & ringBufferMask] = (byte) prevByte1;
+ state.j++;
+ if ((state.pos++ & ringBufferMask) == ringBufferMask) {
+ state.nextRunningState = INSERT_LOOP;
+ state.bytesToWrite = state.ringBufferSize;
+ state.bytesWritten = 0;
+ state.runningState = WRITE;
+ break;
+ }
+ }
+ }
+ if (state.runningState != INSERT_LOOP) {
+ continue;
+ }
+ state.metaBlockLength -= state.insertLength;
+ if (state.metaBlockLength <= 0) {
+ state.runningState = MAIN_LOOP;
+ continue;
+ }
+ if (state.distanceCode < 0) {
+ BitReader.readMoreInput(br);
+ if (state.blockLength[2] == 0) {
+ decodeDistanceBlockSwitch(state);
+ }
+ state.blockLength[2]--;
+ state.distanceCode = readSymbol(state.hGroup2.codes, state.hGroup2.trees[
+ state.distContextMap[state.distContextMapSlice
+ + (state.copyLength > 4 ? 3 : state.copyLength - 2)] & 0xFF], br);
+ if (state.distanceCode >= state.numDirectDistanceCodes) {
+ state.distanceCode -= state.numDirectDistanceCodes;
+ int postfix = state.distanceCode & state.distancePostfixMask;
+ state.distanceCode >>>= state.distancePostfixBits;
+ int n = (state.distanceCode >>> 1) + 1;
+ int offset = ((2 + (state.distanceCode & 1)) << n) - 4;
+ state.distanceCode = state.numDirectDistanceCodes + postfix
+ + ((offset + BitReader.readBits(br, n)) << state.distancePostfixBits);
+ }
+ }
+
+ // Convert the distance code to the actual distance by possibly looking up past distances
+ // from the ringBuffer.
+ state.distance = translateShortCodes(state.distanceCode, state.distRb, state.distRbIdx);
+ if (state.distance < 0) {
+ throw new BrotliRuntimeException("Negative distance"); // COV_NF_LINE
+ }
+
+ if (state.pos < state.maxBackwardDistance
+ && state.maxDistance != state.maxBackwardDistance) {
+ state.maxDistance = state.pos;
+ } else {
+ state.maxDistance = state.maxBackwardDistance;
+ }
+
+ state.copyDst = state.pos & ringBufferMask;
+ if (state.distance > state.maxDistance) {
+ state.runningState = TRANSFORM;
+ continue;
+ }
+
+ if (state.distanceCode > 0) {
+ state.distRb[state.distRbIdx & 3] = state.distance;
+ state.distRbIdx++;
+ }
+
+ if (state.copyLength > state.metaBlockLength) {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ state.j = 0;
+ state.runningState = COPY_LOOP;
+ // fall through
+ case COPY_LOOP:
+ for (; state.j < state.copyLength;) {
+ ringBuffer[state.pos & ringBufferMask] =
+ ringBuffer[(state.pos - state.distance) & ringBufferMask];
+ // TODO: condense
+ state.metaBlockLength--;
+ state.j++;
+ if ((state.pos++ & ringBufferMask) == ringBufferMask) {
+ state.nextRunningState = COPY_LOOP;
+ state.bytesToWrite = state.ringBufferSize;
+ state.bytesWritten = 0;
+ state.runningState = WRITE;
+ break;
+ }
+ }
+ if (state.runningState == COPY_LOOP) {
+ state.runningState = MAIN_LOOP;
+ }
+ continue;
+
+ case TRANSFORM:
+ if (state.copyLength >= Dictionary.MIN_WORD_LENGTH
+ && state.copyLength <= Dictionary.MAX_WORD_LENGTH) {
+ int offset = Dictionary.OFFSETS_BY_LENGTH[state.copyLength];
+ int wordId = state.distance - state.maxDistance - 1;
+ int shift = Dictionary.SIZE_BITS_BY_LENGTH[state.copyLength];
+ int mask = (1 << shift) - 1;
+ int wordIdx = wordId & mask;
+ int transformIdx = wordId >>> shift;
+ offset += wordIdx * state.copyLength;
+ if (transformIdx < Transform.TRANSFORMS.length) {
+ int len = Transform.transformDictionaryWord(ringBuffer, state.copyDst,
+ Dictionary.getData(), offset, state.copyLength,
+ Transform.TRANSFORMS[transformIdx]);
+ state.copyDst += len;
+ state.pos += len;
+ state.metaBlockLength -= len;
+ if (state.copyDst >= state.ringBufferSize) {
+ state.nextRunningState = COPY_WRAP_BUFFER;
+ state.bytesToWrite = state.ringBufferSize;
+ state.bytesWritten = 0;
+ state.runningState = WRITE;
+ continue;
+ }
+ } else {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ } else {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ state.runningState = MAIN_LOOP;
+ continue;
+
+ case COPY_WRAP_BUFFER:
+ System.arraycopy(ringBuffer, state.ringBufferSize, ringBuffer, 0,
+ state.copyDst - state.ringBufferSize);
+ state.runningState = MAIN_LOOP;
+ continue;
+
+ case READ_METADATA:
+ while (state.metaBlockLength > 0) {
+ BitReader.readMoreInput(br);
+ // Optimize
+ BitReader.readBits(br, 8);
+ state.metaBlockLength--;
+ }
+ state.runningState = BLOCK_START;
+ continue;
+
+
+ case COPY_UNCOMPRESSED:
+ copyUncompressedData(state);
+ continue;
+
+ case WRITE:
+ if (!writeRingBuffer(state)) {
+ // Output buffer is full.
+ return;
+ }
+ state.runningState = state.nextRunningState;
+ continue;
+
+ default:
+ throw new BrotliRuntimeException("Unexpected state " + state.runningState);
+ }
+ }
+ if (state.runningState == FINISHED) {
+ if (state.metaBlockLength < 0) {
+ throw new BrotliRuntimeException("Invalid metablock length");
+ }
+ BitReader.jumpToByteBoundary(br);
+ BitReader.checkHealth(state.br);
+ }
+ }
+}