mirror of
https://github.com/google/brotli.git
synced 2026-07-09 02:08:06 +00:00
modify Java decoder in a way it could be transpiled to exception unfriendly languages
PiperOrigin-RevId: 769488037
This commit is contained in:
committed by
Copybara-Service
parent
271be11429
commit
08bdaebbf8
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
47
java/org/brotli/dec/BrotliError.java
Normal file
47
java/org/brotli/dec/BrotliError.java
Normal 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;
|
||||
}
|
||||
@@ -111,6 +111,7 @@ public class BrotliInputStream extends InputStream {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
Decode.close(state);
|
||||
Utils.closeInput(state);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user