Merge pull request #442 from eustas/master

Add Java port of Brotli decoder.
This commit is contained in:
Eugene Kliuchnikov 2016-10-17 14:17:57 +02:00 committed by GitHub
commit 616ed51e6e
26 changed files with 4904 additions and 1 deletions

View File

@ -1,4 +1,9 @@
# Description:
# Bazel workspace file for Brotli.
workspace(name = "io_brotli")
workspace(name = "org_brotli")
maven_jar(
name = "junit_junit",
artifact = "junit:junit:4.12",
)

51
java/dec/BUILD Executable file
View File

@ -0,0 +1,51 @@
# Description:
# Java port of Brotli decoder.
package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # MIT
java_library(
name = "lib",
srcs = glob(["*.java"], exclude = ["*Test*.java"]),
)
java_library(
name = "test_lib",
srcs = glob(["*Test*.java"]),
deps = [
":lib",
"@junit_junit//jar",
],
testonly = 1,
)
java_test(
name = "BitReaderTest",
test_class = "org.brotli.dec.BitReaderTest",
runtime_deps = [":test_lib"],
)
java_test(
name = "DecodeTest",
test_class = "org.brotli.dec.DecodeTest",
runtime_deps = [":test_lib"],
)
java_test(
name = "DictionaryTest",
test_class = "org.brotli.dec.DictionaryTest",
runtime_deps = [":test_lib"],
)
java_test(
name = "SynthTest",
test_class = "org.brotli.dec.SynthTest",
runtime_deps = [":test_lib"],
)
java_test(
name = "TransformTest",
test_class = "org.brotli.dec.TransformTest",
runtime_deps = [":test_lib"],
)

171
java/dec/BitReader.java Executable file
View File

@ -0,0 +1,171 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
/**
* Bit reading helpers.
*/
class BitReader {
/**
* Input byte buffer, consist of a ring-buffer and a "slack" region where bytes from the start of
* the ring-buffer are copied.
*/
private static final int READ_SIZE = 4096;
private static final int BUF_SIZE = READ_SIZE + 64;
private final ByteBuffer byteBuffer =
ByteBuffer.allocateDirect(BUF_SIZE).order(ByteOrder.LITTLE_ENDIAN);
private final IntBuffer intBuffer = byteBuffer.asIntBuffer();
private final byte[] shadowBuffer = new byte[BUF_SIZE];
private InputStream input;
/**
* Input stream is finished.
*/
private boolean endOfStreamReached;
/**
* Pre-fetched bits.
*/
long accumulator;
/**
* Current bit-reading position in accumulator.
*/
int bitOffset;
/**
* Number of 32-bit integers availabale for reading.
*/
private int available;
/* Number of bytes in unfinished "int" item. */
private int tailBytes = 0;
/**
* Fills up the input buffer.
*
* <p> No-op 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.
*/
// TODO: Split to check and read; move read outside of decoding loop.
static void readMoreInput(BitReader br) {
if (br.available > 9) {
return;
}
if (br.endOfStreamReached) {
if (br.available > 4) {
return;
}
throw new BrotliRuntimeException("No more input");
}
int readOffset = br.intBuffer.position() << 2;
int bytesRead = READ_SIZE - readOffset;
System.arraycopy(br.shadowBuffer, readOffset, br.shadowBuffer, 0, bytesRead);
try {
while (bytesRead < READ_SIZE) {
int len = br.input.read(br.shadowBuffer, bytesRead, READ_SIZE - bytesRead);
if (len == -1) {
br.endOfStreamReached = true;
Utils.fillWithZeroes(br.shadowBuffer, bytesRead, 64);
bytesRead += 64;
br.tailBytes = bytesRead & 3;
break;
}
bytesRead += len;
}
} catch (IOException e) {
throw new BrotliRuntimeException("Failed to read input", e);
}
br.byteBuffer.clear();
br.byteBuffer.put(br.shadowBuffer, 0, bytesRead & 0xFFFC);
br.intBuffer.rewind();
br.available = bytesRead >> 2;
}
static void checkHealth(BitReader br) {
if (!br.endOfStreamReached) {
return;
}
/* When end of stream is reached, we "borrow" up to 64 zeroes to bit reader.
* If compressed stream is valid, then borrowed zeroes should remain unused. */
int valentBytes = (br.available << 2) + ((64 - br.bitOffset) >> 3);
int borrowedBytes = 64 - br.tailBytes;
if (valentBytes != borrowedBytes) {
throw new BrotliRuntimeException("Read after end");
}
}
/**
* Advances the Read buffer by 5 bytes to make room for reading next 24 bits.
*/
static void fillBitWindow(BitReader br) {
if (br.bitOffset >= 32) {
br.accumulator = ((long) br.intBuffer.get() << 32) | (br.accumulator >>> 32);
br.bitOffset -= 32;
br.available--;
}
}
/**
* Reads the specified number of bits from Read Buffer.
*/
static int readBits(BitReader br, int n) {
fillBitWindow(br);
int val = (int) (br.accumulator >>> br.bitOffset) & ((1 << n) - 1);
br.bitOffset += n;
return val;
}
/**
* Initialize bit reader.
*
* <p> Initialisation turns bit reader to a ready state. Also a number of bytes is prefetched to
* accumulator. Because of that this method may block until enough data could be read from input.
*
* @param br BitReader POJO
* @param input data source
*/
static void init(BitReader br, InputStream input) {
if (br.input != null) {
throw new IllegalStateException("Bit reader already has associated input stream");
}
br.input = input;
br.accumulator = 0;
br.intBuffer.position(READ_SIZE >> 2);
br.bitOffset = 64;
br.available = 0;
br.endOfStreamReached = false;
readMoreInput(br);
if (br.available == 0) {
throw new BrotliRuntimeException("Can't initialize reader");
}
fillBitWindow(br);
fillBitWindow(br);
}
static void jumpToByteBoundry(BitReader br) {
int padding = (64 - br.bitOffset) & 7;
if (padding != 0) {
int paddingBits = BitReader.readBits(br, padding);
if (paddingBits != 0) {
throw new BrotliRuntimeException("Corrupted padding bits ");
}
}
}
}

27
java/dec/BitReaderTest.java Executable file
View File

@ -0,0 +1,27 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import java.io.ByteArrayInputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link BitReader}.
*/
@RunWith(JUnit4.class)
public class BitReaderTest {
@Test(expected = BrotliRuntimeException.class)
public void testReadAfterEos() {
BitReader reader = new BitReader();
BitReader.init(reader, new ByteArrayInputStream(new byte[1]));
BitReader.readBits(reader, 9);
BitReader.checkHealth(reader);
}
}

163
java/dec/BrotliInputStream.java Executable file
View File

@ -0,0 +1,163 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import java.io.IOException;
import java.io.InputStream;
/**
* {@link InputStream} decorator that decompresses brotli data.
*
* <p> Not thread-safe.
*/
public class BrotliInputStream extends InputStream {
public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 16384;
/**
* Internal buffer used for efficient byte-by-byte reading.
*/
private byte[] buffer;
/**
* Number of decoded but still unused bytes in internal buffer.
*/
private int remainingBufferBytes;
/**
* Next unused byte offset.
*/
private int bufferOffset;
/**
* Decoder state.
*/
private final State state = new State();
/**
* Creates a {@link InputStream} wrapper that decompresses brotli data.
*
* <p> For byte-by-byte reading ({@link #read()}) internal buffer with
* {@link #DEFAULT_INTERNAL_BUFFER_SIZE} size is allocated and used.
*
* <p> Will block the thread until first kilobyte of data of source is available.
*
* @param source underlying data source
*/
public BrotliInputStream(InputStream source) throws IOException {
this(source, DEFAULT_INTERNAL_BUFFER_SIZE, null);
}
/**
* Creates a {@link InputStream} wrapper that decompresses brotli data.
*
* <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is
* allocated and used.
*
* <p> Will block the thread until first kilobyte of data of source is available.
*
* @param source compressed data source
* @param byteReadBufferSize size of internal buffer used in case of
* byte-by-byte reading
*/
public BrotliInputStream(InputStream source, int byteReadBufferSize) throws IOException {
this(source, byteReadBufferSize, null);
}
/**
* Creates a {@link InputStream} wrapper that decompresses brotli data.
*
* <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is
* allocated and used.
*
* <p> Will block the thread until first kilobyte of data of source is available.
*
* @param source compressed data source
* @param byteReadBufferSize size of internal buffer used in case of
* byte-by-byte reading
* @param customDictionary custom dictionary data; {@code null} if not used
*/
public BrotliInputStream(InputStream source, int byteReadBufferSize,
byte[] customDictionary) throws IOException {
if (byteReadBufferSize <= 0) {
throw new IllegalArgumentException("Bad buffer size:" + byteReadBufferSize);
} else if (source == null) {
throw new IllegalArgumentException("source is null");
}
this.buffer = new byte[byteReadBufferSize];
this.remainingBufferBytes = 0;
this.bufferOffset = 0;
try {
State.setInput(state, source);
} catch (BrotliRuntimeException ex) {
throw new IOException(ex);
}
if (customDictionary != null) {
Decode.setCustomDictionary(state, customDictionary);
}
}
/**
* {@inheritDoc}
*/
@Override
public int read() throws IOException {
try {
if (bufferOffset >= remainingBufferBytes) {
remainingBufferBytes = read(buffer, 0, buffer.length);
bufferOffset = 0;
if (remainingBufferBytes == -1) {
return -1;
}
}
return buffer[bufferOffset++] & 0xFF;
} catch (BrotliRuntimeException ex) {
throw new IOException(ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public int read(byte[] destBuffer, int destOffset, int destLen) throws IOException {
if (destOffset < 0) {
throw new IllegalArgumentException("Bad offset: " + destOffset);
} else if (destLen < 0) {
throw new IllegalArgumentException("Bad length: " + destLen);
} else if (destOffset + destLen > destBuffer.length) {
throw new IllegalArgumentException(
"Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.length);
} else if (destLen == 0) {
return 0;
}
int copyLen = Math.max(remainingBufferBytes - bufferOffset, 0);
if (copyLen != 0) {
copyLen = Math.min(copyLen, destLen);
System.arraycopy(buffer, bufferOffset, destBuffer, destOffset, copyLen);
bufferOffset += copyLen;
destOffset += copyLen;
destLen -= copyLen;
if (destLen == 0) {
return copyLen;
}
}
try {
state.output = destBuffer;
state.outputOffset = destOffset;
state.outputLength = destLen;
state.outputUsed = 0;
Decode.decompress(state);
if (state.outputUsed == 0) {
return -1;
}
return state.outputUsed + copyLen;
} catch (BrotliRuntimeException ex) {
throw new IOException(ex);
}
}
}

View File

@ -0,0 +1,25 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Unchecked exception used internally.
*/
class BrotliRuntimeException extends RuntimeException {
BrotliRuntimeException(String message) {
super(message);
}
BrotliRuntimeException(String message, Throwable cause) {
super(message, cause);
}
BrotliRuntimeException(Throwable cause) {
super(cause);
}
}

162
java/dec/Context.java Executable file
View File

@ -0,0 +1,162 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Common context lookup table for all context modes.
*/
final class Context {
static final int[] LOOKUP = {
// CONTEXT_UTF8, last byte.
// ASCII range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 4, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8, 12, 16, 12, 12, 20, 12, 16, 24, 28, 12, 12, 32, 12, 36, 12,
44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 32, 32, 24, 40, 28, 12,
12, 48, 52, 52, 52, 48, 52, 52, 52, 48, 52, 52, 52, 52, 52, 48,
52, 52, 52, 52, 52, 48, 52, 52, 52, 52, 52, 24, 12, 28, 12, 12,
12, 56, 60, 60, 60, 56, 60, 60, 60, 56, 60, 60, 60, 60, 60, 56,
60, 60, 60, 60, 60, 56, 60, 60, 60, 60, 60, 24, 12, 28, 12, 0,
// UTF8 continuation byte range.
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
// UTF8 lead byte range.
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
// CONTEXT_UTF8 second last byte.
// ASCII range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 0,
// UTF8 continuation byte range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// UTF8 lead byte range.
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// CONTEXT_SIGNED, second last byte.
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7,
// CONTEXT_SIGNED, last byte, same as the above values shifted by 3 bits.
0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 56,
// CONTEXT_LSB6, last byte.
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
// CONTEXT_MSB6, last byte.
0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63,
// CONTEXT_{M,L}SB6, second last byte,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static final int[] LOOKUP_OFFSETS = {
// CONTEXT_LSB6
1024, 1536,
// CONTEXT_MSB6
1280, 1536,
// CONTEXT_UTF8
0, 256,
// CONTEXT_SIGNED
768, 512
};
}

835
java/dec/Decode.java Executable file
View File

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

186
java/dec/DecodeTest.java Executable file
View File

@ -0,0 +1,186 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import static org.junit.Assert.assertArrayEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link Decode}.
*/
@RunWith(JUnit4.class)
public class DecodeTest {
private byte[] decompress(byte[] data, boolean byByte) throws IOException {
byte[] buffer = new byte[65536];
ByteArrayInputStream input = new ByteArrayInputStream(data);
ByteArrayOutputStream output = new ByteArrayOutputStream();
InputStream brotliInput = new BrotliInputStream(input);
if (byByte) {
while (true) {
int next = brotliInput.read();
if (next == -1) {
break;
}
output.write(next);
}
} else {
while (true) {
int len = brotliInput.read(buffer);
if (len <= 0) {
break;
}
output.write(buffer, 0, len);
}
}
brotliInput.close();
return output.toByteArray();
}
private byte[] decompressWithDictionary(byte[] data, byte[] dictionary) throws IOException {
byte[] buffer = new byte[65536];
ByteArrayInputStream input = new ByteArrayInputStream(data);
ByteArrayOutputStream output = new ByteArrayOutputStream();
InputStream brotliInput = new BrotliInputStream(
input, BrotliInputStream.DEFAULT_INTERNAL_BUFFER_SIZE, dictionary);
while (true) {
int len = brotliInput.read(buffer);
if (len <= 0) {
break;
}
output.write(buffer, 0, len);
}
brotliInput.close();
return output.toByteArray();
}
private void checkDecodeResourceWithDictionary(String expected, String compressed,
String dictionary) throws IOException {
byte[] expectedBytes = Transform.readUniBytes(expected);
byte[] compressedBytes = Transform.readUniBytes(compressed);
byte[] dictionaryBytes = Transform.readUniBytes(dictionary);
byte[] actual = decompressWithDictionary(compressedBytes, dictionaryBytes);
assertArrayEquals(expectedBytes, actual);
}
private void checkDecodeResource(String expected, String compressed) throws IOException {
byte[] expectedBytes = Transform.readUniBytes(expected);
byte[] compressedBytes = Transform.readUniBytes(compressed);
byte[] actual = decompress(compressedBytes, false);
assertArrayEquals(expectedBytes, actual);
byte[] actualByByte = decompress(compressedBytes, true);
assertArrayEquals(expectedBytes, actualByByte);
}
@Test
public void testEmpty() throws IOException {
checkDecodeResource(
"",
"\u0006");
}
@Test
public void testX() throws IOException {
checkDecodeResource(
"X",
"\u000B\u0000\u0080X\u0003");
}
@Test
public void testX10Y10() throws IOException {
checkDecodeResource(
"XXXXXXXXXXYYYYYYYYYY",
"\u001B\u0013\u0000\u0000\u00A4\u00B0\u00B2\u00EA\u0081G\u0002\u008A");
}
@Test
public void testX64() throws IOException {
checkDecodeResource(
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"\u001B\u003F\u0000\u0000$\u00B0\u00E2\u0099\u0080\u0012");
}
@Test
public void testUkkonooa() throws IOException {
checkDecodeResource(
"ukko nooa, ukko nooa oli kunnon mies, kun han meni saunaan, "
+ "pisti laukun naulaan, ukko nooa, ukko nooa oli kunnon mies.",
"\u001Bv\u0000\u0000\u0014J\u00AC\u009Bz\u00BD\u00E1\u0097\u009D\u007F\u008E\u00C2\u0082"
+ "6\u000E\u009C\u00E0\u0090\u0003\u00F7\u008B\u009E8\u00E6\u00B6\u0000\u00AB\u00C3\u00CA"
+ "\u00A0\u00C2\u00DAf6\u00DC\u00CD\u0080\u008D.!\u00D7n\u00E3\u00EAL\u00B8\u00F0\u00D2"
+ "\u00B8\u00C7\u00C2pM:\u00F0i~\u00A1\u00B8Es\u00AB\u00C4W\u001E");
}
@Test
public void testMonkey() throws IOException {
checkDecodeResource(
"znxcvnmz,xvnm.,zxcnv.,xcn.z,vn.zvn.zxcvn.,zxcn.vn.v,znm.,vnzx.,vnzxc.vn.z,vnz.,nv.z,nvmz"
+ "xc,nvzxcvcnm.,vczxvnzxcnvmxc.zmcnvzm.,nvmc,nzxmc,vn.mnnmzxc,vnxcnmv,znvzxcnmv,.xcnvm,zxc"
+ "nzxv.zx,qweryweurqioweupropqwutioweupqrioweutiopweuriopweuriopqwurioputiopqwuriowuqeriou"
+ "pqweropuweropqwurweuqriopuropqwuriopuqwriopuqweopruioqweurqweuriouqweopruioupqiytioqtyio"
+ "wtyqptypryoqweutioioqtweqruowqeytiowquiourowetyoqwupiotweuqiorweuqroipituqwiorqwtioweuri"
+ "ouytuioerytuioweryuitoweytuiweyuityeruirtyuqriqweuropqweiruioqweurioqwuerioqwyuituierwot"
+ "ueryuiotweyrtuiwertyioweryrueioqptyioruyiopqwtjkasdfhlafhlasdhfjklashjkfhasjklfhklasjdfh"
+ "klasdhfjkalsdhfklasdhjkflahsjdkfhklasfhjkasdfhasfjkasdhfklsdhalghhaf;hdklasfhjklashjklfa"
+ "sdhfasdjklfhsdjklafsd;hkldadfjjklasdhfjasddfjklfhakjklasdjfkl;asdjfasfljasdfhjklasdfhjka"
+ "ghjkashf;djfklasdjfkljasdklfjklasdjfkljasdfkljaklfj",
"\u001BJ\u0003\u0000\u008C\u0094n\u00DE\u00B4\u00D7\u0096\u00B1x\u0086\u00F2-\u00E1\u001A"
+ "\u00BC\u000B\u001C\u00BA\u00A9\u00C7\u00F7\u00CCn\u00B2B4QD\u008BN\u0013\b\u00A0\u00CDn"
+ "\u00E8,\u00A5S\u00A1\u009C],\u001D#\u001A\u00D2V\u00BE\u00DB\u00EB&\u00BA\u0003e|\u0096j"
+ "\u00A2v\u00EC\u00EF\u0087G3\u00D6\'\u000Ec\u0095\u00E2\u001D\u008D,\u00C5\u00D1(\u009F`"
+ "\u0094o\u0002\u008B\u00DD\u00AAd\u0094,\u001E;e|\u0007EZ\u00B2\u00E2\u00FCI\u0081,\u009F"
+ "@\u00AE\u00EFh\u0081\u00AC\u0016z\u000F\u00F5;m\u001C\u00B9\u001E-_\u00D5\u00C8\u00AF^"
+ "\u0085\u00AA\u0005\u00BESu\u00C2\u00B0\"\u008A\u0015\u00C6\u00A3\u00B1\u00E6B\u0014"
+ "\u00F4\u0084TS\u0019_\u00BE\u00C3\u00F2\u001D\u00D1\u00B7\u00E5\u00DD\u00B6\u00D9#\u00C6"
+ "\u00F6\u009F\u009E\u00F6Me0\u00FB\u00C0qE\u0004\u00AD\u0003\u00B5\u00BE\u00C9\u00CB"
+ "\u00FD\u00E2PZFt\u0004\r\u00FF \u0004w\u00B2m\'\u00BFG\u00A9\u009D\u001B\u0096,b\u0090#"
+ "\u008B\u00E0\u00F8\u001D\u00CF\u00AF\u001D=\u00EE\u008A\u00C8u#f\u00DD\u00DE\u00D6m"
+ "\u00E3*\u0082\u008Ax\u008A\u00DB\u00E6 L\u00B7\\c\u00BA0\u00E3?\u00B6\u00EE\u008C\""
+ "\u00A2*\u00B0\"\n\u0099\u00FF=bQ\u00EE\b\u00F6=J\u00E4\u00CC\u00EF\"\u0087\u0011\u00E2"
+ "\u0083(\u00E4\u00F5\u008F5\u0019c[\u00E1Z\u0092s\u00DD\u00A1P\u009D8\\\u00EB\u00B5\u0003"
+ "jd\u0090\u0094\u00C8\u008D\u00FB/\u008A\u0086\"\u00CC\u001D\u0087\u00E0H\n\u0096w\u00909"
+ "\u00C6##H\u00FB\u0011GV\u00CA \u00E3B\u0081\u00F7w2\u00C1\u00A5\\@!e\u0017@)\u0017\u0017"
+ "lV2\u00988\u0006\u00DC\u0099M3)\u00BB\u0002\u00DFL&\u0093l\u0017\u0082\u0086 \u00D7"
+ "\u0003y}\u009A\u0000\u00D7\u0087\u0000\u00E7\u000Bf\u00E3Lfqg\b2\u00F9\b>\u00813\u00CD"
+ "\u0017r1\u00F0\u00B8\u0094RK\u00901\u008Eh\u00C1\u00EF\u0090\u00C9\u00E5\u00F2a\tr%"
+ "\u00AD\u00EC\u00C5b\u00C0\u000B\u0012\u0005\u00F7\u0091u\r\u00EEa..\u0019\t\u00C2\u0003"
);
}
@Test
public void testFox() throws IOException {
checkDecodeResource(
"The quick brown fox jumps over the lazy dog",
"\u001B*\u0000\u0000\u0004\u0004\u00BAF:\u0085\u0003\u00E9\u00FA\f\u0091\u0002H\u0011,"
+ "\u00F3\u008A:\u00A3V\u007F\u001A\u00AE\u00BF\u00A4\u00AB\u008EM\u00BF\u00ED\u00E2\u0004K"
+ "\u0091\u00FF\u0087\u00E9\u001E");
}
@Test
public void testFoxFox() throws IOException {
checkDecodeResourceWithDictionary(
"The quick brown fox jumps over the lazy dog",
"\u001B*\u0000\u0000 \u0000\u00C2\u0098\u00B0\u00CA\u0001",
"The quick brown fox jumps over the lazy dog");
}
@Test
public void testUtils() {
new Context();
new Decode();
new Dictionary();
new Huffman();
new Prefix();
}
}

83
java/dec/Dictionary.java Executable file

File diff suppressed because one or more lines are too long

31
java/dec/DictionaryTest.java Executable file
View File

@ -0,0 +1,31 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import static org.junit.Assert.assertEquals;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link Dictionary}.
*/
@RunWith(JUnit4.class)
public class DictionaryTest {
@Test
public void testGetData() throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(Dictionary.getData());
byte[] digest = md.digest();
String sha256 = String.format("%064x", new java.math.BigInteger(1, digest));
assertEquals("20e42eb1b511c21806d4d227d07e5dd06877d8ce7b3a817f378f313653f35c70", sha256);
}
}

138
java/dec/Huffman.java Executable file
View File

@ -0,0 +1,138 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Utilities for building Huffman decoding tables.
*/
final class Huffman {
/**
* Maximum possible Huffman table size for an alphabet size of 704, max code length 15 and root
* table bits 8.
*/
static final int HUFFMAN_MAX_TABLE_SIZE = 1080;
private static final int MAX_LENGTH = 15;
/**
* Returns reverse(reverse(key, len) + 1, len).
*
* <p> reverse(key, len) is the bit-wise reversal of the len least significant bits of key.
*/
private static int getNextKey(int key, int len) {
int step = 1 << (len - 1);
while ((key & step) != 0) {
step >>= 1;
}
return (key & (step - 1)) + step;
}
/**
* Stores {@code item} in {@code table[0], table[step], table[2 * step] .., table[end]}.
*
* <p> Assumes that end is an integer multiple of step.
*/
private static void replicateValue(int[] table, int offset, int step, int end, int item) {
do {
end -= step;
table[offset + end] = item;
} while (end > 0);
}
/**
* @param count histogram of bit lengths for the remaining symbols,
* @param len code length of the next processed symbol.
* @return table width of the next 2nd level table.
*/
private static int nextTableBitSize(int[] count, int len, int rootBits) {
int left = 1 << (len - rootBits);
while (len < MAX_LENGTH) {
left -= count[len];
if (left <= 0) {
break;
}
len++;
left <<= 1;
}
return len - rootBits;
}
/**
* Builds Huffman lookup table assuming code lengths are in symbol order.
*/
static void buildHuffmanTable(int[] rootTable, int tableOffset, int rootBits, int[] codeLengths,
int codeLengthsSize) {
int key; // Reversed prefix code.
int[] sorted = new int[codeLengthsSize]; // Symbols sorted by code length.
// TODO: fill with zeroes?
int[] count = new int[MAX_LENGTH + 1]; // Number of codes of each length.
int[] offset = new int[MAX_LENGTH + 1]; // Offsets in sorted table for each length.
int symbol;
// Build histogram of code lengths.
for (symbol = 0; symbol < codeLengthsSize; symbol++) {
count[codeLengths[symbol]]++;
}
// Generate offsets into sorted symbol table by code length.
offset[1] = 0;
for (int len = 1; len < MAX_LENGTH; len++) {
offset[len + 1] = offset[len] + count[len];
}
// Sort symbols by length, by symbol order within each length.
for (symbol = 0; symbol < codeLengthsSize; symbol++) {
if (codeLengths[symbol] != 0) {
sorted[offset[codeLengths[symbol]]++] = symbol;
}
}
int tableBits = rootBits;
int tableSize = 1 << tableBits;
int totalSize = tableSize;
// Special case code with only one value.
if (offset[MAX_LENGTH] == 1) {
for (key = 0; key < totalSize; key++) {
rootTable[tableOffset + key] = sorted[0];
}
return;
}
// Fill in root table.
key = 0;
symbol = 0;
for (int len = 1, step = 2; len <= rootBits; len++, step <<= 1) {
for (; count[len] > 0; count[len]--) {
replicateValue(rootTable, tableOffset + key, step, tableSize, len << 16 | sorted[symbol++]);
key = getNextKey(key, len);
}
}
// Fill in 2nd level tables and add pointers to root table.
int mask = totalSize - 1;
int low = -1;
int currentOffset = tableOffset;
for (int len = rootBits + 1, step = 2; len <= MAX_LENGTH; len++, step <<= 1) {
for (; count[len] > 0; count[len]--) {
if ((key & mask) != low) {
currentOffset += tableSize;
tableBits = nextTableBitSize(count, len, rootBits);
tableSize = 1 << tableBits;
totalSize += tableSize;
low = key & mask;
rootTable[tableOffset + low] =
(tableBits + rootBits) << 16 | (currentOffset - tableOffset - low);
}
replicateValue(rootTable, currentOffset + (key >> rootBits), step, tableSize,
(len - rootBits) << 16 | sorted[symbol++]);
key = getNextKey(key, len);
}
}
}
}

57
java/dec/HuffmanTreeGroup.java Executable file
View File

@ -0,0 +1,57 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Contains a collection of huffman trees with the same alphabet size.
*/
final class HuffmanTreeGroup {
/**
* The maximal alphabet size in this group.
*/
private int alphabetSize;
/**
* Storage for Huffman lookup tables.
*/
int[] codes;
/**
* Offsets of distinct lookup tables in {@link #codes} storage.
*/
int[] trees;
/**
* Initializes the Huffman tree group.
*
* @param group POJO to be initialised
* @param alphabetSize the maximal alphabet size in this group
* @param n number of Huffman codes
*/
static void init(HuffmanTreeGroup group, int alphabetSize, int n) {
group.alphabetSize = alphabetSize;
group.codes = new int[n * Huffman.HUFFMAN_MAX_TABLE_SIZE];
group.trees = new int[n];
}
/**
* Decodes Huffman trees from input stream and constructs lookup tables.
*
* @param group target POJO
* @param br data source
*/
static void decode(HuffmanTreeGroup group, BitReader br) {
int next = 0;
int n = group.trees.length;
for (int i = 0; i < n; i++) {
group.trees[i] = next;
Decode.readHuffmanCode(group.alphabetSize, group.codes, next, br);
next += Huffman.HUFFMAN_MAX_TABLE_SIZE;
}
}
}

53
java/dec/Prefix.java Executable file
View File

@ -0,0 +1,53 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Lookup tables to map prefix codes to value ranges.
*
* <p> This is used during decoding of the block lengths, literal insertion lengths and copy
* lengths.
*
* <p> Range represents values: [offset, offset + 2 ^ n_bits)
*/
final class Prefix {
static final int[] BLOCK_LENGTH_OFFSET = {
1, 5, 9, 13, 17, 25, 33, 41, 49, 65, 81, 97, 113, 145, 177, 209, 241, 305, 369, 497,
753, 1265, 2289, 4337, 8433, 16625
};
static final int[] BLOCK_LENGTH_N_BITS = {
2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 24
};
static final int[] INSERT_LENGTH_OFFSET = {
0, 1, 2, 3, 4, 5, 6, 8, 10, 14, 18, 26, 34, 50, 66, 98, 130, 194, 322, 578, 1090, 2114, 6210,
22594
};
static final int[] INSERT_LENGTH_N_BITS = {
0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 12, 14, 24
};
static final int[] COPY_LENGTH_OFFSET = {
2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 18, 22, 30, 38, 54, 70, 102, 134, 198, 326, 582, 1094,
2118
};
static final int[] COPY_LENGTH_N_BITS = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 24
};
static final int[] INSERT_RANGE_LUT = {
0, 0, 8, 8, 0, 16, 8, 16, 16
};
static final int[] COPY_RANGE_LUT = {
0, 8, 0, 8, 16, 0, 16, 8, 16
};
}

25
java/dec/RunningState.java Executable file
View File

@ -0,0 +1,25 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Enumeration of decoding state-machine.
*/
enum RunningState {
UNINITIALIZED,
BLOCK_START,
COMPRESSED_BLOCK_START,
MAIN_LOOP,
READ_METADATA,
COPY_UNCOMPRESSED,
INSERT_LOOP,
COPY_LOOP,
COPY_WRAP_BUFFER,
TRANSFORM,
FINISHED,
WRITE
}

107
java/dec/State.java Executable file
View File

@ -0,0 +1,107 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import static org.brotli.dec.RunningState.BLOCK_START;
import static org.brotli.dec.RunningState.UNINITIALIZED;
import java.io.InputStream;
final class State {
RunningState runningState = UNINITIALIZED;
RunningState nextRunningState;
final BitReader br = new BitReader();
byte[] ringBuffer;
final int[] blockTypeTrees = new int[3 * Huffman.HUFFMAN_MAX_TABLE_SIZE];
final int[] blockLenTrees = new int[3 * Huffman.HUFFMAN_MAX_TABLE_SIZE];
// Current meta-block header information.
int metaBlockLength;
boolean inputEnd;
boolean isUncompressed;
boolean isMetadata;
final HuffmanTreeGroup hGroup0 = new HuffmanTreeGroup();
final HuffmanTreeGroup hGroup1 = new HuffmanTreeGroup();
final HuffmanTreeGroup hGroup2 = new HuffmanTreeGroup();
final int[] blockLength = new int[3];
final int[] numBlockTypes = new int[3];
final int[] blockTypeRb = new int[6];
final int[] distRb = {16, 15, 11, 4};
int pos = 0;
int maxDistance = 0;
int distRbIdx = 0;
boolean trivialLiteralContext = false;
int literalTreeIndex = 0;
int literalTree;
int j;
int insertLength;
byte[] contextModes;
byte[] contextMap;
int contextMapSlice;
int distContextMapSlice;
int contextLookupOffset1;
int contextLookupOffset2;
int treeCommandOffset;
int distanceCode;
byte[] distContextMap;
int numDirectDistanceCodes;
int distancePostfixMask;
int distancePostfixBits;
int distance;
int copyLength;
int copyDst;
int maxBackwardDistance;
int maxRingBufferSize;
int ringBufferSize = 0;
long expectedTotalSize = 0;
byte[] customDictionary = new byte[0];
int bytesToIgnore = 0;
int outputOffset;
int outputLength;
int outputUsed;
int bytesWritten;
int bytesToWrite;
byte[] output;
// TODO: Update to current spec.
private static int decodeWindowBits(BitReader br) {
if (BitReader.readBits(br, 1) == 0) {
return 16;
}
int n = BitReader.readBits(br, 3);
if (n != 0) {
return 17 + n;
}
n = BitReader.readBits(br, 3);
if (n != 0) {
return 8 + n;
}
return 17;
}
/**
* Associate input with decoder state.
*
* @param state uninitialized state without associated input
* @param input compressed data source
*/
static void setInput(State state, InputStream input) {
if (state.runningState != UNINITIALIZED) {
throw new IllegalStateException("State is MUST be uninitialized");
}
BitReader.init(state.br, input);
int windowBits = decodeWindowBits(state.br);
if (windowBits == 9) { /* Reserved case for future expansion. */
throw new BrotliRuntimeException("Invalid 'windowBits' code");
}
state.maxRingBufferSize = 1 << windowBits;
state.maxBackwardDistance = state.maxRingBufferSize - 16;
state.runningState = BLOCK_START;
}
}

2131
java/dec/SynthTest.java Executable file

File diff suppressed because it is too large Load Diff

240
java/dec/Transform.java Executable file
View File

@ -0,0 +1,240 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import static org.brotli.dec.WordTransformType.IDENTITY;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_1;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_2;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_3;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_4;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_5;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_6;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_7;
import static org.brotli.dec.WordTransformType.OMIT_FIRST_9;
import static org.brotli.dec.WordTransformType.OMIT_LAST_1;
import static org.brotli.dec.WordTransformType.OMIT_LAST_2;
import static org.brotli.dec.WordTransformType.OMIT_LAST_3;
import static org.brotli.dec.WordTransformType.OMIT_LAST_4;
import static org.brotli.dec.WordTransformType.OMIT_LAST_5;
import static org.brotli.dec.WordTransformType.OMIT_LAST_6;
import static org.brotli.dec.WordTransformType.OMIT_LAST_7;
import static org.brotli.dec.WordTransformType.OMIT_LAST_8;
import static org.brotli.dec.WordTransformType.OMIT_LAST_9;
import static org.brotli.dec.WordTransformType.UPPERCASE_ALL;
import static org.brotli.dec.WordTransformType.UPPERCASE_FIRST;
/**
* Transformations on dictionary words.
*/
final class Transform {
private final byte[] prefix;
private final WordTransformType type;
private final byte[] suffix;
Transform(String prefix, WordTransformType type, String suffix) {
this.prefix = readUniBytes(prefix);
this.type = type;
this.suffix = readUniBytes(suffix);
}
static byte[] readUniBytes(String uniBytes) {
byte[] result = new byte[uniBytes.length()];
for (int i = 0; i < result.length; ++i) {
result[i] = (byte) uniBytes.charAt(i);
}
return result;
}
static final Transform[] TRANSFORMS = {
new Transform("", IDENTITY, ""),
new Transform("", IDENTITY, " "),
new Transform(" ", IDENTITY, " "),
new Transform("", OMIT_FIRST_1, ""),
new Transform("", UPPERCASE_FIRST, " "),
new Transform("", IDENTITY, " the "),
new Transform(" ", IDENTITY, ""),
new Transform("s ", IDENTITY, " "),
new Transform("", IDENTITY, " of "),
new Transform("", UPPERCASE_FIRST, ""),
new Transform("", IDENTITY, " and "),
new Transform("", OMIT_FIRST_2, ""),
new Transform("", OMIT_LAST_1, ""),
new Transform(", ", IDENTITY, " "),
new Transform("", IDENTITY, ", "),
new Transform(" ", UPPERCASE_FIRST, " "),
new Transform("", IDENTITY, " in "),
new Transform("", IDENTITY, " to "),
new Transform("e ", IDENTITY, " "),
new Transform("", IDENTITY, "\""),
new Transform("", IDENTITY, "."),
new Transform("", IDENTITY, "\">"),
new Transform("", IDENTITY, "\n"),
new Transform("", OMIT_LAST_3, ""),
new Transform("", IDENTITY, "]"),
new Transform("", IDENTITY, " for "),
new Transform("", OMIT_FIRST_3, ""),
new Transform("", OMIT_LAST_2, ""),
new Transform("", IDENTITY, " a "),
new Transform("", IDENTITY, " that "),
new Transform(" ", UPPERCASE_FIRST, ""),
new Transform("", IDENTITY, ". "),
new Transform(".", IDENTITY, ""),
new Transform(" ", IDENTITY, ", "),
new Transform("", OMIT_FIRST_4, ""),
new Transform("", IDENTITY, " with "),
new Transform("", IDENTITY, "'"),
new Transform("", IDENTITY, " from "),
new Transform("", IDENTITY, " by "),
new Transform("", OMIT_FIRST_5, ""),
new Transform("", OMIT_FIRST_6, ""),
new Transform(" the ", IDENTITY, ""),
new Transform("", OMIT_LAST_4, ""),
new Transform("", IDENTITY, ". The "),
new Transform("", UPPERCASE_ALL, ""),
new Transform("", IDENTITY, " on "),
new Transform("", IDENTITY, " as "),
new Transform("", IDENTITY, " is "),
new Transform("", OMIT_LAST_7, ""),
new Transform("", OMIT_LAST_1, "ing "),
new Transform("", IDENTITY, "\n\t"),
new Transform("", IDENTITY, ":"),
new Transform(" ", IDENTITY, ". "),
new Transform("", IDENTITY, "ed "),
new Transform("", OMIT_FIRST_9, ""),
new Transform("", OMIT_FIRST_7, ""),
new Transform("", OMIT_LAST_6, ""),
new Transform("", IDENTITY, "("),
new Transform("", UPPERCASE_FIRST, ", "),
new Transform("", OMIT_LAST_8, ""),
new Transform("", IDENTITY, " at "),
new Transform("", IDENTITY, "ly "),
new Transform(" the ", IDENTITY, " of "),
new Transform("", OMIT_LAST_5, ""),
new Transform("", OMIT_LAST_9, ""),
new Transform(" ", UPPERCASE_FIRST, ", "),
new Transform("", UPPERCASE_FIRST, "\""),
new Transform(".", IDENTITY, "("),
new Transform("", UPPERCASE_ALL, " "),
new Transform("", UPPERCASE_FIRST, "\">"),
new Transform("", IDENTITY, "=\""),
new Transform(" ", IDENTITY, "."),
new Transform(".com/", IDENTITY, ""),
new Transform(" the ", IDENTITY, " of the "),
new Transform("", UPPERCASE_FIRST, "'"),
new Transform("", IDENTITY, ". This "),
new Transform("", IDENTITY, ","),
new Transform(".", IDENTITY, " "),
new Transform("", UPPERCASE_FIRST, "("),
new Transform("", UPPERCASE_FIRST, "."),
new Transform("", IDENTITY, " not "),
new Transform(" ", IDENTITY, "=\""),
new Transform("", IDENTITY, "er "),
new Transform(" ", UPPERCASE_ALL, " "),
new Transform("", IDENTITY, "al "),
new Transform(" ", UPPERCASE_ALL, ""),
new Transform("", IDENTITY, "='"),
new Transform("", UPPERCASE_ALL, "\""),
new Transform("", UPPERCASE_FIRST, ". "),
new Transform(" ", IDENTITY, "("),
new Transform("", IDENTITY, "ful "),
new Transform(" ", UPPERCASE_FIRST, ". "),
new Transform("", IDENTITY, "ive "),
new Transform("", IDENTITY, "less "),
new Transform("", UPPERCASE_ALL, "'"),
new Transform("", IDENTITY, "est "),
new Transform(" ", UPPERCASE_FIRST, "."),
new Transform("", UPPERCASE_ALL, "\">"),
new Transform(" ", IDENTITY, "='"),
new Transform("", UPPERCASE_FIRST, ","),
new Transform("", IDENTITY, "ize "),
new Transform("", UPPERCASE_ALL, "."),
new Transform("\u00c2\u00a0", IDENTITY, ""),
new Transform(" ", IDENTITY, ","),
new Transform("", UPPERCASE_FIRST, "=\""),
new Transform("", UPPERCASE_ALL, "=\""),
new Transform("", IDENTITY, "ous "),
new Transform("", UPPERCASE_ALL, ", "),
new Transform("", UPPERCASE_FIRST, "='"),
new Transform(" ", UPPERCASE_FIRST, ","),
new Transform(" ", UPPERCASE_ALL, "=\""),
new Transform(" ", UPPERCASE_ALL, ", "),
new Transform("", UPPERCASE_ALL, ","),
new Transform("", UPPERCASE_ALL, "("),
new Transform("", UPPERCASE_ALL, ". "),
new Transform(" ", UPPERCASE_ALL, "."),
new Transform("", UPPERCASE_ALL, "='"),
new Transform(" ", UPPERCASE_ALL, ". "),
new Transform(" ", UPPERCASE_FIRST, "=\""),
new Transform(" ", UPPERCASE_ALL, "='"),
new Transform(" ", UPPERCASE_FIRST, "='")
};
static int transformDictionaryWord(byte[] dst, int dstOffset, byte[] word, int wordOffset,
int len, Transform transform) {
int offset = dstOffset;
// Copy prefix.
byte[] string = transform.prefix;
int tmp = string.length;
int i = 0;
// In most cases tmp < 10 -> no benefits from System.arrayCopy
while (i < tmp) {
dst[offset++] = string[i++];
}
// Copy trimmed word.
WordTransformType op = transform.type;
tmp = op.omitFirst;
if (tmp > len) {
tmp = len;
}
wordOffset += tmp;
len -= tmp;
len -= op.omitLast;
i = len;
while (i > 0) {
dst[offset++] = word[wordOffset++];
i--;
}
if (op == UPPERCASE_ALL || op == UPPERCASE_FIRST) {
int uppercaseOffset = offset - len;
if (op == UPPERCASE_FIRST) {
len = 1;
}
while (len > 0) {
tmp = dst[uppercaseOffset] & 0xFF;
if (tmp < 0xc0) {
if (tmp >= 'a' && tmp <= 'z') {
dst[uppercaseOffset] ^= (byte) 32;
}
uppercaseOffset += 1;
len -= 1;
} else if (tmp < 0xe0) {
dst[uppercaseOffset + 1] ^= (byte) 32;
uppercaseOffset += 2;
len -= 2;
} else {
dst[uppercaseOffset + 2] ^= (byte) 5;
uppercaseOffset += 3;
len -= 3;
}
}
}
// Copy suffix.
string = transform.suffix;
tmp = string.length;
i = 0;
while (i < tmp) {
dst[offset++] = string[i++];
}
return offset - dstOffset;
}
}

63
java/dec/TransformTest.java Executable file
View File

@ -0,0 +1,63 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link Transform}.
*/
@RunWith(JUnit4.class)
public class TransformTest {
@Test
public void testTrimAll() {
byte[] output = new byte[2];
byte[] input = "word".getBytes(StandardCharsets.UTF_8);
Transform transform = new Transform("[", WordTransformType.OMIT_FIRST_5, "]");
Transform.transformDictionaryWord(output, 0, input, 0, input.length, transform);
assertArrayEquals(output, "[]".getBytes(StandardCharsets.UTF_8));
}
@Test
public void testCapitalize() {
byte[] output = new byte[8];
byte[] input = "qæप".getBytes(StandardCharsets.UTF_8);
Transform transform = new Transform("[", WordTransformType.UPPERCASE_ALL, "]");
Transform.transformDictionaryWord(output, 0, input, 0, input.length, transform);
assertArrayEquals(output, "[QÆय]".getBytes(StandardCharsets.UTF_8));
}
@Test
public void testAllTransforms() throws NoSuchAlgorithmException {
/* This string allows to apply all transforms: head and tail cutting, capitalization and
turning to upper case; all results will be mutually different. */
byte[] testWord = Transform.readUniBytes("o123456789abcdef");
byte[] output = new byte[2259];
int offset = 0;
for (int i = 0; i < Transform.TRANSFORMS.length; ++i) {
offset += Transform.transformDictionaryWord(
output, offset, testWord, 0, testWord.length, Transform.TRANSFORMS[i]);
output[offset++] = -1;
}
assertEquals(output.length, offset);
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(output);
byte[] digest = md.digest();
String sha256 = String.format("%064x", new java.math.BigInteger(1, digest));
assertEquals("60f1c7e45d788e24938c5a3919aaf41a7d8ad474d0ced6b9e4c0079f4d1da8c4", sha256);
}
}

55
java/dec/Utils.java Executable file
View File

@ -0,0 +1,55 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* A set of utility methods.
*/
final class Utils {
private static final byte[] BYTE_ZEROES = new byte[1024];
private static final int[] INT_ZEROES = new int[1024];
/**
* Fills byte array with zeroes.
*
* <p> Current implementation uses {@link System#arraycopy}, so it should be used for length not
* less than 16.
*
* @param dest array to fill with zeroes
* @param offset the first byte to fill
* @param length number of bytes to change
*/
static void fillWithZeroes(byte[] dest, int offset, int length) {
int cursor = 0;
while (cursor < length) {
int step = Math.min(cursor + 1024, length) - cursor;
System.arraycopy(BYTE_ZEROES, 0, dest, offset + cursor, step);
cursor += step;
}
}
/**
* Fills int array with zeroes.
*
* <p> Current implementation uses {@link System#arraycopy}, so it should be used for length not
* less than 16.
*
* @param dest array to fill with zeroes
* @param offset the first item to fill
* @param length number of item to change
*/
static void fillWithZeroes(int[] dest, int offset, int length) {
int cursor = 0;
while (cursor < length) {
int step = Math.min(cursor + 1024, length) - cursor;
System.arraycopy(INT_ZEROES, 0, dest, offset + cursor, step);
cursor += step;
}
}
}

48
java/dec/WordTransformType.java Executable file
View File

@ -0,0 +1,48 @@
/* Copyright 2015 Google Inc. All Rights Reserved.
Distributed under MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
*/
package org.brotli.dec;
/**
* Enumeration of all possible word transformations.
*
* <p>There are two simple types of transforms: omit X first/last symbols, two character-case
* transforms and the identity transform.
*/
enum WordTransformType {
IDENTITY(0, 0),
OMIT_LAST_1(0, 1),
OMIT_LAST_2(0, 2),
OMIT_LAST_3(0, 3),
OMIT_LAST_4(0, 4),
OMIT_LAST_5(0, 5),
OMIT_LAST_6(0, 6),
OMIT_LAST_7(0, 7),
OMIT_LAST_8(0, 8),
OMIT_LAST_9(0, 9),
UPPERCASE_FIRST(0, 0),
UPPERCASE_ALL(0, 0),
OMIT_FIRST_1(1, 0),
OMIT_FIRST_2(2, 0),
OMIT_FIRST_3(3, 0),
OMIT_FIRST_4(4, 0),
OMIT_FIRST_5(5, 0),
OMIT_FIRST_6(6, 0),
OMIT_FIRST_7(7, 0),
/*
* brotli specification doesn't use OMIT_FIRST_8(8, 0) transform.
* Probably, it would be used in future format extensions.
*/
OMIT_FIRST_9(9, 0);
final int omitFirst;
final int omitLast;
WordTransformType(int omitFirst, int omitLast) {
this.omitFirst = omitFirst;
this.omitLast = omitLast;
}
}

46
java/dec/pom.xml Executable file
View File

@ -0,0 +1,46 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>dec</name>
<url>http://brotli.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>..</sourceDirectory>
<testSourceDirectory>..</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
<excludes>
<exclude>**/*Test*.java</exclude>
</excludes>
<testIncludes>
<include>**/*Test*.java</include>
</testIncludes>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

23
java/integration/BUILD Executable file
View File

@ -0,0 +1,23 @@
# Description:
# Integration test runner + corpus for Java port of Brotli decoder.
java_library(
name = "bundle_checker_lib",
srcs = ["BundleChecker.java"],
deps = ["//java/dec:lib"],
)
java_binary(
name = "bundle_checker",
main_class = "org.brotli.integration.BundleChecker",
runtime_deps = [":bundle_checker_lib"],
)
java_test(
name = "bundle_checker_test",
args = ["java/integration/test_data.zip"],
data = ["test_data.zip"],
main_class = "org.brotli.integration.BundleChecker",
use_testrunner = 0,
runtime_deps = [":bundle_checker_lib"],
)

View File

@ -0,0 +1,120 @@
/* Copyright 2016 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.integration;
import org.brotli.dec.BrotliInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Decompress files and checks thier checksums.
*
* <p> File are read from ZIP archive passed as an array of bytes. Multiple checkers negotiate about
* task distribution via shared AtomicInteger counter.
* <p> All entries are expected to be valid brotli compressed streams and output CRC64 checksum
* is expected to match the checksum hex-encoded in the first part of entry name.
*/
public class BundleChecker implements Runnable {
final AtomicInteger nextJob;
final InputStream input;
public BundleChecker(InputStream input, AtomicInteger nextJob) {
this.input = input;
this.nextJob = nextJob;
}
/** ECMA CRC64 polynomial. */
static final long CRC_64_POLY = new BigInteger("C96C5795D7870F42", 16).longValue();
/**
* Rolls CRC64 calculation.
*
* <p> {@code CRC64(data) = -1 ^ updateCrc64((... updateCrc64(-1, firstBlock), ...), lastBlock);}
* <p> This simple and reliable checksum is chosen to make is easy to calculate the same value
* across the variety of languages (C++, Java, Go, ...).
*/
private static long updateCrc64(long crc, byte[] data, int offset, int length) {
for (int i = offset; i < offset + length; ++i) {
long c = (crc ^ (long) (data[i] & 0xFF)) & 0xFF;
for (int k = 0; k < 8; k++) {
c = ((c & 1) == 1) ? CRC_64_POLY ^ (c >>> 1) : c >>> 1;
}
crc = c ^ (crc >>> 8);
}
return crc;
}
private long decompressAndCalculateCrc(ZipInputStream input) throws IOException {
/* Do not allow entry readers to close the whole ZipInputStream. */
FilterInputStream entryStream = new FilterInputStream(input) {
@Override
public void close() {}
};
long crc = -1;
byte[] buffer = new byte[65536];
BrotliInputStream decompressedStream = new BrotliInputStream(entryStream);
while (true) {
int len = decompressedStream.read(buffer);
if (len <= 0) {
break;
}
crc = updateCrc64(crc, buffer, 0, len);
}
decompressedStream.close();
return crc ^ -1;
}
@Override
public void run() {
String entryName = "";
ZipInputStream zis = new ZipInputStream(input);
try {
int entryIndex = 0;
ZipEntry entry = null;
int jobIndex = nextJob.getAndIncrement();
while ((entry = zis.getNextEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
if (entryIndex++ != jobIndex) {
zis.closeEntry();
continue;
}
entryName = entry.getName();
String entryCrcString = entryName.substring(0, entryName.indexOf('.'));
long entryCrc = new BigInteger(entryCrcString, 16).longValue();
if (entryCrc != decompressAndCalculateCrc(zis)) {
throw new RuntimeException("CRC mismatch");
}
zis.closeEntry();
entryName = "";
jobIndex = nextJob.getAndIncrement();
}
zis.close();
input.close();
} catch (Throwable ex) {
throw new RuntimeException(entryName, ex);
}
}
public static void main(String[] args) throws FileNotFoundException {
if (args.length == 0) {
throw new RuntimeException("Usage: BundleChecker <fileX.zip> ...");
}
for (int i = 0; i < args.length; ++i) {
new BundleChecker(new FileInputStream(args[i]), new AtomicInteger(0)).run();
}
}
}

58
java/integration/pom.xml Executable file
View File

@ -0,0 +1,58 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.brotli</groupId>
<artifactId>integration</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>integration</name>
<url>http://brotli.org</url>
<dependencies>
<dependency>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>..</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<executable>java</executable>
<mainClass>org.brotli.integration.BundleChecker</mainClass>
<arguments>
<argument>test_data.zip</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

BIN
java/integration/test_data.zip Executable file

Binary file not shown.