diff options
Diffstat (limited to 'java/org/brotli/dec/BitReader.java')
-rwxr-xr-x | java/org/brotli/dec/BitReader.java | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/java/org/brotli/dec/BitReader.java b/java/org/brotli/dec/BitReader.java new file mode 100755 index 0000000..13ab9c4 --- /dev/null +++ b/java/org/brotli/dec/BitReader.java @@ -0,0 +1,180 @@ +/* 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 "); + } + } + } + +} |