mirror of
https://github.com/google/brotli.git
synced 2024-11-09 21:50:07 +00:00
Add Java port of Brotli decoder.
This commit is contained in:
parent
85817beba8
commit
5025365d0f
@ -1,4 +1,9 @@
|
|||||||
# Description:
|
# Description:
|
||||||
# Bazel workspace file for Brotli.
|
# 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
51
java/dec/BUILD
Executable 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
171
java/dec/BitReader.java
Executable 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
27
java/dec/BitReaderTest.java
Executable 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
163
java/dec/BrotliInputStream.java
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
java/dec/BrotliRuntimeException.java
Executable file
25
java/dec/BrotliRuntimeException.java
Executable 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
162
java/dec/Context.java
Executable 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
835
java/dec/Decode.java
Executable 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
186
java/dec/DecodeTest.java
Executable 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
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
31
java/dec/DictionaryTest.java
Executable 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
138
java/dec/Huffman.java
Executable 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
57
java/dec/HuffmanTreeGroup.java
Executable 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
53
java/dec/Prefix.java
Executable 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
25
java/dec/RunningState.java
Executable 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
107
java/dec/State.java
Executable 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
2131
java/dec/SynthTest.java
Executable file
File diff suppressed because it is too large
Load Diff
240
java/dec/Transform.java
Executable file
240
java/dec/Transform.java
Executable 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
63
java/dec/TransformTest.java
Executable 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
55
java/dec/Utils.java
Executable 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
48
java/dec/WordTransformType.java
Executable 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
46
java/dec/pom.xml
Executable 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
23
java/integration/BUILD
Executable 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"],
|
||||||
|
)
|
120
java/integration/BundleChecker.java
Executable file
120
java/integration/BundleChecker.java
Executable 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
58
java/integration/pom.xml
Executable 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
BIN
java/integration/test_data.zip
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user