/* Copyright 2015 Google Inc. All Rights Reserved. Distributed under MIT license. See file LICENSE for detail or copy at https://opensource.org/licenses/MIT */ namespace Org.Brotli.Dec { /// Bit reading helpers. internal sealed 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 const int Capacity = 1024; private const int Slack = 16; private const int IntBufferSize = Capacity + Slack; private const int ByteReadSize = Capacity << 2; private const int ByteBufferSize = IntBufferSize << 2; private readonly byte[] byteBuffer = new byte[ByteBufferSize]; private readonly int[] intBuffer = new int[IntBufferSize]; private readonly Org.Brotli.Dec.IntReader intReader = new Org.Brotli.Dec.IntReader(); private System.IO.Stream input; /// Input stream is finished. private bool endOfStreamReached; /// Pre-fetched bits. internal long accumulator; /// Current bit-reading position in accumulator. internal int bitOffset; /// Offset of next item in intBuffer. private int intOffset; private int tailBytes = 0; /* Number of bytes in unfinished "int" item. */ /// Fills up the input buffer. /// /// Fills up the input buffer. ///

No-op if there are at least 36 bytes present after current position. ///

After encountering the end of the input stream, 64 additional zero bytes are copied to the /// buffer. /// internal static void ReadMoreInput(Org.Brotli.Dec.BitReader br) { // TODO: Split to check and read; move read outside of decoding loop. if (br.intOffset <= Capacity - 9) { return; } if (br.endOfStreamReached) { if (IntAvailable(br) >= -2) { return; } throw new Org.Brotli.Dec.BrotliRuntimeException("No more input"); } int readOffset = br.intOffset << 2; int bytesRead = ByteReadSize - readOffset; System.Array.Copy(br.byteBuffer, readOffset, br.byteBuffer, 0, bytesRead); br.intOffset = 0; try { while (bytesRead < ByteReadSize) { int len = br.input.Read(br.byteBuffer, bytesRead, ByteReadSize - bytesRead); // EOF is -1 in Java, but 0 in C#. if (len <= 0) { br.endOfStreamReached = true; br.tailBytes = bytesRead; bytesRead += 3; break; } bytesRead += len; } } catch (System.IO.IOException e) { throw new Org.Brotli.Dec.BrotliRuntimeException("Failed to read input", e); } Org.Brotli.Dec.IntReader.Convert(br.intReader, bytesRead >> 2); } internal static void CheckHealth(Org.Brotli.Dec.BitReader br, bool endOfStream) { if (!br.endOfStreamReached) { return; } int byteOffset = (br.intOffset << 2) + ((br.bitOffset + 7) >> 3) - 8; if (byteOffset > br.tailBytes) { throw new Org.Brotli.Dec.BrotliRuntimeException("Read after end"); } if (endOfStream && (byteOffset != br.tailBytes)) { throw new Org.Brotli.Dec.BrotliRuntimeException("Unused bytes after end"); } } ///

Advances the Read buffer by 5 bytes to make room for reading next 24 bits. internal static void FillBitWindow(Org.Brotli.Dec.BitReader br) { if (br.bitOffset >= 32) { br.accumulator = ((long)br.intBuffer[br.intOffset++] << 32) | ((long)(((ulong)br.accumulator) >> 32)); br.bitOffset -= 32; } } /// Reads the specified number of bits from Read Buffer. internal static int ReadBits(Org.Brotli.Dec.BitReader br, int n) { FillBitWindow(br); int val = (int)((long)(((ulong)br.accumulator) >> br.bitOffset)) & ((1 << n) - 1); br.bitOffset += n; return val; } /// Initialize bit reader. /// /// Initialize bit reader. ///

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. /// /// BitReader POJO /// data source internal static void Init(Org.Brotli.Dec.BitReader br, System.IO.Stream input) { if (br.input != null) { throw new System.InvalidOperationException("Bit reader already has associated input stream"); } Org.Brotli.Dec.IntReader.Init(br.intReader, br.byteBuffer, br.intBuffer); br.input = input; br.accumulator = 0; br.bitOffset = 64; br.intOffset = Capacity; br.endOfStreamReached = false; Prepare(br); } private static void Prepare(Org.Brotli.Dec.BitReader br) { ReadMoreInput(br); CheckHealth(br, false); FillBitWindow(br); FillBitWindow(br); } internal static void Reload(Org.Brotli.Dec.BitReader br) { if (br.bitOffset == 64) { Prepare(br); } } /// internal static void Close(Org.Brotli.Dec.BitReader br) { System.IO.Stream @is = br.input; br.input = null; if (@is != null) { @is.Close(); } } internal static void JumpToByteBoundary(Org.Brotli.Dec.BitReader br) { int padding = (64 - br.bitOffset) & 7; if (padding != 0) { int paddingBits = Org.Brotli.Dec.BitReader.ReadBits(br, padding); if (paddingBits != 0) { throw new Org.Brotli.Dec.BrotliRuntimeException("Corrupted padding bits"); } } } internal static int IntAvailable(Org.Brotli.Dec.BitReader br) { int limit = Capacity; if (br.endOfStreamReached) { limit = (br.tailBytes + 3) >> 2; } return limit - br.intOffset; } internal static void CopyBytes(Org.Brotli.Dec.BitReader br, byte[] data, int offset, int length) { if ((br.bitOffset & 7) != 0) { throw new Org.Brotli.Dec.BrotliRuntimeException("Unaligned copyBytes"); } // Drain accumulator. while ((br.bitOffset != 64) && (length != 0)) { data[offset++] = unchecked((byte)((long)(((ulong)br.accumulator) >> br.bitOffset))); br.bitOffset += 8; length--; } if (length == 0) { return; } // Get data from shadow buffer with "sizeof(int)" granularity. int copyInts = System.Math.Min(IntAvailable(br), length >> 2); if (copyInts > 0) { int readOffset = br.intOffset << 2; System.Array.Copy(br.byteBuffer, readOffset, data, offset, copyInts << 2); offset += copyInts << 2; length -= copyInts << 2; br.intOffset += copyInts; } if (length == 0) { return; } // Read tail bytes. if (IntAvailable(br) > 0) { // length = 1..3 FillBitWindow(br); while (length != 0) { data[offset++] = unchecked((byte)((long)(((ulong)br.accumulator) >> br.bitOffset))); br.bitOffset += 8; length--; } CheckHealth(br, false); return; } // Now it is possible to copy bytes directly. try { while (length > 0) { int len = br.input.Read(data, offset, length); if (len == -1) { throw new Org.Brotli.Dec.BrotliRuntimeException("Unexpected end of input"); } offset += len; length -= len; } } catch (System.IO.IOException e) { throw new Org.Brotli.Dec.BrotliRuntimeException("Failed to read input", e); } } } }