net.sourceforge.plantuml.brotli.Decode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plantuml-epl Show documentation
Show all versions of plantuml-epl Show documentation
PlantUML is a component that allows to quickly write diagrams from text.
// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
* |
* | PlantUML : a free UML diagram generator
* |
* +=======================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/liberapay (only 1€ per month!)
* https://plantuml.com/paypal
*
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the Eclipse Public License.
*
* THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
* LICENSE ("AGREEMENT"). [Eclipse Public License - v 1.0]
*
* ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES
* RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
*
* You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.
*
* PlantUML can occasionally display sponsored or advertising messages. Those
* messages are usually generated on welcome or error images and never on
* functional diagrams.
* See https://plantuml.com/professional if you want to remove them
*
* Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
* are owned by the author of their corresponding sources code (that is, their
* textual description in PlantUML language). Those images are not covered by
* this EPL license.
*
* The generated images can then be used without any reference to the EPL license.
* It is not even necessary to stipulate that they have been generated with PlantUML,
* although this will be appreciated by the PlantUML team.
*
* There is an exception : if the textual description in PlantUML language is also covered
* by any license, then the generated images are logically covered
* by the very same license.
*
* This is the IGY distribution (Install GraphViz by Yourself).
* You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
* (see https://plantuml.com/graphviz-dot )
*
* Icons provided by OpenIconic : https://useiconic.com/open
* Archimate sprites provided by Archi : http://www.archimatetool.com
* Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
* Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
* ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
* ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
* CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
* Brotli (c) by the Brotli Authors https://github.com/google/brotli
* Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
* Twemoji (c) by Twitter at https://twemoji.twitter.com/
*
*/
package net.sourceforge.plantuml.brotli;
import java.io.IOException;
import java.io.InputStream;
/**
* API for Brotli decompression.
*/
final class Decode {
// ----------------------------------------------------------------------------
// RunningState
// ----------------------------------------------------------------------------
private static final int UNINITIALIZED = 0;
private static final int BLOCK_START = 1;
private static final int COMPRESSED_BLOCK_START = 2;
private static final int MAIN_LOOP = 3;
private static final int READ_METADATA = 4;
private static final int COPY_UNCOMPRESSED = 5;
private static final int INSERT_LOOP = 6;
private static final int COPY_LOOP = 7;
private static final int COPY_WRAP_BUFFER = 8;
private static final int TRANSFORM = 9;
private static final int FINISHED = 10;
private static final int CLOSED = 11;
private static final int WRITE = 12;
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;
/**
* Maximum possible Huffman table size for an alphabet size of 704, max code
* length 15 and root table bits 8.
*/
static final int HUFFMAN_TABLE_SIZE = 1080;
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 };
static final int[] DICTIONARY_OFFSETS_BY_LENGTH = { 0, 0, 0, 0, 0, 4096, 9216, 21504, 35840, 44032, 53248, 63488,
74752, 87040, 93696, 100864, 104704, 106752, 108928, 113536, 115968, 118528, 119872, 121280, 122016 };
static final int[] DICTIONARY_SIZE_BITS_BY_LENGTH = { 0, 0, 0, 0, 10, 10, 11, 11, 10, 10, 10, 10, 10, 9, 9, 8, 7, 7,
8, 7, 7, 6, 6, 5, 5 };
static final int MIN_WORD_LENGTH = 4;
static final int MAX_WORD_LENGTH = 24;
static final int MAX_TRANSFORMED_WORD_LENGTH = 5 + MAX_WORD_LENGTH + 8;
// ----------------------------------------------------------------------------
// Prefix code LUT.
// ----------------------------------------------------------------------------
static final int[] BLOCK_LENGTH_OFFSET = { 1, 5, 9, 13, 17, 25, 33, 41, 49, 65, 81, 97, 113, 145, 177, 209, 241,
305, 369, 497, 753, 1265, 2289, 4337, 8433, 16625 };
static final int[] BLOCK_LENGTH_N_BITS = { 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, 9, 10, 11,
12, 13, 24 };
static final int[] INSERT_LENGTH_OFFSET = { 0, 1, 2, 3, 4, 5, 6, 8, 10, 14, 18, 26, 34, 50, 66, 98, 130, 194, 322,
578, 1090, 2114, 6210, 22594 };
static final int[] INSERT_LENGTH_N_BITS = { 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 12, 14,
24 };
static final int[] COPY_LENGTH_OFFSET = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 18, 22, 30, 38, 54, 70, 102, 134, 198,
326, 582, 1094, 2118 };
static final int[] COPY_LENGTH_N_BITS = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10,
24 };
static final int[] INSERT_RANGE_LUT = { 0, 0, 8, 8, 0, 16, 8, 16, 16 };
static final int[] COPY_RANGE_LUT = { 0, 8, 0, 8, 16, 0, 16, 8, 16 };
private static int decodeWindowBits(State s) {
BitReader.fillBitWindow(s);
if (BitReader.readFewBits(s, 1) == 0) {
return 16;
}
int n = BitReader.readFewBits(s, 3);
if (n != 0) {
return 17 + n;
}
n = BitReader.readFewBits(s, 3);
if (n != 0) {
return 8 + n;
}
return 17;
}
/**
* Associate input with decoder state.
*
* @param s uninitialized state without associated input
* @param input compressed data source
*/
static void initState(State s, InputStream input) {
if (s.runningState != UNINITIALIZED) {
throw new IllegalStateException("State MUST be uninitialized");
}
s.blockTrees = new int[6 * HUFFMAN_TABLE_SIZE];
s.input = input;
BitReader.initBitReader(s);
int windowBits = decodeWindowBits(s);
if (windowBits == 9) { /* Reserved case for future expansion. */
throw new BrotliRuntimeException("Invalid 'windowBits' code");
}
s.maxRingBufferSize = 1 << windowBits;
s.maxBackwardDistance = s.maxRingBufferSize - 16;
s.runningState = BLOCK_START;
}
static void close(State s) throws IOException {
if (s.runningState == UNINITIALIZED) {
throw new IllegalStateException("State MUST be initialized");
}
if (s.runningState == CLOSED) {
return;
}
s.runningState = CLOSED;
if (s.input != null) {
Utils.closeInput(s.input);
s.input = null;
}
}
/**
* Decodes a number in the range [0..255], by reading 1 - 11 bits.
*/
private static int decodeVarLenUnsignedByte(State s) {
BitReader.fillBitWindow(s);
if (BitReader.readFewBits(s, 1) != 0) {
int n = BitReader.readFewBits(s, 3);
if (n == 0) {
return 1;
} else {
return BitReader.readFewBits(s, n) + (1 << n);
}
}
return 0;
}
private static void decodeMetaBlockLength(State s) {
BitReader.fillBitWindow(s);
s.inputEnd = BitReader.readFewBits(s, 1);
s.metaBlockLength = 0;
s.isUncompressed = 0;
s.isMetadata = 0;
if ((s.inputEnd != 0) && BitReader.readFewBits(s, 1) != 0) {
return;
}
int sizeNibbles = BitReader.readFewBits(s, 2) + 4;
if (sizeNibbles == 7) {
s.isMetadata = 1;
if (BitReader.readFewBits(s, 1) != 0) {
throw new BrotliRuntimeException("Corrupted reserved bit");
}
int sizeBytes = BitReader.readFewBits(s, 2);
if (sizeBytes == 0) {
return;
}
for (int i = 0; i < sizeBytes; i++) {
BitReader.fillBitWindow(s);
int bits = BitReader.readFewBits(s, 8);
if (bits == 0 && i + 1 == sizeBytes && sizeBytes > 1) {
throw new BrotliRuntimeException("Exuberant nibble");
}
s.metaBlockLength |= bits << (i * 8);
}
} else {
for (int i = 0; i < sizeNibbles; i++) {
BitReader.fillBitWindow(s);
int bits = BitReader.readFewBits(s, 4);
if (bits == 0 && i + 1 == sizeNibbles && sizeNibbles > 4) {
throw new BrotliRuntimeException("Exuberant nibble");
}
s.metaBlockLength |= bits << (i * 4);
}
}
s.metaBlockLength++;
if (s.inputEnd == 0) {
s.isUncompressed = BitReader.readFewBits(s, 1);
}
}
/**
* Decodes the next Huffman code from bit-stream.
*/
private static int readSymbol(int[] table, int offset, State s) {
int val = BitReader.peekBits(s);
offset += val & HUFFMAN_TABLE_MASK;
int bits = table[offset] >> 16;
int sym = table[offset] & 0xFFFF;
if (bits <= HUFFMAN_TABLE_BITS) {
s.bitOffset += bits;
return sym;
}
offset += sym;
int mask = (1 << bits) - 1;
offset += (val & mask) >>> HUFFMAN_TABLE_BITS;
s.bitOffset += ((table[offset] >> 16) + HUFFMAN_TABLE_BITS);
return table[offset] & 0xFFFF;
}
private static int readBlockLength(int[] table, int offset, State s) {
BitReader.fillBitWindow(s);
int code = readSymbol(table, offset, s);
int n = BLOCK_LENGTH_N_BITS[code];
BitReader.fillBitWindow(s);
return BLOCK_LENGTH_OFFSET[code] + BitReader.readBits(s, 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,
State s) {
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(s);
BitReader.fillBitWindow(s);
int p = BitReader.peekBits(s) & 31;
s.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;
}
BitReader.fillBitWindow(s);
repeat += BitReader.readFewBits(s, 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.fillIntsWithZeroes(codeLengths, symbol, numSymbols);
}
static int checkDupes(int[] symbols, int length) {
for (int i = 0; i < length - 1; ++i) {
for (int j = i + 1; j < length; ++j) {
if (symbols[i] == symbols[j]) {
return 0;
}
}
}
return 1;
}
// TODO: Use specialized versions for smaller tables.
static void readHuffmanCode(int alphabetSize, int[] table, int offset, State s) {
int ok = 1;
int simpleCodeOrSkip;
BitReader.readMoreInput(s);
// TODO: Avoid allocation.
int[] codeLengths = new int[alphabetSize];
BitReader.fillBitWindow(s);
simpleCodeOrSkip = BitReader.readFewBits(s, 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.readFewBits(s, 2) + 1;
while (maxBitsCounter != 0) {
maxBitsCounter >>= 1;
maxBits++;
}
// TODO: uncomment when codeLengths is reused.
// Utils.fillWithZeroes(codeLengths, 0, alphabetSize);
for (int i = 0; i < numSymbols; i++) {
BitReader.fillBitWindow(s);
symbols[i] = BitReader.readFewBits(s, maxBits) % alphabetSize;
codeLengths[symbols[i]] = 2;
}
codeLengths[symbols[0]] = 1;
switch (numSymbols) {
case 2:
codeLengths[symbols[1]] = 1;
break;
case 4:
if (BitReader.readFewBits(s, 1) == 1) {
codeLengths[symbols[2]] = 3;
codeLengths[symbols[3]] = 3;
} else {
codeLengths[symbols[0]] = 2;
}
break;
default:
break;
}
ok = checkDupes(symbols, numSymbols);
} 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(s);
int p = BitReader.peekBits(s) & 15;
// TODO: Demultiplex FIXED_TABLE.
s.bitOffset += FIXED_TABLE[p] >> 16;
int v = FIXED_TABLE[p] & 0xFFFF;
codeLengthCodeLengths[codeLenIdx] = v;
if (v != 0) {
space -= (32 >> v);
numCodes++;
}
}
if (space != 0 && numCodes != 1) {
ok = 0;
}
readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize, codeLengths, s);
}
if (ok == 0) {
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, State s) {
BitReader.readMoreInput(s);
int numTrees = decodeVarLenUnsignedByte(s) + 1;
if (numTrees == 1) {
Utils.fillBytesWithZeroes(contextMap, 0, contextMapSize);
return numTrees;
}
BitReader.fillBitWindow(s);
int useRleForZeros = BitReader.readFewBits(s, 1);
int maxRunLengthPrefix = 0;
if (useRleForZeros != 0) {
maxRunLengthPrefix = BitReader.readFewBits(s, 4) + 1;
}
int[] table = new int[HUFFMAN_TABLE_SIZE];
readHuffmanCode(numTrees + maxRunLengthPrefix, table, 0, s);
for (int i = 0; i < contextMapSize;) {
BitReader.readMoreInput(s);
BitReader.fillBitWindow(s);
int code = readSymbol(table, 0, s);
if (code == 0) {
contextMap[i] = 0;
i++;
} else if (code <= maxRunLengthPrefix) {
BitReader.fillBitWindow(s);
int reps = (1 << code) + BitReader.readFewBits(s, 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++;
}
}
BitReader.fillBitWindow(s);
if (BitReader.readFewBits(s, 1) == 1) {
inverseMoveToFrontTransform(contextMap, contextMapSize);
}
return numTrees;
}
private static int decodeBlockTypeAndLength(State s, int treeType, int numBlockTypes) {
final int[] ringBuffers = s.rings;
final int offset = 4 + treeType * 2;
BitReader.fillBitWindow(s);
int blockType = readSymbol(s.blockTrees, treeType * HUFFMAN_TABLE_SIZE, s);
int result = readBlockLength(s.blockTrees, (treeType + 3) * HUFFMAN_TABLE_SIZE, s);
if (blockType == 1) {
blockType = ringBuffers[offset + 1] + 1;
} else if (blockType == 0) {
blockType = ringBuffers[offset];
} else {
blockType -= 2;
}
if (blockType >= numBlockTypes) {
blockType -= numBlockTypes;
}
ringBuffers[offset] = ringBuffers[offset + 1];
ringBuffers[offset + 1] = blockType;
return result;
}
private static void decodeLiteralBlockSwitch(State s) {
s.literalBlockLength = decodeBlockTypeAndLength(s, 0, s.numLiteralBlockTypes);
int literalBlockType = s.rings[5];
s.contextMapSlice = literalBlockType << LITERAL_CONTEXT_BITS;
s.literalTreeIndex = s.contextMap[s.contextMapSlice] & 0xFF;
s.literalTree = s.hGroup0[s.literalTreeIndex];
int contextMode = s.contextModes[literalBlockType];
s.contextLookupOffset1 = contextMode << 9;
s.contextLookupOffset2 = s.contextLookupOffset1 + 256;
}
private static void decodeCommandBlockSwitch(State s) {
s.commandBlockLength = decodeBlockTypeAndLength(s, 1, s.numCommandBlockTypes);
s.treeCommandOffset = s.hGroup1[s.rings[7]];
}
private static void decodeDistanceBlockSwitch(State s) {
s.distanceBlockLength = decodeBlockTypeAndLength(s, 2, s.numDistanceBlockTypes);
s.distContextMapSlice = s.rings[9] << DISTANCE_CONTEXT_BITS;
}
private static void maybeReallocateRingBuffer(State s) {
int newSize = s.maxRingBufferSize;
if (newSize > s.expectedTotalSize) {
/* TODO: Handle 2GB+ cases more gracefully. */
int minimalNewSize = s.expectedTotalSize;
while ((newSize >> 1) > minimalNewSize) {
newSize >>= 1;
}
if ((s.inputEnd == 0) && newSize < 16384 && s.maxRingBufferSize >= 16384) {
newSize = 16384;
}
}
if (newSize <= s.ringBufferSize) {
return;
}
int ringBufferSizeWithSlack = newSize + MAX_TRANSFORMED_WORD_LENGTH;
byte[] newBuffer = new byte[ringBufferSizeWithSlack];
if (s.ringBuffer.length != 0) {
System.arraycopy(s.ringBuffer, 0, newBuffer, 0, s.ringBufferSize);
}
s.ringBuffer = newBuffer;
s.ringBufferSize = newSize;
}
private static void readNextMetablockHeader(State s) {
if (s.inputEnd != 0) {
s.nextRunningState = FINISHED;
s.bytesToWrite = s.pos;
s.bytesWritten = 0;
s.runningState = WRITE;
return;
}
// TODO: Reset? Do we need this?
s.hGroup0 = new int[0];
s.hGroup1 = new int[0];
s.hGroup2 = new int[0];
BitReader.readMoreInput(s);
decodeMetaBlockLength(s);
if ((s.metaBlockLength == 0) && (s.isMetadata == 0)) {
return;
}
if ((s.isUncompressed != 0) || (s.isMetadata != 0)) {
BitReader.jumpToByteBoundary(s);
s.runningState = (s.isMetadata != 0) ? READ_METADATA : COPY_UNCOMPRESSED;
} else {
s.runningState = COMPRESSED_BLOCK_START;
}
if (s.isMetadata != 0) {
return;
}
s.expectedTotalSize += s.metaBlockLength;
if (s.expectedTotalSize > 1 << 30) {
s.expectedTotalSize = 1 << 30;
}
if (s.ringBufferSize < s.maxRingBufferSize) {
maybeReallocateRingBuffer(s);
}
}
private static int readMetablockPartition(State s, int treeType, int numBlockTypes) {
if (numBlockTypes <= 1) {
return 1 << 28;
}
readHuffmanCode(numBlockTypes + 2, s.blockTrees, treeType * HUFFMAN_TABLE_SIZE, s);
readHuffmanCode(NUM_BLOCK_LENGTH_CODES, s.blockTrees, (treeType + 3) * HUFFMAN_TABLE_SIZE, s);
return readBlockLength(s.blockTrees, (treeType + 3) * HUFFMAN_TABLE_SIZE, s);
}
private static void readMetablockHuffmanCodesAndContextMaps(State s) {
s.numLiteralBlockTypes = decodeVarLenUnsignedByte(s) + 1;
s.literalBlockLength = readMetablockPartition(s, 0, s.numLiteralBlockTypes);
s.numCommandBlockTypes = decodeVarLenUnsignedByte(s) + 1;
s.commandBlockLength = readMetablockPartition(s, 1, s.numCommandBlockTypes);
s.numDistanceBlockTypes = decodeVarLenUnsignedByte(s) + 1;
s.distanceBlockLength = readMetablockPartition(s, 2, s.numDistanceBlockTypes);
BitReader.readMoreInput(s);
BitReader.fillBitWindow(s);
s.distancePostfixBits = BitReader.readFewBits(s, 2);
s.numDirectDistanceCodes = NUM_DISTANCE_SHORT_CODES + (BitReader.readFewBits(s, 4) << s.distancePostfixBits);
s.distancePostfixMask = (1 << s.distancePostfixBits) - 1;
int numDistanceCodes = s.numDirectDistanceCodes + (48 << s.distancePostfixBits);
// TODO: Reuse?
s.contextModes = new byte[s.numLiteralBlockTypes];
for (int i = 0; i < s.numLiteralBlockTypes;) {
/* Ensure that less than 256 bits read between readMoreInput. */
int limit = Math.min(i + 96, s.numLiteralBlockTypes);
for (; i < limit; ++i) {
BitReader.fillBitWindow(s);
s.contextModes[i] = (byte) (BitReader.readFewBits(s, 2));
}
BitReader.readMoreInput(s);
}
// TODO: Reuse?
s.contextMap = new byte[s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS];
int numLiteralTrees = decodeContextMap(s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS, s.contextMap, s);
s.trivialLiteralContext = 1;
for (int j = 0; j < s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS; j++) {
if (s.contextMap[j] != j >> LITERAL_CONTEXT_BITS) {
s.trivialLiteralContext = 0;
break;
}
}
// TODO: Reuse?
s.distContextMap = new byte[s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS];
int numDistTrees = decodeContextMap(s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS, s.distContextMap, s);
s.hGroup0 = decodeHuffmanTreeGroup(NUM_LITERAL_CODES, numLiteralTrees, s);
s.hGroup1 = decodeHuffmanTreeGroup(NUM_INSERT_AND_COPY_CODES, s.numCommandBlockTypes, s);
s.hGroup2 = decodeHuffmanTreeGroup(numDistanceCodes, numDistTrees, s);
s.contextMapSlice = 0;
s.distContextMapSlice = 0;
s.contextLookupOffset1 = (int) (s.contextModes[0]) << 9;
s.contextLookupOffset2 = s.contextLookupOffset1 + 256;
s.literalTreeIndex = 0;
s.literalTree = s.hGroup0[0];
s.treeCommandOffset = s.hGroup1[0];
s.rings[4] = 1;
s.rings[5] = 0;
s.rings[6] = 1;
s.rings[7] = 0;
s.rings[8] = 1;
s.rings[9] = 0;
}
private static void copyUncompressedData(State s) {
final byte[] ringBuffer = s.ringBuffer;
// Could happen if block ends at ring buffer end.
if (s.metaBlockLength <= 0) {
BitReader.reload(s);
s.runningState = BLOCK_START;
return;
}
int chunkLength = Math.min(s.ringBufferSize - s.pos, s.metaBlockLength);
BitReader.copyBytes(s, ringBuffer, s.pos, chunkLength);
s.metaBlockLength -= chunkLength;
s.pos += chunkLength;
if (s.pos == s.ringBufferSize) {
s.nextRunningState = COPY_UNCOMPRESSED;
s.bytesToWrite = s.ringBufferSize;
s.bytesWritten = 0;
s.runningState = WRITE;
return;
}
BitReader.reload(s);
s.runningState = BLOCK_START;
}
private static int writeRingBuffer(State s) {
int toWrite = Math.min(s.outputLength - s.outputUsed, s.bytesToWrite - s.bytesWritten);
if (toWrite != 0) {
System.arraycopy(s.ringBuffer, s.bytesWritten, s.output, s.outputOffset + s.outputUsed, toWrite);
s.outputUsed += toWrite;
s.bytesWritten += toWrite;
}
if (s.outputUsed < s.outputLength) {
return 1;
} else {
return 0;
}
}
private static int[] decodeHuffmanTreeGroup(int alphabetSize, int n, State s) {
int[] group = new int[n + (n * HUFFMAN_TABLE_SIZE)];
int next = n;
for (int i = 0; i < n; i++) {
group[i] = next;
Decode.readHuffmanCode(alphabetSize, group, next, s);
next += HUFFMAN_TABLE_SIZE;
}
return group;
}
/**
* Actual decompress implementation.
*/
static void decompress(State s) {
if (s.runningState == UNINITIALIZED) {
throw new IllegalStateException("Can't decompress until initialized");
}
if (s.runningState == CLOSED) {
throw new IllegalStateException("Can't decompress after close");
}
int ringBufferMask = s.ringBufferSize - 1;
byte[] ringBuffer = s.ringBuffer;
while (s.runningState != FINISHED) {
// TODO: extract cases to methods for the better readability.
switch (s.runningState) {
case BLOCK_START:
if (s.metaBlockLength < 0) {
throw new BrotliRuntimeException("Invalid metablock length");
}
readNextMetablockHeader(s);
/* Ring-buffer would be reallocated here. */
ringBufferMask = s.ringBufferSize - 1;
ringBuffer = s.ringBuffer;
continue;
case COMPRESSED_BLOCK_START:
readMetablockHuffmanCodesAndContextMaps(s);
s.runningState = MAIN_LOOP;
// Fall through
case MAIN_LOOP:
if (s.metaBlockLength <= 0) {
s.runningState = BLOCK_START;
continue;
}
BitReader.readMoreInput(s);
if (s.commandBlockLength == 0) {
decodeCommandBlockSwitch(s);
}
s.commandBlockLength--;
BitReader.fillBitWindow(s);
int cmdCode = readSymbol(s.hGroup1, s.treeCommandOffset, s);
int rangeIdx = cmdCode >>> 6;
s.distanceCode = 0;
if (rangeIdx >= 2) {
rangeIdx -= 2;
s.distanceCode = -1;
}
int insertCode = INSERT_RANGE_LUT[rangeIdx] + ((cmdCode >>> 3) & 7);
BitReader.fillBitWindow(s);
int insertBits = INSERT_LENGTH_N_BITS[insertCode];
int insertExtra = BitReader.readBits(s, insertBits);
s.insertLength = INSERT_LENGTH_OFFSET[insertCode] + insertExtra;
int copyCode = COPY_RANGE_LUT[rangeIdx] + (cmdCode & 7);
BitReader.fillBitWindow(s);
int copyBits = COPY_LENGTH_N_BITS[copyCode];
int copyExtra = BitReader.readBits(s, copyBits);
s.copyLength = COPY_LENGTH_OFFSET[copyCode] + copyExtra;
s.j = 0;
s.runningState = INSERT_LOOP;
// Fall through
case INSERT_LOOP:
if (s.trivialLiteralContext != 0) {
while (s.j < s.insertLength) {
BitReader.readMoreInput(s);
if (s.literalBlockLength == 0) {
decodeLiteralBlockSwitch(s);
}
s.literalBlockLength--;
BitReader.fillBitWindow(s);
ringBuffer[s.pos] = (byte) readSymbol(s.hGroup0, s.literalTree, s);
s.j++;
if (s.pos++ == ringBufferMask) {
s.nextRunningState = INSERT_LOOP;
s.bytesToWrite = s.ringBufferSize;
s.bytesWritten = 0;
s.runningState = WRITE;
break;
}
}
} else {
int prevByte1 = ringBuffer[(s.pos - 1) & ringBufferMask] & 0xFF;
int prevByte2 = ringBuffer[(s.pos - 2) & ringBufferMask] & 0xFF;
while (s.j < s.insertLength) {
BitReader.readMoreInput(s);
if (s.literalBlockLength == 0) {
decodeLiteralBlockSwitch(s);
}
int literalTreeIndex = s.contextMap[s.contextMapSlice
+ (Context.LOOKUP[s.contextLookupOffset1 + prevByte1]
| Context.LOOKUP[s.contextLookupOffset2 + prevByte2])]
& 0xFF;
s.literalBlockLength--;
prevByte2 = prevByte1;
BitReader.fillBitWindow(s);
prevByte1 = readSymbol(s.hGroup0, s.hGroup0[literalTreeIndex], s);
ringBuffer[s.pos] = (byte) prevByte1;
s.j++;
if (s.pos++ == ringBufferMask) {
s.nextRunningState = INSERT_LOOP;
s.bytesToWrite = s.ringBufferSize;
s.bytesWritten = 0;
s.runningState = WRITE;
break;
}
}
}
if (s.runningState != INSERT_LOOP) {
continue;
}
s.metaBlockLength -= s.insertLength;
if (s.metaBlockLength <= 0) {
s.runningState = MAIN_LOOP;
continue;
}
if (s.distanceCode < 0) {
BitReader.readMoreInput(s);
if (s.distanceBlockLength == 0) {
decodeDistanceBlockSwitch(s);
}
s.distanceBlockLength--;
BitReader.fillBitWindow(s);
s.distanceCode = readSymbol(s.hGroup2, s.hGroup2[s.distContextMap[s.distContextMapSlice
+ (s.copyLength > 4 ? 3 : s.copyLength - 2)] & 0xFF], s);
if (s.distanceCode >= s.numDirectDistanceCodes) {
s.distanceCode -= s.numDirectDistanceCodes;
int postfix = s.distanceCode & s.distancePostfixMask;
s.distanceCode >>>= s.distancePostfixBits;
int n = (s.distanceCode >>> 1) + 1;
int offset = ((2 + (s.distanceCode & 1)) << n) - 4;
BitReader.fillBitWindow(s);
int distanceExtra = BitReader.readBits(s, n);
s.distanceCode = s.numDirectDistanceCodes + postfix
+ ((offset + distanceExtra) << s.distancePostfixBits);
}
}
// Convert the distance code to the actual distance by possibly looking up past
// distances
// from the ringBuffer.
s.distance = translateShortCodes(s.distanceCode, s.rings, s.distRbIdx);
if (s.distance < 0) {
throw new BrotliRuntimeException("Negative distance"); // COV_NF_LINE
}
if (s.maxDistance != s.maxBackwardDistance && s.pos < s.maxBackwardDistance) {
s.maxDistance = s.pos;
} else {
s.maxDistance = s.maxBackwardDistance;
}
s.copyDst = s.pos;
if (s.distance > s.maxDistance) {
s.runningState = TRANSFORM;
continue;
}
if (s.distanceCode > 0) {
s.rings[s.distRbIdx & 3] = s.distance;
s.distRbIdx++;
}
if (s.copyLength > s.metaBlockLength) {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
}
s.j = 0;
s.runningState = COPY_LOOP;
// fall through
case COPY_LOOP:
int src = (s.pos - s.distance) & ringBufferMask;
int dst = s.pos;
int copyLength = s.copyLength - s.j;
int srcEnd = src + copyLength;
int dstEnd = dst + copyLength;
if ((srcEnd < ringBufferMask) && (dstEnd < ringBufferMask)) {
if (copyLength < 12 || (srcEnd > dst && dstEnd > src)) {
for (int k = 0; k < copyLength; ++k) {
ringBuffer[dst++] = ringBuffer[src++];
}
} else {
Utils.copyBytesWithin(ringBuffer, dst, src, srcEnd);
}
s.j += copyLength;
s.metaBlockLength -= copyLength;
s.pos += copyLength;
} else {
for (; s.j < s.copyLength;) {
ringBuffer[s.pos] = ringBuffer[(s.pos - s.distance) & ringBufferMask];
s.metaBlockLength--;
s.j++;
if (s.pos++ == ringBufferMask) {
s.nextRunningState = COPY_LOOP;
s.bytesToWrite = s.ringBufferSize;
s.bytesWritten = 0;
s.runningState = WRITE;
break;
}
}
}
if (s.runningState == COPY_LOOP) {
s.runningState = MAIN_LOOP;
}
continue;
case TRANSFORM:
if (s.copyLength >= MIN_WORD_LENGTH && s.copyLength <= MAX_WORD_LENGTH) {
int offset = DICTIONARY_OFFSETS_BY_LENGTH[s.copyLength];
int wordId = s.distance - s.maxDistance - 1;
int shift = DICTIONARY_SIZE_BITS_BY_LENGTH[s.copyLength];
int mask = (1 << shift) - 1;
int wordIdx = wordId & mask;
int transformIdx = wordId >>> shift;
offset += wordIdx * s.copyLength;
if (transformIdx < Transform.NUM_TRANSFORMS) {
int len = Transform.transformDictionaryWord(ringBuffer, s.copyDst, Dictionary.getData(), offset,
s.copyLength, transformIdx);
s.copyDst += len;
s.pos += len;
s.metaBlockLength -= len;
if (s.copyDst >= s.ringBufferSize) {
s.nextRunningState = COPY_WRAP_BUFFER;
s.bytesToWrite = s.ringBufferSize;
s.bytesWritten = 0;
s.runningState = WRITE;
continue;
}
} else {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
}
} else {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
}
s.runningState = MAIN_LOOP;
continue;
case COPY_WRAP_BUFFER:
Utils.copyBytesWithin(ringBuffer, 0, s.ringBufferSize, s.copyDst);
s.runningState = MAIN_LOOP;
continue;
case READ_METADATA:
while (s.metaBlockLength > 0) {
BitReader.readMoreInput(s);
// Optimize
BitReader.fillBitWindow(s);
BitReader.readFewBits(s, 8);
s.metaBlockLength--;
}
s.runningState = BLOCK_START;
continue;
case COPY_UNCOMPRESSED:
copyUncompressedData(s);
continue;
case WRITE:
if (writeRingBuffer(s) == 0) {
// Output buffer is full.
return;
}
if (s.pos >= s.maxBackwardDistance) {
s.maxDistance = s.maxBackwardDistance;
}
s.pos &= ringBufferMask;
s.runningState = s.nextRunningState;
continue;
default:
throw new BrotliRuntimeException("Unexpected state " + s.runningState);
}
}
if (s.runningState == FINISHED) {
if (s.metaBlockLength < 0) {
throw new BrotliRuntimeException("Invalid metablock length");
}
BitReader.jumpToByteBoundary(s);
BitReader.checkHealth(s, 1);
}
}
}