modify Java decoder in a way it could be transpiled to exception unfriendly languages

PiperOrigin-RevId: 769488037
This commit is contained in:
Evgenii Kliuchnikov
2025-06-10 00:27:22 -07:00
committed by Copybara-Service
parent 271be11429
commit 08bdaebbf8
8 changed files with 500 additions and 191 deletions

View File

@@ -6,6 +6,14 @@
package org.brotli.dec;
import static org.brotli.dec.BrotliError.BROTLI_ERROR;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_PADDING_BITS;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_READ_AFTER_END;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_TRUNCATED_INPUT;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_UNUSED_BYTES_AFTER_END;
import static org.brotli.dec.BrotliError.BROTLI_OK;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_UNALIGNED_COPY_BYTES;
/**
* Bit reading helpers.
*/
@@ -16,8 +24,8 @@ final class BitReader {
private static final int LOG_BITNESS = Utils.getLogBintness();
// Not only Java compiler prunes "if (const false)" code, but JVM as well.
// Code under "if (DEBUG != 0)" have zero performance impact (outside unit tests).
private static final int DEBUG = Utils.isDebugMode();
// Code under "if (BIT_READER_DEBUG != 0)" have zero performance impact (outside unit tests).
private static final int BIT_READER_DEBUG = Utils.isDebugMode();
static final int BITNESS = 1 << LOG_BITNESS;
@@ -36,30 +44,25 @@ final class BitReader {
private static final int HALF_SIZE = BYTENESS / 2;
private static final int HALVES_CAPACITY = CAPACITY / HALF_SIZE;
private static final int HALF_BUFFER_SIZE = BUFFER_SIZE / HALF_SIZE;
private static final int HALF_WATERLINE = WATERLINE / HALF_SIZE;
private static final int LOG_HALF_SIZE = LOG_BITNESS - 4;
static final int HALF_WATERLINE = WATERLINE / HALF_SIZE;
/**
* Fills up the input buffer.
*
* <p> No-op if there are at least 36 bytes present after current position.
* <p> Should not be called if there are at least 36 bytes present after current position.
*
* <p> After encountering the end of the input stream, 64 additional zero bytes are copied to the
* buffer.
*/
static void readMoreInput(State s) {
if (s.halfOffset > HALF_WATERLINE) {
doReadMoreInput(s);
}
}
static void doReadMoreInput(State s) {
static int readMoreInput(State s) {
if (s.endOfStreamReached != 0) {
if (halfAvailable(s) >= -2) {
return;
return BROTLI_OK;
}
throw new BrotliRuntimeException("No more input");
return Utils.makeError(s, BROTLI_ERROR_TRUNCATED_INPUT);
}
final int readOffset = s.halfOffset << LOG_HALF_SIZE;
int bytesInBuffer = CAPACITY - readOffset;
@@ -70,6 +73,9 @@ final class BitReader {
final int spaceLeft = CAPACITY - bytesInBuffer;
final int len = Utils.readInput(s, s.byteBuffer, bytesInBuffer, spaceLeft);
// EOF is -1 in Java, but 0 in C#.
if (len < BROTLI_ERROR) {
return len;
}
if (len <= 0) {
s.endOfStreamReached = 1;
s.tailBytes = bytesInBuffer;
@@ -79,19 +85,21 @@ final class BitReader {
bytesInBuffer += len;
}
bytesToNibbles(s, bytesInBuffer);
return BROTLI_OK;
}
static void checkHealth(State s, int endOfStream) {
static int checkHealth(State s, int endOfStream) {
if (s.endOfStreamReached == 0) {
return;
return BROTLI_OK;
}
final int byteOffset = (s.halfOffset << LOG_HALF_SIZE) + ((s.bitOffset + 7) >> 3) - BYTENESS;
if (byteOffset > s.tailBytes) {
throw new BrotliRuntimeException("Read after end");
return Utils.makeError(s, BROTLI_ERROR_READ_AFTER_END);
}
if ((endOfStream != 0) && (byteOffset != s.tailBytes)) {
throw new BrotliRuntimeException("Unused bytes after end");
return Utils.makeError(s, BROTLI_ERROR_UNUSED_BYTES_AFTER_END);
}
return BROTLI_OK;
}
static void assertAccumulatorHealthy(State s) {
@@ -101,7 +109,7 @@ final class BitReader {
}
static void fillBitWindow(State s) {
if (DEBUG != 0) {
if (BIT_READER_DEBUG != 0) {
assertAccumulatorHealthy(s);
}
if (s.bitOffset >= HALF_BITNESS) {
@@ -118,7 +126,7 @@ final class BitReader {
}
static void doFillBitWindow(State s) {
if (DEBUG != 0) {
if (BIT_READER_DEBUG != 0) {
assertAccumulatorHealthy(s);
}
if (BITNESS == 64) {
@@ -165,7 +173,7 @@ final class BitReader {
return low | (readFewBits(s, n - 16) << 16);
}
static void initBitReader(State s) {
static int initBitReader(State s) {
s.byteBuffer = new byte[BUFFER_SIZE];
if (BITNESS == 64) {
s.accumulator64 = 0;
@@ -177,30 +185,41 @@ final class BitReader {
s.bitOffset = BITNESS;
s.halfOffset = HALVES_CAPACITY;
s.endOfStreamReached = 0;
prepare(s);
return prepare(s);
}
private static void prepare(State s) {
readMoreInput(s);
checkHealth(s, 0);
doFillBitWindow(s);
doFillBitWindow(s);
}
static void reload(State s) {
if (s.bitOffset == BITNESS) {
prepare(s);
private static int prepare(State s) {
if (s.halfOffset > BitReader.HALF_WATERLINE) {
int result = readMoreInput(s);
if (result != BROTLI_OK) {
return result;
}
}
int health = checkHealth(s, 0);
if (health != BROTLI_OK) {
return health;
}
doFillBitWindow(s);
doFillBitWindow(s);
return BROTLI_OK;
}
static void jumpToByteBoundary(State s) {
static int reload(State s) {
if (s.bitOffset == BITNESS) {
return prepare(s);
}
return BROTLI_OK;
}
static int jumpToByteBoundary(State s) {
final int padding = (BITNESS - s.bitOffset) & 7;
if (padding != 0) {
final int paddingBits = readFewBits(s, padding);
if (paddingBits != 0) {
throw new BrotliRuntimeException("Corrupted padding bits");
return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_PADDING_BITS);
}
}
return BROTLI_OK;
}
static int halfAvailable(State s) {
@@ -211,11 +230,11 @@ final class BitReader {
return limit - s.halfOffset;
}
static void copyRawBytes(State s, byte[] data, int offset, int length) {
static int copyRawBytes(State s, byte[] data, int offset, int length) {
int pos = offset;
int len = length;
if ((s.bitOffset & 7) != 0) {
throw new BrotliRuntimeException("Unaligned copyBytes");
return Utils.makeError(s, BROTLI_PANIC_UNALIGNED_COPY_BYTES);
}
// Drain accumulator.
@@ -225,7 +244,7 @@ final class BitReader {
len--;
}
if (len == 0) {
return;
return BROTLI_OK;
}
// Get data from shadow buffer with "sizeof(int)" granularity.
@@ -239,7 +258,7 @@ final class BitReader {
s.halfOffset += copyNibbles;
}
if (len == 0) {
return;
return BROTLI_OK;
}
// Read tail bytes.
@@ -251,19 +270,23 @@ final class BitReader {
s.bitOffset += 8;
len--;
}
checkHealth(s, 0);
return;
return checkHealth(s, 0);
}
// Now it is possible to copy bytes directly.
while (len > 0) {
final int chunkLen = Utils.readInput(s, data, pos, len);
if (chunkLen == -1) {
throw new BrotliRuntimeException("Unexpected end of input");
// EOF is -1 in Java, but 0 in C#.
if (len < BROTLI_ERROR) {
return len;
}
if (chunkLen <= 0) {
return Utils.makeError(s, BROTLI_ERROR_TRUNCATED_INPUT);
}
pos += chunkLen;
len -= chunkLen;
}
return BROTLI_OK;
}
/**

View File

@@ -0,0 +1,47 @@
/* Copyright 2025 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;
/**
* Possible errors from decoder.
*/
public class BrotliError {
public static final int BROTLI_OK = 0;
public static final int BROTLI_OK_DONE = BROTLI_OK + 1;
public static final int BROTLI_OK_NEED_MORE_OUTPUT = BROTLI_OK + 2;
// It is important that actual error codes are LESS than -1!
public static final int BROTLI_ERROR = -1;
public static final int BROTLI_ERROR_CORRUPTED_CODE_LENGTH_TABLE = BROTLI_ERROR - 1;
public static final int BROTLI_ERROR_CORRUPTED_CONTEXT_MAP = BROTLI_ERROR - 2;
public static final int BROTLI_ERROR_CORRUPTED_HUFFMAN_CODE_HISTOGRAM = BROTLI_ERROR - 3;
public static final int BROTLI_ERROR_CORRUPTED_PADDING_BITS = BROTLI_ERROR - 4;
public static final int BROTLI_ERROR_CORRUPTED_RESERVED_BIT = BROTLI_ERROR - 5;
public static final int BROTLI_ERROR_DUPLICATE_SIMPLE_HUFFMAN_SYMBOL = BROTLI_ERROR - 6;
public static final int BROTLI_ERROR_EXUBERANT_NIBBLE = BROTLI_ERROR - 7;
public static final int BROTLI_ERROR_INVALID_BACKWARD_REFERENCE = BROTLI_ERROR - 8;
public static final int BROTLI_ERROR_INVALID_METABLOCK_LENGTH = BROTLI_ERROR - 9;
public static final int BROTLI_ERROR_INVALID_WINDOW_BITS = BROTLI_ERROR - 10;
public static final int BROTLI_ERROR_NEGATIVE_DISTANCE = BROTLI_ERROR - 11;
public static final int BROTLI_ERROR_READ_AFTER_END = BROTLI_ERROR - 12;
public static final int BROTLI_ERROR_READ_FAILED = BROTLI_ERROR - 13;
public static final int BROTLI_ERROR_SYMBOL_OUT_OF_RANGE = BROTLI_ERROR - 14;
public static final int BROTLI_ERROR_TRUNCATED_INPUT = BROTLI_ERROR - 15;
public static final int BROTLI_ERROR_UNUSED_BYTES_AFTER_END = BROTLI_ERROR - 16;
public static final int BROTLI_ERROR_UNUSED_HUFFMAN_SPACE = BROTLI_ERROR - 17;
public static final int BROTLI_PANIC = -21;
public static final int BROTLI_PANIC_ALREADY_CLOSED = BROTLI_PANIC - 1;
public static final int BROTLI_PANIC_MAX_DISTANCE_TOO_SMALL = BROTLI_PANIC - 2;
public static final int BROTLI_PANIC_STATE_NOT_FRESH = BROTLI_PANIC - 3;
public static final int BROTLI_PANIC_STATE_NOT_INITIALIZED = BROTLI_PANIC - 4;
public static final int BROTLI_PANIC_STATE_NOT_UNINITIALIZED = BROTLI_PANIC - 5;
public static final int BROTLI_PANIC_TOO_MANY_DICTIONARY_CHUNKS = BROTLI_PANIC - 6;
public static final int BROTLI_PANIC_UNEXPECTED_STATE = BROTLI_PANIC - 7;
public static final int BROTLI_PANIC_UNREACHABLE = BROTLI_PANIC - 8;
public static final int BROTLI_PANIC_UNALIGNED_COPY_BYTES = BROTLI_PANIC - 9;
}

View File

@@ -111,6 +111,7 @@ public class BrotliInputStream extends InputStream {
@Override
public void close() throws IOException {
Decode.close(state);
Utils.closeInput(state);
}
/**

View File

@@ -6,7 +6,30 @@
package org.brotli.dec;
import java.io.IOException;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_CODE_LENGTH_TABLE;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_CONTEXT_MAP;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_HUFFMAN_CODE_HISTOGRAM;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_CORRUPTED_RESERVED_BIT;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_DUPLICATE_SIMPLE_HUFFMAN_SYMBOL;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_EXUBERANT_NIBBLE;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_INVALID_BACKWARD_REFERENCE;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_INVALID_METABLOCK_LENGTH;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_INVALID_WINDOW_BITS;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_NEGATIVE_DISTANCE;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_SYMBOL_OUT_OF_RANGE;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_UNUSED_HUFFMAN_SPACE;
import static org.brotli.dec.BrotliError.BROTLI_OK;
import static org.brotli.dec.BrotliError.BROTLI_OK_DONE;
import static org.brotli.dec.BrotliError.BROTLI_OK_NEED_MORE_OUTPUT;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_ALREADY_CLOSED;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_MAX_DISTANCE_TOO_SMALL;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_STATE_NOT_FRESH;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_STATE_NOT_INITIALIZED;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_STATE_NOT_UNINITIALIZED;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_TOO_MANY_DICTIONARY_CHUNKS;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_UNEXPECTED_STATE;
import static org.brotli.dec.BrotliError.BROTLI_PANIC_UNREACHABLE;
import java.nio.ByteBuffer;
/**
@@ -22,6 +45,7 @@ final class Decode {
//----------------------------------------------------------------------------
// RunningState
//----------------------------------------------------------------------------
// NB: negative values are used for errors.
private static final int UNINITIALIZED = 0;
private static final int INITIALIZED = 1;
private static final int BLOCK_START = 2;
@@ -156,9 +180,9 @@ final class Decode {
// TODO(eustas): add a correctness test for this function when
// large-window and dictionary are implemented.
private static int calculateDistanceAlphabetLimit(int maxDistance, int npostfix, int ndirect) {
private static int calculateDistanceAlphabetLimit(State s, int maxDistance, int npostfix, int ndirect) {
if (maxDistance < ndirect + (2 << npostfix)) {
throw new IllegalArgumentException("maxDistance is too small");
return Utils.makeError(s, BROTLI_PANIC_MAX_DISTANCE_TOO_SMALL);
}
final int offset = ((maxDistance - ndirect) >> npostfix) + 4;
final int ndistbits = log2floor(offset) - 1;
@@ -237,9 +261,8 @@ final class Decode {
return -1;
}
return n;
} else {
return 8 + n;
}
return 8 + n;
}
return 17;
}
@@ -251,24 +274,26 @@ final class Decode {
*
* @param s initialized state, before any read is performed.
*/
static void enableEagerOutput(State s) {
static int enableEagerOutput(State s) {
if (s.runningState != INITIALIZED) {
throw new IllegalStateException("State MUST be freshly initialized");
return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_FRESH);
}
s.isEager = 1;
return BROTLI_OK;
}
static void enableLargeWindow(State s) {
static int enableLargeWindow(State s) {
if (s.runningState != INITIALIZED) {
throw new IllegalStateException("State MUST be freshly initialized");
return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_FRESH);
}
s.isLargeWindow = 1;
return BROTLI_OK;
}
// TODO(eustas): do we need byte views?
static void attachDictionaryChunk(State s, byte[] data) {
static int attachDictionaryChunk(State s, byte[] data) {
if (s.runningState != INITIALIZED) {
throw new IllegalStateException("State MUST be freshly initialized");
return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_FRESH);
}
if (s.cdNumChunks == 0) {
s.cdChunks = new byte[16][];
@@ -276,45 +301,52 @@ final class Decode {
s.cdBlockBits = -1;
}
if (s.cdNumChunks == 15) {
throw new IllegalStateException("Too many dictionary chunks");
return Utils.makeError(s, BROTLI_PANIC_TOO_MANY_DICTIONARY_CHUNKS);
}
s.cdChunks[s.cdNumChunks] = data;
s.cdNumChunks++;
s.cdTotalSize += data.length;
s.cdChunkOffsets[s.cdNumChunks] = s.cdTotalSize;
return BROTLI_OK;
}
/**
* Associate input with decoder state.
*
* @param s uninitialized state without associated input
* @param input compressed data source
*/
static void initState(State s) {
static int initState(State s) {
if (s.runningState != UNINITIALIZED) {
throw new IllegalStateException("State MUST be uninitialized");
return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_UNINITIALIZED);
}
/* 6 trees + 1 extra "offset" slot to simplify table decoding logic. */
s.blockTrees = new int[7 + 3 * (HUFFMAN_TABLE_SIZE_258 + HUFFMAN_TABLE_SIZE_26)];
s.blockTrees[0] = 7;
s.distRbIdx = 3;
final int maxDistanceAlphabetLimit =
calculateDistanceAlphabetLimit(MAX_ALLOWED_DISTANCE, 3, 15 << 3);
int result = calculateDistanceAlphabetLimit(s, MAX_ALLOWED_DISTANCE, 3, 15 << 3);
if (result < BROTLI_OK) {
return result;
}
final int maxDistanceAlphabetLimit = result;
s.distExtraBits = new byte[maxDistanceAlphabetLimit];
s.distOffset = new int[maxDistanceAlphabetLimit];
BitReader.initBitReader(s);
result = BitReader.initBitReader(s);
if (result < BROTLI_OK) {
return result;
}
s.runningState = INITIALIZED;
return BROTLI_OK;
}
static void close(State s) throws IOException {
static int close(State s) {
if (s.runningState == UNINITIALIZED) {
throw new IllegalStateException("State MUST be initialized");
return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_INITIALIZED);
}
if (s.runningState == CLOSED) {
return;
return Utils.makeError(s, BROTLI_PANIC_ALREADY_CLOSED);
}
s.runningState = CLOSED;
Utils.closeInput(s);
return BROTLI_OK;
}
/**
@@ -326,37 +358,36 @@ final class Decode {
final int n = BitReader.readFewBits(s, 3);
if (n == 0) {
return 1;
} else {
return BitReader.readFewBits(s, n) + (1 << n);
}
return BitReader.readFewBits(s, n) + (1 << n);
}
return 0;
}
private static void decodeMetaBlockLength(State s) {
private static int 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;
return BROTLI_OK;
}
final 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");
return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_RESERVED_BIT);
}
final int sizeBytes = BitReader.readFewBits(s, 2);
if (sizeBytes == 0) {
return;
return BROTLI_OK;
}
for (int i = 0; i < sizeBytes; ++i) {
BitReader.fillBitWindow(s);
final int bits = BitReader.readFewBits(s, 8);
if (bits == 0 && i + 1 == sizeBytes && sizeBytes > 1) {
throw new BrotliRuntimeException("Exuberant nibble");
return Utils.makeError(s, BROTLI_ERROR_EXUBERANT_NIBBLE);
}
s.metaBlockLength += bits << (i * 8);
}
@@ -365,7 +396,7 @@ final class Decode {
BitReader.fillBitWindow(s);
final int bits = BitReader.readFewBits(s, 4);
if (bits == 0 && i + 1 == sizeNibbles && sizeNibbles > 4) {
throw new BrotliRuntimeException("Exuberant nibble");
return Utils.makeError(s, BROTLI_ERROR_EXUBERANT_NIBBLE);
}
s.metaBlockLength += bits << (i * 4);
}
@@ -374,6 +405,7 @@ final class Decode {
if (s.inputEnd == 0) {
s.isUncompressed = BitReader.readFewBits(s, 1);
}
return BROTLI_OK;
}
/**
@@ -428,7 +460,7 @@ final class Decode {
}
}
private static void readHuffmanCodeLengths(
private static int readHuffmanCodeLengths(
int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths, State s) {
int symbol = 0;
int prevCodeLen = DEFAULT_CODE_LENGTH;
@@ -440,7 +472,12 @@ final class Decode {
Huffman.buildHuffmanTable(table, tableIdx, 5, codeLengthCodeLengths, CODE_LENGTH_CODES);
while (symbol < numSymbols && space > 0) {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
int result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
BitReader.fillBitWindow(s);
final int p = BitReader.peekBits(s) & 31;
s.bitOffset += table[p] >> 16;
@@ -471,7 +508,7 @@ final class Decode {
repeat += BitReader.readFewBits(s, extraBits) + 3;
final int repeatDelta = repeat - oldRepeat;
if (symbol + repeatDelta > numSymbols) {
throw new BrotliRuntimeException("symbol + repeatDelta > numSymbols"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_CODE_LENGTH_TABLE);
}
for (int i = 0; i < repeatDelta; ++i) {
codeLengths[symbol++] = repeatCodeLen;
@@ -482,20 +519,22 @@ final class Decode {
}
}
if (space != 0) {
throw new BrotliRuntimeException("Unused space"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_UNUSED_HUFFMAN_SPACE);
}
// TODO(eustas): Pass max_symbol to Huffman table builder instead?
Utils.fillIntsWithZeroes(codeLengths, symbol, numSymbols);
return BROTLI_OK;
}
private static void checkDupes(int[] symbols, int length) {
private static int checkDupes(State s, 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]) {
throw new BrotliRuntimeException("Duplicate simple Huffman code symbol"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_DUPLICATE_SIMPLE_HUFFMAN_SYMBOL);
}
}
}
return BROTLI_OK;
}
/**
@@ -514,11 +553,14 @@ final class Decode {
BitReader.fillBitWindow(s);
final int symbol = BitReader.readFewBits(s, maxBits);
if (symbol >= alphabetSizeLimit) {
throw new BrotliRuntimeException("Can't readHuffmanCode"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_SYMBOL_OUT_OF_RANGE);
}
symbols[i] = symbol;
}
checkDupes(symbols, numSymbols);
int result = checkDupes(s, symbols, numSymbols);
if (result < BROTLI_OK) {
return result;
}
int histogramId = numSymbols;
if (numSymbols == 4) {
@@ -583,14 +625,19 @@ final class Decode {
if (v != 0) {
space -= (32 >> v);
numCodes++;
if (space <= 0) break;
if (space <= 0) {
break;
}
}
}
if (space != 0 && numCodes != 1) {
throw new BrotliRuntimeException("Corrupted Huffman code histogram"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_HUFFMAN_CODE_HISTOGRAM);
}
readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSizeLimit, codeLengths, s);
int result = readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSizeLimit, codeLengths, s);
if (result < BROTLI_OK) {
return result;
}
return Huffman.buildHuffmanTable(
tableGroup, tableIdx, HUFFMAN_TABLE_BITS, codeLengths, alphabetSizeLimit);
@@ -603,18 +650,28 @@ final class Decode {
*/
private static int readHuffmanCode(int alphabetSizeMax, int alphabetSizeLimit,
int[] tableGroup, int tableIdx, State s) {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
int result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
BitReader.fillBitWindow(s);
final int simpleCodeOrSkip = BitReader.readFewBits(s, 2);
if (simpleCodeOrSkip == 1) {
return readSimpleHuffmanCode(alphabetSizeMax, alphabetSizeLimit, tableGroup, tableIdx, s);
} else {
return readComplexHuffmanCode(alphabetSizeLimit, simpleCodeOrSkip, tableGroup, tableIdx, s);
}
return readComplexHuffmanCode(alphabetSizeLimit, simpleCodeOrSkip, tableGroup, tableIdx, s);
}
private static int decodeContextMap(int contextMapSize, byte[] contextMap, State s) {
BitReader.readMoreInput(s);
int result;
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
final int numTrees = decodeVarLenUnsignedByte(s) + 1;
if (numTrees == 1) {
@@ -633,10 +690,18 @@ final class Decode {
/* Speculative single entry table group. */
final int[] table = new int[tableSize + 1];
final int tableIdx = table.length - 1;
readHuffmanCode(alphabetSize, alphabetSize, table, tableIdx, s);
result = readHuffmanCode(alphabetSize, alphabetSize, table, tableIdx, s);
if (result < BROTLI_OK) {
return result;
}
int i = 0;
while (i < contextMapSize) {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
BitReader.fillBitWindow(s);
final int code = readSymbol(table, tableIdx, s);
if (code == 0) {
@@ -647,7 +712,7 @@ final class Decode {
int reps = (1 << code) + BitReader.readFewBits(s, code);
while (reps != 0) {
if (i >= contextMapSize) {
throw new BrotliRuntimeException("Corrupted context map"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_CORRUPTED_CONTEXT_MAP);
}
contextMap[i] = 0;
i++;
@@ -732,24 +797,36 @@ final class Decode {
s.ringBufferSize = newSize;
}
private static void readNextMetablockHeader(State s) {
private static int readNextMetablockHeader(State s) {
if (s.inputEnd != 0) {
s.nextRunningState = FINISHED;
s.runningState = INIT_WRITE;
return;
return BROTLI_OK;
}
// TODO(eustas): Reset? Do we need this?
s.literalTreeGroup = new int[0];
s.commandTreeGroup = new int[0];
s.distanceTreeGroup = new int[0];
BitReader.readMoreInput(s);
decodeMetaBlockLength(s);
int result;
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
result = decodeMetaBlockLength(s);
if (result < BROTLI_OK) {
return result;
}
if ((s.metaBlockLength == 0) && (s.isMetadata == 0)) {
return;
return BROTLI_OK;
}
if ((s.isUncompressed != 0) || (s.isMetadata != 0)) {
BitReader.jumpToByteBoundary(s);
result = BitReader.jumpToByteBoundary(s);
if (result < BROTLI_OK) {
return result;
}
if (s.isMetadata == 0) {
s.runningState = COPY_UNCOMPRESSED;
} else {
@@ -760,7 +837,7 @@ final class Decode {
}
if (s.isMetadata != 0) {
return;
return BROTLI_OK;
}
s.expectedTotalSize += s.metaBlockLength;
if (s.expectedTotalSize > 1 << 30) {
@@ -769,6 +846,7 @@ final class Decode {
if (s.ringBufferSize < s.maxRingBufferSize) {
maybeReallocateRingBuffer(s);
}
return BROTLI_OK;
}
private static int readMetablockPartition(State s, int treeType, int numBlockTypes) {
@@ -780,13 +858,21 @@ final class Decode {
}
final int blockTypeAlphabetSize = numBlockTypes + 2;
offset += readHuffmanCode(
int result = readHuffmanCode(
blockTypeAlphabetSize, blockTypeAlphabetSize, s.blockTrees, 2 * treeType, s);
if (result < BROTLI_OK) {
return result;
}
offset += result;
s.blockTrees[2 * treeType + 1] = offset;
final int blockLengthAlphabetSize = NUM_BLOCK_LENGTH_CODES;
offset += readHuffmanCode(
result = readHuffmanCode(
blockLengthAlphabetSize, blockLengthAlphabetSize, s.blockTrees, 2 * treeType + 1, s);
if (result < BROTLI_OK) {
return result;
}
offset += result;
s.blockTrees[2 * treeType + 2] = offset;
return readBlockLength(s.blockTrees, 2 * treeType + 1, s);
@@ -825,15 +911,32 @@ final class Decode {
}
}
private static void readMetablockHuffmanCodesAndContextMaps(State s) {
private static int readMetablockHuffmanCodesAndContextMaps(State s) {
s.numLiteralBlockTypes = decodeVarLenUnsignedByte(s) + 1;
s.literalBlockLength = readMetablockPartition(s, 0, s.numLiteralBlockTypes);
int result = readMetablockPartition(s, 0, s.numLiteralBlockTypes);
if (result < BROTLI_OK) {
return result;
}
s.literalBlockLength = result;
s.numCommandBlockTypes = decodeVarLenUnsignedByte(s) + 1;
s.commandBlockLength = readMetablockPartition(s, 1, s.numCommandBlockTypes);
result = readMetablockPartition(s, 1, s.numCommandBlockTypes);
if (result < BROTLI_OK) {
return result;
}
s.commandBlockLength = result;
s.numDistanceBlockTypes = decodeVarLenUnsignedByte(s) + 1;
s.distanceBlockLength = readMetablockPartition(s, 2, s.numDistanceBlockTypes);
result = readMetablockPartition(s, 2, s.numDistanceBlockTypes);
if (result < BROTLI_OK) {
return result;
}
s.distanceBlockLength = result;
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
BitReader.fillBitWindow(s);
s.distancePostfixBits = BitReader.readFewBits(s, 2);
s.numDirectDistanceCodes = BitReader.readFewBits(s, 4) << s.distancePostfixBits;
@@ -848,13 +951,22 @@ final class Decode {
s.contextModes[i] = (byte) BitReader.readFewBits(s, 2);
i++;
}
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
}
// TODO(eustas): Reuse?
final int contextMapLength = s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS;
s.contextMap = new byte[contextMapLength];
final int numLiteralTrees = decodeContextMap(contextMapLength, s.contextMap, s);
result = decodeContextMap(contextMapLength, s.contextMap, s);
if (result < BROTLI_OK) {
return result;
}
final int numLiteralTrees = result;
s.trivialLiteralContext = 1;
for (int j = 0; j < contextMapLength; ++j) {
if ((int) s.contextMap[j] != j >> LITERAL_CONTEXT_BITS) {
@@ -865,24 +977,46 @@ final class Decode {
// TODO(eustas): Reuse?
s.distContextMap = new byte[s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS];
final int numDistTrees = decodeContextMap(s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS,
result = decodeContextMap(s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS,
s.distContextMap, s);
if (result < BROTLI_OK) {
return result;
}
final int numDistTrees = result;
s.literalTreeGroup = decodeHuffmanTreeGroup(NUM_LITERAL_CODES, NUM_LITERAL_CODES,
numLiteralTrees, s);
s.commandTreeGroup = decodeHuffmanTreeGroup(NUM_COMMAND_CODES, NUM_COMMAND_CODES,
s.numCommandBlockTypes, s);
s.literalTreeGroup = new int[huffmanTreeGroupAllocSize(NUM_LITERAL_CODES, numLiteralTrees)];
result = decodeHuffmanTreeGroup(
NUM_LITERAL_CODES, NUM_LITERAL_CODES, numLiteralTrees, s, s.literalTreeGroup);
if (result < BROTLI_OK) {
return result;
}
s.commandTreeGroup =
new int[huffmanTreeGroupAllocSize(NUM_COMMAND_CODES, s.numCommandBlockTypes)];
result = decodeHuffmanTreeGroup(
NUM_COMMAND_CODES, NUM_COMMAND_CODES, s.numCommandBlockTypes, s, s.commandTreeGroup);
if (result < BROTLI_OK) {
return result;
}
int distanceAlphabetSizeMax = calculateDistanceAlphabetSize(
s.distancePostfixBits, s.numDirectDistanceCodes, MAX_DISTANCE_BITS);
int distanceAlphabetSizeLimit = distanceAlphabetSizeMax;
if (s.isLargeWindow == 1) {
distanceAlphabetSizeMax = calculateDistanceAlphabetSize(
s.distancePostfixBits, s.numDirectDistanceCodes, MAX_LARGE_WINDOW_DISTANCE_BITS);
distanceAlphabetSizeLimit = calculateDistanceAlphabetLimit(
MAX_ALLOWED_DISTANCE, s.distancePostfixBits, s.numDirectDistanceCodes);
result = calculateDistanceAlphabetLimit(
s, MAX_ALLOWED_DISTANCE, s.distancePostfixBits, s.numDirectDistanceCodes);
if (result < BROTLI_OK) {
return result;
}
distanceAlphabetSizeLimit = result;
}
s.distanceTreeGroup =
new int[huffmanTreeGroupAllocSize(distanceAlphabetSizeLimit, numDistTrees)];
result = decodeHuffmanTreeGroup(
distanceAlphabetSizeMax, distanceAlphabetSizeLimit, numDistTrees, s, s.distanceTreeGroup);
if (result < BROTLI_OK) {
return result;
}
s.distanceTreeGroup = decodeHuffmanTreeGroup(distanceAlphabetSizeMax, distanceAlphabetSizeLimit,
numDistTrees, s);
calculateDistanceLut(s, distanceAlphabetSizeLimit);
s.contextMapSlice = 0;
@@ -898,30 +1032,42 @@ final class Decode {
s.rings[7] = 0;
s.rings[8] = 1;
s.rings[9] = 0;
return BROTLI_OK;
}
private static void copyUncompressedData(State s) {
private static int copyUncompressedData(State s) {
final byte[] ringBuffer = s.ringBuffer;
int result;
// Could happen if block ends at ring buffer end.
if (s.metaBlockLength <= 0) {
BitReader.reload(s);
result = BitReader.reload(s);
if (result < BROTLI_OK) {
return result;
}
s.runningState = BLOCK_START;
return;
return BROTLI_OK;
}
final int chunkLength = Utils.min(s.ringBufferSize - s.pos, s.metaBlockLength);
BitReader.copyRawBytes(s, ringBuffer, s.pos, chunkLength);
result = BitReader.copyRawBytes(s, ringBuffer, s.pos, chunkLength);
if (result < BROTLI_OK) {
return result;
}
s.metaBlockLength -= chunkLength;
s.pos += chunkLength;
if (s.pos == s.ringBufferSize) {
s.nextRunningState = COPY_UNCOMPRESSED;
s.runningState = INIT_WRITE;
return;
return BROTLI_OK;
}
BitReader.reload(s);
result = BitReader.reload(s);
if (result < BROTLI_OK) {
return result;
}
s.runningState = BLOCK_START;
return BROTLI_OK;
}
private static int writeRingBuffer(State s) {
@@ -936,22 +1082,28 @@ final class Decode {
}
if (s.outputUsed < s.outputLength) {
return 1;
} else {
return 0;
return BROTLI_OK;
}
return BROTLI_OK_NEED_MORE_OUTPUT;
}
private static int[] decodeHuffmanTreeGroup(int alphabetSizeMax, int alphabetSizeLimit,
int n, State s) {
private static int huffmanTreeGroupAllocSize(int alphabetSizeLimit, int n) {
final int maxTableSize = MAX_HUFFMAN_TABLE_SIZE[(alphabetSizeLimit + 31) >> 5];
final int[] group = new int[n + n * maxTableSize];
return n + n * maxTableSize;
}
private static int decodeHuffmanTreeGroup(int alphabetSizeMax, int alphabetSizeLimit,
int n, State s, int[] group) {
int next = n;
for (int i = 0; i < n; ++i) {
group[i] = next;
next += readHuffmanCode(alphabetSizeMax, alphabetSizeLimit, group, i, s);
int result = readHuffmanCode(alphabetSizeMax, alphabetSizeLimit, group, i, s);
if (result < BROTLI_OK) {
return result;
}
next += result;
}
return group;
return BROTLI_OK;
}
// Returns offset in ringBuffer that should trigger WRITE when filled.
@@ -963,24 +1115,27 @@ final class Decode {
return result;
}
private static void doUseDictionary(State s, int fence) {
private static int doUseDictionary(State s, int fence) {
if (s.distance > MAX_ALLOWED_DISTANCE) {
throw new BrotliRuntimeException("Invalid backward reference");
return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE);
}
final int address = s.distance - s.maxDistance - 1 - s.cdTotalSize;
if (address < 0) {
initializeCompoundDictionaryCopy(s, -address - 1, s.copyLength);
int result = initializeCompoundDictionaryCopy(s, -address - 1, s.copyLength);
if (result < BROTLI_OK) {
return result;
}
s.runningState = COPY_FROM_COMPOUND_DICTIONARY;
} else {
// Force lazy dictionary initialization.
final ByteBuffer dictionaryData = Dictionary.getData();
final int wordLength = s.copyLength;
if (wordLength > Dictionary.MAX_DICTIONARY_WORD_LENGTH) {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE);
}
final int shift = Dictionary.sizeBits[wordLength];
if (shift == 0) {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE);
}
int offset = Dictionary.offsets[wordLength];
final int mask = (1 << shift) - 1;
@@ -989,7 +1144,7 @@ final class Decode {
offset += wordIdx * wordLength;
final Transform.Transforms transforms = Transform.RFC_TRANSFORMS;
if (transformIdx >= transforms.numTransforms) {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE);
}
final int len = Transform.transformDictionaryWord(s.ringBuffer, s.pos, dictionaryData,
offset, wordLength, transforms, transformIdx);
@@ -998,10 +1153,11 @@ final class Decode {
if (s.pos >= fence) {
s.nextRunningState = MAIN_LOOP;
s.runningState = INIT_WRITE;
return;
return BROTLI_OK;
}
s.runningState = MAIN_LOOP;
}
return BROTLI_OK;
}
private static void initializeCompoundDictionary(State s) {
@@ -1024,7 +1180,7 @@ final class Decode {
}
}
private static void initializeCompoundDictionaryCopy(State s, int address, int length) {
private static int initializeCompoundDictionaryCopy(State s, int address, int length) {
if (s.cdBlockBits == -1) {
initializeCompoundDictionary(s);
}
@@ -1033,7 +1189,7 @@ final class Decode {
index++;
}
if (s.cdTotalSize > address + length) {
throw new BrotliRuntimeException("Invalid backward reference");
return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE);
}
/* Update the recent distances cache */
s.distRbIdx = (s.distRbIdx + 1) & 0x3;
@@ -1043,6 +1199,7 @@ final class Decode {
s.cdBrOffset = address - s.cdChunkOffsets[index];
s.cdBrLength = length;
s.cdBrCopied = 0;
return BROTLI_OK;
}
private static int copyFromCompoundDictionary(State s, int fence) {
@@ -1078,17 +1235,21 @@ final class Decode {
/**
* Actual decompress implementation.
*/
static void decompress(State s) {
static int decompress(State s) {
int result;
if (s.runningState == UNINITIALIZED) {
throw new IllegalStateException("Can't decompress until initialized");
return Utils.makeError(s, BROTLI_PANIC_STATE_NOT_INITIALIZED);
}
if (s.runningState < 0) {
return Utils.makeError(s, BROTLI_PANIC_UNEXPECTED_STATE);
}
if (s.runningState == CLOSED) {
throw new IllegalStateException("Can't decompress after close");
return Utils.makeError(s, BROTLI_PANIC_ALREADY_CLOSED);
}
if (s.runningState == INITIALIZED) {
final int windowBits = decodeWindowBits(s);
if (windowBits == -1) { /* Reserved case for future expansion. */
throw new BrotliRuntimeException("Invalid 'windowBits' code");
return Utils.makeError(s, BROTLI_ERROR_INVALID_WINDOW_BITS);
}
s.maxRingBufferSize = 1 << windowBits;
s.maxBackwardDistance = s.maxRingBufferSize - 16;
@@ -1104,26 +1265,38 @@ final class Decode {
switch (s.runningState) {
case BLOCK_START:
if (s.metaBlockLength < 0) {
throw new BrotliRuntimeException("Invalid metablock length");
return Utils.makeError(s, BROTLI_ERROR_INVALID_METABLOCK_LENGTH);
}
result = readNextMetablockHeader(s);
if (result < BROTLI_OK) {
return result;
}
readNextMetablockHeader(s);
/* Ring-buffer would be reallocated here. */
fence = calculateFence(s);
ringBufferMask = s.ringBufferSize - 1;
ringBuffer = s.ringBuffer;
continue;
case COMPRESSED_BLOCK_START:
readMetablockHuffmanCodesAndContextMaps(s);
case COMPRESSED_BLOCK_START: {
result = readMetablockHuffmanCodesAndContextMaps(s);
if (result < BROTLI_OK) {
return result;
}
s.runningState = MAIN_LOOP;
continue;
}
case MAIN_LOOP:
if (s.metaBlockLength <= 0) {
s.runningState = BLOCK_START;
continue;
}
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
if (s.commandBlockLength == 0) {
decodeCommandBlockSwitch(s);
}
@@ -1152,7 +1325,12 @@ final class Decode {
case INSERT_LOOP:
if (s.trivialLiteralContext != 0) {
while (s.j < s.insertLength) {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
if (s.literalBlockLength == 0) {
decodeLiteralBlockSwitch(s);
}
@@ -1171,7 +1349,12 @@ final class Decode {
int prevByte1 = (int) ringBuffer[(s.pos - 1) & ringBufferMask] & 0xFF;
int prevByte2 = (int) ringBuffer[(s.pos - 2) & ringBufferMask] & 0xFF;
while (s.j < s.insertLength) {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
if (s.literalBlockLength == 0) {
decodeLiteralBlockSwitch(s);
}
@@ -1206,7 +1389,12 @@ final class Decode {
// distanceCode in untouched; assigning it 0 won't affect distance ring buffer rolling.
s.distance = s.rings[s.distRbIdx];
} else {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
if (s.distanceBlockLength == 0) {
decodeDistanceBlockSwitch(s);
}
@@ -1220,7 +1408,7 @@ final class Decode {
(s.distRbIdx + DISTANCE_SHORT_CODE_INDEX_OFFSET[distanceCode]) & 0x3;
s.distance = s.rings[index] + DISTANCE_SHORT_CODE_VALUE_OFFSET[distanceCode];
if (s.distance < 0) {
throw new BrotliRuntimeException("Negative distance"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_NEGATIVE_DISTANCE);
}
} else {
final int extraBits = (int) s.distExtraBits[distanceCode];
@@ -1253,7 +1441,7 @@ final class Decode {
}
if (s.copyLength > s.metaBlockLength) {
throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
return Utils.makeError(s, BROTLI_ERROR_INVALID_BACKWARD_REFERENCE);
}
s.j = 0;
s.runningState = COPY_LOOP;
@@ -1300,7 +1488,10 @@ final class Decode {
continue;
case USE_DICTIONARY:
doUseDictionary(s, fence);
result = doUseDictionary(s, fence);
if (result < BROTLI_OK) {
return result;
}
continue;
case COPY_FROM_COMPOUND_DICTIONARY:
@@ -1308,14 +1499,19 @@ final class Decode {
if (s.pos >= fence) {
s.nextRunningState = COPY_FROM_COMPOUND_DICTIONARY;
s.runningState = INIT_WRITE;
return;
return BROTLI_OK_NEED_MORE_OUTPUT;
}
s.runningState = MAIN_LOOP;
continue;
case READ_METADATA:
while (s.metaBlockLength > 0) {
BitReader.readMoreInput(s);
if (s.halfOffset > BitReader.HALF_WATERLINE) {
result = BitReader.readMoreInput(s);
if (result < BROTLI_OK) {
return result;
}
}
// Optimize
BitReader.fillBitWindow(s);
BitReader.readFewBits(s, 8);
@@ -1325,7 +1521,10 @@ final class Decode {
continue;
case COPY_UNCOMPRESSED:
copyUncompressedData(s);
result = copyUncompressedData(s);
if (result < BROTLI_OK) {
return result;
}
continue;
case INIT_WRITE:
@@ -1334,9 +1533,10 @@ final class Decode {
continue;
case WRITE:
if (writeRingBuffer(s) == 0) {
result = writeRingBuffer(s);
if (result != BROTLI_OK) {
// Output buffer is full.
return;
return result;
}
if (s.pos >= s.maxBackwardDistance) {
s.maxDistance = s.maxBackwardDistance;
@@ -1353,15 +1553,23 @@ final class Decode {
continue;
default:
throw new BrotliRuntimeException("Unexpected state " + String.valueOf(s.runningState));
return Utils.makeError(s, BROTLI_PANIC_UNEXPECTED_STATE);
}
}
if (s.runningState == FINISHED) {
if (s.metaBlockLength < 0) {
throw new BrotliRuntimeException("Invalid metablock length");
}
BitReader.jumpToByteBoundary(s);
BitReader.checkHealth(s, 1);
if (s.runningState != FINISHED) {
return Utils.makeError(s, BROTLI_PANIC_UNREACHABLE);
}
if (s.metaBlockLength < 0) {
return Utils.makeError(s, BROTLI_ERROR_INVALID_METABLOCK_LENGTH);
}
result = BitReader.jumpToByteBoundary(s);
if (result != BROTLI_OK) {
return result;
}
result = BitReader.checkHealth(s, 1);
if (result != BROTLI_OK) {
return result;
}
return BROTLI_OK_DONE;
}
}

View File

@@ -39,19 +39,23 @@ public final class Dictionary {
}
}
private static final int DICTIONARY_DEBUG = Utils.isDebugMode();
public static void setData(ByteBuffer newData, int[] newSizeBits) {
if ((Utils.isDirect(newData) == 0) || (Utils.isReadOnly(newData) == 0)) {
throw new BrotliRuntimeException("newData must be a direct read-only byte buffer");
}
// TODO: is that so?
if (newSizeBits.length > MAX_DICTIONARY_WORD_LENGTH) {
throw new BrotliRuntimeException(
"sizeBits length must be at most " + String.valueOf(MAX_DICTIONARY_WORD_LENGTH));
}
for (int i = 0; i < MIN_DICTIONARY_WORD_LENGTH; ++i) {
if (newSizeBits[i] != 0) {
if (DICTIONARY_DEBUG != 0) {
if ((Utils.isDirect(newData) == 0) || (Utils.isReadOnly(newData) == 0)) {
throw new BrotliRuntimeException("newData must be a direct read-only byte buffer");
}
// TODO: is that so?
if (newSizeBits.length > MAX_DICTIONARY_WORD_LENGTH) {
throw new BrotliRuntimeException(
"first " + String.valueOf(MIN_DICTIONARY_WORD_LENGTH) + " must be 0");
"sizeBits length must be at most " + String.valueOf(MAX_DICTIONARY_WORD_LENGTH));
}
for (int i = 0; i < MIN_DICTIONARY_WORD_LENGTH; ++i) {
if (newSizeBits[i] != 0) {
throw new BrotliRuntimeException(
"first " + String.valueOf(MIN_DICTIONARY_WORD_LENGTH) + " must be 0");
}
}
}
final int[] dictionaryOffsets = Dictionary.offsets;
@@ -65,20 +69,24 @@ public final class Dictionary {
dictionaryOffsets[i] = pos;
final int bits = dictionarySizeBits[i];
if (bits != 0) {
if (bits >= 31) {
throw new BrotliRuntimeException("newSizeBits values must be less than 31");
}
pos += i << bits;
if (pos <= 0 || pos > limit) {
throw new BrotliRuntimeException("newSizeBits is inconsistent: overflow");
pos += i << (bits & 31);
if (DICTIONARY_DEBUG != 0) {
if (bits >= 31) {
throw new BrotliRuntimeException("newSizeBits values must be less than 31");
}
if (pos <= 0 || pos > limit) {
throw new BrotliRuntimeException("newSizeBits is inconsistent: overflow");
}
}
}
}
for (int i = newSizeBits.length; i < 32; ++i) {
dictionaryOffsets[i] = pos;
}
if (pos != limit) {
throw new BrotliRuntimeException("newSizeBits is inconsistent: underflow");
if (DICTIONARY_DEBUG != 0) {
if (pos != limit) {
throw new BrotliRuntimeException("newSizeBits is inconsistent: underflow");
}
}
Dictionary.data = newData;
}

View File

@@ -33,12 +33,16 @@ final class DictionaryData {
*/
private static final String SIZE_BITS_DATA = "AAAAKKLLKKKKKJJIHHIHHGGFF";
private static final int DICTIONARY_DATA_DEBUG = Utils.isDebugMode();
private static void unpackDictionaryData(ByteBuffer dictionary, String data0, String data1,
String skipFlip, int[] sizeBits, String sizeBitsData) {
// Initialize lower 7 bits of every byte in the dictionary.
final byte[] dict = Utils.toUsAsciiBytes(data0 + data1);
if (dict.length != dictionary.capacity()) {
throw new RuntimeException("Corrupted brotli dictionary");
if (DICTIONARY_DATA_DEBUG != 0) {
if (dict.length != dictionary.capacity()) {
throw new RuntimeException("Corrupted brotli dictionary");
}
}
// Toggle high bit using run-length delta encoded "skipFlip".

View File

@@ -50,7 +50,7 @@ public class SynthTest {
assertArrayEquals(expected, actual);
} catch (IOException ex) {
if (expectSuccess) {
fail("expected to succeed decoding, but failed");
throw new AssertionError("expected to succeed decoding, but failed", ex);
}
}
}

View File

@@ -6,6 +6,10 @@
package org.brotli.dec;
import static org.brotli.dec.BrotliError.BROTLI_ERROR_READ_FAILED;
import static org.brotli.dec.BrotliError.BROTLI_OK;
import static org.brotli.dec.BrotliError.BROTLI_PANIC;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -29,8 +33,8 @@ final class Utils {
* less than 16.
*
* @param dest array to fill with zeroes
* @param offset the first byte to fill
* @param length number of bytes to change
* @param start the first item to fill
* @param end the last item to fill (exclusive)
*/
static void fillBytesWithZeroes(byte[] dest, int start, int end) {
int cursor = start;
@@ -48,8 +52,8 @@ final class Utils {
* less than 16.
*
* @param dest array to fill with zeroes
* @param offset the first item to fill
* @param length number of item to change
* @param start the first item to fill
* @param end the last item to fill (exclusive)
*/
static void fillIntsWithZeroes(int[] dest, int start, int end) {
int cursor = start;
@@ -72,7 +76,7 @@ final class Utils {
try {
return s.input.read(dst, offset, length);
} catch (IOException e) {
throw new BrotliRuntimeException("Failed to read input", e);
return makeError(s, BROTLI_ERROR_READ_FAILED);
}
}
@@ -134,4 +138,18 @@ final class Utils {
static int min(int a, int b) {
return Math.min(a, b);
}
static int makeError(State s, int code) {
if (code >= BROTLI_OK) {
return code;
}
if (s.runningState >= 0) {
s.runningState = code; // Only the first error is remembered.
}
// TODO(eustas): expand codes to messages, if ever necessary.
if (code <= BROTLI_PANIC) {
throw new IllegalStateException("Brotli error code: " + code);
}
throw new BrotliRuntimeException("Error code: " + code);
}
}