blob: 13ab9c462d1af969c7c6fe6476d262d291e55159 [file] [log] [blame]
/* 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 available 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 unusedBytes = (br.available << 2) + ((64 - br.bitOffset) >> 3);
int borrowedBytes = 64 - br.tailBytes;
if (unusedBytes != 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);
/* This situation is impossible in current implementation. */
if (br.available == 0) {
throw new BrotliRuntimeException("Can't initialize reader");
}
fillBitWindow(br);
fillBitWindow(br);
}
static void close(BitReader br) throws IOException {
InputStream is = br.input;
br.input = null;
if (is != null) {
is.close();
}
}
static void jumpToByteBoundary(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 ");
}
}
}
}