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. + */ + static void readMoreInput(State s) { + if (s.halfOffset > HALF_WATERLINE) { + doReadMoreInput(s); + } + } + + static void doReadMoreInput(State s) { + if (s.endOfStreamReached != 0) { + if (halfAvailable(s) >= -2) { + return; + } + throw new BrotliRuntimeException("No more input"); + } + final int readOffset = s.halfOffset << LOG_HALF_SIZE; + int bytesInBuffer = CAPACITY - readOffset; + // Move unused bytes to the head of the buffer. + Utils.copyBytesWithin(s.byteBuffer, 0, readOffset, CAPACITY); + s.halfOffset = 0; + while (bytesInBuffer < CAPACITY) { + final int spaceLeft = CAPACITY - bytesInBuffer; + final int len = Utils.readInput(s.input, s.byteBuffer, bytesInBuffer, spaceLeft); + // EOF is -1 in Java, but 0 in C#. + if (len <= 0) { + s.endOfStreamReached = 1; + s.tailBytes = bytesInBuffer; + bytesInBuffer += HALF_SIZE - 1; + break; + } + bytesInBuffer += len; + } + bytesToNibbles(s, bytesInBuffer); + } + + static void checkHealth(State s, int endOfStream) { + if (s.endOfStreamReached == 0) { + return; + } + final int byteOffset = (s.halfOffset << LOG_HALF_SIZE) + ((s.bitOffset + 7) >> 3) - BYTENESS; + if (byteOffset > s.tailBytes) { + throw new BrotliRuntimeException("Read after end"); + } + if ((endOfStream != 0) && (byteOffset != s.tailBytes)) { + throw new BrotliRuntimeException("Unused bytes after end"); + } + } + + static void assertAccumulatorHealthy(State s) { + if (s.bitOffset > BITNESS) { + throw new IllegalStateException("Accumulator underloaded: " + s.bitOffset); + } + } + + static void fillBitWindow(State s) { + if (DEBUG != 0) { + assertAccumulatorHealthy(s); + } + if (s.bitOffset >= HALF_BITNESS) { + // Same as doFillBitWindow. JVM fails to inline it. + if (BITNESS == 64) { + s.accumulator64 = ((long) s.intBuffer[s.halfOffset++] << HALF_BITNESS) + | (s.accumulator64 >>> HALF_BITNESS); + } else { + s.accumulator32 = ((int) s.shortBuffer[s.halfOffset++] << HALF_BITNESS) + | (s.accumulator32 >>> HALF_BITNESS); + } + s.bitOffset -= HALF_BITNESS; + } + } + + static void doFillBitWindow(State s) { + if (DEBUG != 0) { + assertAccumulatorHealthy(s); + } + if (BITNESS == 64) { + s.accumulator64 = ((long) s.intBuffer[s.halfOffset++] << HALF_BITNESS) + | (s.accumulator64 >>> HALF_BITNESS); + } else { + s.accumulator32 = ((int) s.shortBuffer[s.halfOffset++] << HALF_BITNESS) + | (s.accumulator32 >>> HALF_BITNESS); + } + s.bitOffset -= HALF_BITNESS; + } + + static int peekBits(State s) { + if (BITNESS == 64) { + return (int) (s.accumulator64 >>> s.bitOffset); + } else { + return s.accumulator32 >>> s.bitOffset; + } + } + + /** + * Fetches bits from accumulator. + * + * WARNING: accumulator MUST contain at least the specified amount of bits, + * otherwise BitReader will become broken. + */ + static int readFewBits(State s, int n) { + final int val = peekBits(s) & ((1 << n) - 1); + s.bitOffset += n; + return val; + } + + static int readBits(State s, int n) { + if (HALF_BITNESS >= 24) { + return readFewBits(s, n); + } else { + return (n <= 16) ? readFewBits(s, n) : readManyBits(s, n); + } + } + + private static int readManyBits(State s, int n) { + final int low = readFewBits(s, 16); + doFillBitWindow(s); + return low | (readFewBits(s, n - 16) << 16); + } + + static void initBitReader(State s) { + s.byteBuffer = new byte[BUFFER_SIZE]; + if (BITNESS == 64) { + s.accumulator64 = 0; + s.intBuffer = new int[HALF_BUFFER_SIZE]; + } else { + s.accumulator32 = 0; + s.shortBuffer = new short[HALF_BUFFER_SIZE]; + } + s.bitOffset = BITNESS; + s.halfOffset = HALVES_CAPACITY; + s.endOfStreamReached = 0; + prepare(s); + } + + private static void prepare(State s) { + readMoreInput(s); + checkHealth(s, 0); + doFillBitWindow(s); + doFillBitWindow(s); + } + + static void reload(State s) { + if (s.bitOffset == BITNESS) { + prepare(s); + } + } + + static void jumpToByteBoundary(State s) { + final int padding = (BITNESS - s.bitOffset) & 7; + if (padding != 0) { + final int paddingBits = readFewBits(s, padding); + if (paddingBits != 0) { + throw new BrotliRuntimeException("Corrupted padding bits"); + } + } + } + + static int halfAvailable(State s) { + int limit = HALVES_CAPACITY; + if (s.endOfStreamReached != 0) { + limit = (s.tailBytes + (HALF_SIZE - 1)) >> LOG_HALF_SIZE; + } + return limit - s.halfOffset; + } + + static void copyRawBytes(State s, byte[] data, int offset, int length) { + if ((s.bitOffset & 7) != 0) { + throw new BrotliRuntimeException("Unaligned copyBytes"); + } + + // Drain accumulator. + while ((s.bitOffset != BITNESS) && (length != 0)) { + data[offset++] = (byte) peekBits(s); + s.bitOffset += 8; + length--; + } + if (length == 0) { + return; + } + + // Get data from shadow buffer with "sizeof(int)" granularity. + final int copyNibbles = Math.min(halfAvailable(s), length >> LOG_HALF_SIZE); + if (copyNibbles > 0) { + final int readOffset = s.halfOffset << LOG_HALF_SIZE; + final int delta = copyNibbles << LOG_HALF_SIZE; + System.arraycopy(s.byteBuffer, readOffset, data, offset, delta); + offset += delta; + length -= delta; + s.halfOffset += copyNibbles; + } + if (length == 0) { + return; + } + + // Read tail bytes. + if (halfAvailable(s) > 0) { + // length = 1..3 + fillBitWindow(s); + while (length != 0) { + data[offset++] = (byte) peekBits(s); + s.bitOffset += 8; + length--; + } + checkHealth(s, 0); + return; + } + + // Now it is possible to copy bytes directly. + while (length > 0) { + final int len = Utils.readInput(s.input, data, offset, length); + if (len == -1) { + throw new BrotliRuntimeException("Unexpected end of input"); + } + offset += len; + length -= len; + } + } + + /** + * Translates bytes to halves (int/short). + */ + static void bytesToNibbles(State s, int byteLen) { + final byte[] byteBuffer = s.byteBuffer; + final int halfLen = byteLen >> LOG_HALF_SIZE; + if (BITNESS == 64) { + final int[] intBuffer = s.intBuffer; + for (int i = 0; i < halfLen; ++i) { + intBuffer[i] = ((byteBuffer[i * 4] & 0xFF)) + | ((byteBuffer[(i * 4) + 1] & 0xFF) << 8) + | ((byteBuffer[(i * 4) + 2] & 0xFF) << 16) + | ((byteBuffer[(i * 4) + 3] & 0xFF) << 24); + } + } else { + final short[] shortBuffer = s.shortBuffer; + for (int i = 0; i < halfLen; ++i) { + shortBuffer[i] = (short) ((byteBuffer[i * 2] & 0xFF) + | ((byteBuffer[(i * 2) + 1] & 0xFF) << 8)); + } + } + } +} diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/BrotliInputStream.java b/firka_wear/android/app/src/main/java/org/brotli/dec/BrotliInputStream.java new file mode 100644 index 00000000..7bbe2f63 --- /dev/null +++ b/firka_wear/android/app/src/main/java/org/brotli/dec/BrotliInputStream.java @@ -0,0 +1,172 @@ +/* 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. + * + *
Not thread-safe. + */ +public class BrotliInputStream extends InputStream { + + public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 256; + + /** + * Value expected by InputStream contract when stream is over. + * + * In Java it is -1. + * In C# it is 0 (should be patched during transpilation). + */ + private static final int END_OF_STREAM_MARKER = -1; + + /** + * 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. + * + *
For byte-by-byte reading ({@link #read()}) internal buffer with + * {@link #DEFAULT_INTERNAL_BUFFER_SIZE} size is allocated and used. + * + *
Will block the thread until first {@link BitReader#CAPACITY} bytes of data of source + * are available. + * + * @param source underlying data source + * @throws IOException in case of corrupted data or source stream problems + */ + public BrotliInputStream(InputStream source) throws IOException { + this(source, DEFAULT_INTERNAL_BUFFER_SIZE); + } + + /** + * Creates a {@link InputStream} wrapper that decompresses brotli data. + * + *
For byte-by-byte reading ({@link #read()}) internal buffer of specified size is + * allocated and used. + * + *
Will block the thread until first {@link BitReader#CAPACITY} bytes of data of source
+ * are available.
+ *
+ * @param source compressed data source
+ * @param byteReadBufferSize size of internal buffer used in case of
+ * byte-by-byte reading
+ * @throws IOException in case of corrupted data or source stream problems
+ */
+ public BrotliInputStream(InputStream source, int byteReadBufferSize) 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 {
+ Decode.initState(state, source);
+ } catch (BrotliRuntimeException ex) {
+ throw new IOException("Brotli decoder initialization failed", ex);
+ }
+ }
+
+ public void attachDictionaryChunk(byte[] data) {
+ Decode.attachDictionaryChunk(state, data);
+ }
+
+ public void enableEagerOutput() {
+ Decode.enableEagerOutput(state);
+ }
+
+ public void enableLargeWindow() {
+ Decode.enableLargeWindow(state);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() throws IOException {
+ Decode.close(state);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int read() throws IOException {
+ if (bufferOffset >= remainingBufferBytes) {
+ remainingBufferBytes = read(buffer, 0, buffer.length);
+ bufferOffset = 0;
+ if (remainingBufferBytes == END_OF_STREAM_MARKER) {
+ // Both Java and C# return the same value for EOF on single-byte read.
+ return -1;
+ }
+ }
+ return buffer[bufferOffset++] & 0xFF;
+ }
+
+ /**
+ * {@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);
+ copyLen += state.outputUsed;
+ copyLen = (copyLen > 0) ? copyLen : END_OF_STREAM_MARKER;
+ return copyLen;
+ } catch (BrotliRuntimeException ex) {
+ throw new IOException("Brotli stream decoding failed", ex);
+ }
+
+ // <{[INJECTED CODE]}>
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/BrotliRuntimeException.java b/firka_wear/android/app/src/main/java/org/brotli/dec/BrotliRuntimeException.java
new file mode 100644
index 00000000..18449072
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/BrotliRuntimeException.java
@@ -0,0 +1,21 @@
+/* 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);
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/Context.java b/firka_wear/android/app/src/main/java/org/brotli/dec/Context.java
new file mode 100644
index 00000000..10bf0cbc
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/Context.java
@@ -0,0 +1,58 @@
+/* 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 = new int[2048];
+
+ private static final String UTF_MAP = " !! ! \"#$##%#$&'##(#)#+++++++++"
+ + "+((&*'##,---,---,-----,-----,-----'###.///.///./////./////./////'# ";
+ private static final String UTF_RLE = "A/* ': & : $ \u0081 @";
+
+ private static void unpackLookupTable(int[] lookup, String map, String rle) {
+ // LSB6, MSB6, SIGNED
+ for (int i = 0; i < 256; ++i) {
+ lookup[i] = i & 0x3F;
+ lookup[512 + i] = i >> 2;
+ lookup[1792 + i] = 2 + (i >> 6);
+ }
+ // UTF8
+ for (int i = 0; i < 128; ++i) {
+ lookup[1024 + i] = 4 * (map.charAt(i) - 32);
+ }
+ for (int i = 0; i < 64; ++i) {
+ lookup[1152 + i] = i & 1;
+ lookup[1216 + i] = 2 + (i & 1);
+ }
+ int offset = 1280;
+ for (int k = 0; k < 19; ++k) {
+ final int value = k & 3;
+ final int rep = rle.charAt(k) - 32;
+ for (int i = 0; i < rep; ++i) {
+ lookup[offset++] = value;
+ }
+ }
+ // SIGNED
+ for (int i = 0; i < 16; ++i) {
+ lookup[1792 + i] = 1;
+ lookup[2032 + i] = 6;
+ }
+ lookup[1792] = 0;
+ lookup[2047] = 7;
+ for (int i = 0; i < 256; ++i) {
+ lookup[1536 + i] = lookup[1792 + i] << 3;
+ }
+ }
+
+ static {
+ unpackLookupTable(LOOKUP, UTF_MAP, UTF_RLE);
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/Decode.java b/firka_wear/android/app/src/main/java/org/brotli/dec/Decode.java
new file mode 100644
index 00000000..bf9b6817
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/Decode.java
@@ -0,0 +1,1357 @@
+/* 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;
+
+/**
+ * API for Brotli decompression.
+ */
+final class Decode {
+
+ static final int MIN_LARGE_WINDOW_BITS = 10;
+ /* Maximum was chosen to be 30 to allow efficient decoder implementation.
+ * Format allows bigger window, but Java does not support 2G+ arrays. */
+ static final int MAX_LARGE_WINDOW_BITS = 30;
+
+ //----------------------------------------------------------------------------
+ // RunningState
+ //----------------------------------------------------------------------------
+ private static final int UNINITIALIZED = 0;
+ private static final int INITIALIZED = 1;
+ private static final int BLOCK_START = 2;
+ private static final int COMPRESSED_BLOCK_START = 3;
+ private static final int MAIN_LOOP = 4;
+ private static final int READ_METADATA = 5;
+ private static final int COPY_UNCOMPRESSED = 6;
+ private static final int INSERT_LOOP = 7;
+ private static final int COPY_LOOP = 8;
+ private static final int USE_DICTIONARY = 9;
+ private static final int FINISHED = 10;
+ private static final int CLOSED = 11;
+ private static final int INIT_WRITE = 12;
+ private static final int WRITE = 13;
+ private static final int COPY_FROM_COMPOUND_DICTIONARY = 14;
+
+ 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_COMMAND_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 CD_BLOCK_MAP_BITS = 8;
+ private static final int HUFFMAN_TABLE_BITS = 8;
+ private static final int HUFFMAN_TABLE_MASK = 0xFF;
+
+ /**
+ * Maximum possible Huffman table size for an alphabet size of (index * 32),
+ * max code length 15 and root table bits 8.
+ * The biggest alphabet is "command" - 704 symbols. Though "distance" alphabet could theoretically
+ * outreach that limit (for 62 extra bit distances), practically it is limited by
+ * MAX_ALLOWED_DISTANCE and never gets bigger than 544 symbols.
+ */
+ static final int[] MAX_HUFFMAN_TABLE_SIZE = {
+ 256, 402, 436, 468, 500, 534, 566, 598, 630, 662, 694, 726, 758, 790, 822,
+ 854, 886, 920, 952, 984, 1016, 1048, 1080
+ };
+
+ private static final int HUFFMAN_TABLE_SIZE_26 = 396;
+ private static final int HUFFMAN_TABLE_SIZE_258 = 632;
+
+ 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 = {
+ 0, 3, 2, 1, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3
+ };
+
+ 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
+ };
+
+ // TODO(eustas): generalize.
+ static final int MAX_TRANSFORMED_WORD_LENGTH = 5 + 24 + 8;
+
+ private static final int MAX_DISTANCE_BITS = 24;
+ private static final int MAX_LARGE_WINDOW_DISTANCE_BITS = 62;
+
+ /**
+ * Safe distance limit.
+ *
+ * Limit ((1 << 31) - 4) allows safe distance calculation without overflows,
+ * given the distance alphabet size is limited to corresponding size.
+ */
+ private static final int MAX_ALLOWED_DISTANCE = 0x7FFFFFFC;
+
+ //----------------------------------------------------------------------------
+ // Prefix code LUT.
+ //----------------------------------------------------------------------------
+ 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 short[] INSERT_LENGTH_N_BITS = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03,
+ 0x04, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C, 0x0E, 0x18
+ };
+
+ static final short[] COPY_LENGTH_N_BITS = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x02,
+ 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x18
+ };
+
+ // Each command is represented with 4x16-bit values:
+ // * [insertLenExtraBits, copyLenExtraBits]
+ // * insertLenOffset
+ // * copyLenOffset
+ // * distanceContext
+ static final short[] CMD_LOOKUP = new short[NUM_COMMAND_CODES * 4];
+
+ static {
+ unpackCommandLookupTable(CMD_LOOKUP);
+ }
+
+ private static int log2floor(int i) {
+ int result = -1;
+ int step = 16;
+ while (step > 0) {
+ if ((i >>> step) != 0) {
+ result += step;
+ i = i >>> step;
+ }
+ step = step >> 1;
+ }
+ return result + i;
+ }
+
+ private static int calculateDistanceAlphabetSize(int npostfix, int ndirect, int maxndistbits) {
+ return NUM_DISTANCE_SHORT_CODES + ndirect + 2 * (maxndistbits << npostfix);
+ }
+
+ // TODO(eustas): add a correctness test for this function when
+ // large-window and dictionary are implemented.
+ private static int calculateDistanceAlphabetLimit(int maxDistance, int npostfix, int ndirect) {
+ if (maxDistance < ndirect + (2 << npostfix)) {
+ throw new IllegalArgumentException("maxDistance is too small");
+ }
+ final int offset = ((maxDistance - ndirect) >> npostfix) + 4;
+ final int ndistbits = log2floor(offset) - 1;
+ final int group = ((ndistbits - 1) << 1) | ((offset >> ndistbits) & 1);
+ return ((group - 1) << npostfix) + (1 << npostfix) + ndirect + NUM_DISTANCE_SHORT_CODES;
+ }
+
+ private static void unpackCommandLookupTable(short[] cmdLookup) {
+ final short[] insertLengthOffsets = new short[24];
+ final short[] copyLengthOffsets = new short[24];
+ copyLengthOffsets[0] = 2;
+ for (int i = 0; i < 23; ++i) {
+ insertLengthOffsets[i + 1] =
+ (short) (insertLengthOffsets[i] + (1 << INSERT_LENGTH_N_BITS[i]));
+ copyLengthOffsets[i + 1] =
+ (short) (copyLengthOffsets[i] + (1 << COPY_LENGTH_N_BITS[i]));
+ }
+
+ for (int cmdCode = 0; cmdCode < NUM_COMMAND_CODES; ++cmdCode) {
+ int rangeIdx = cmdCode >>> 6;
+ /* -4 turns any regular distance code to negative. */
+ int distanceContextOffset = -4;
+ if (rangeIdx >= 2) {
+ rangeIdx -= 2;
+ distanceContextOffset = 0;
+ }
+ final int insertCode = (((0x29850 >>> (rangeIdx * 2)) & 0x3) << 3) | ((cmdCode >>> 3) & 7);
+ final int copyCode = (((0x26244 >>> (rangeIdx * 2)) & 0x3) << 3) | (cmdCode & 7);
+ final short copyLengthOffset = copyLengthOffsets[copyCode];
+ final int distanceContext =
+ distanceContextOffset + (copyLengthOffset > 4 ? 3 : copyLengthOffset - 2);
+ final int index = cmdCode * 4;
+ cmdLookup[index + 0] =
+ (short) (INSERT_LENGTH_N_BITS[insertCode] | (COPY_LENGTH_N_BITS[copyCode] << 8));
+ cmdLookup[index + 1] = insertLengthOffsets[insertCode];
+ cmdLookup[index + 2] = copyLengthOffsets[copyCode];
+ cmdLookup[index + 3] = (short) distanceContext;
+ }
+ }
+
+ /**
+ * Reads brotli stream header and parses "window bits".
+ *
+ * @param s initialized state, before any read is performed.
+ * @return -1 if header is invalid
+ */
+ private static int decodeWindowBits(State s) {
+ /* Change the meaning of flag. Before that step it means "decoder must be capable of reading
+ * "large-window" brotli stream. After this step it means that "large-window" feature
+ * is actually detected. Despite the window size could be same as before (lgwin = 10..24),
+ * encoded distances are allowed to be much greater, thus bigger dictinary could be used. */
+ final int largeWindowEnabled = s.isLargeWindow;
+ s.isLargeWindow = 0;
+
+ BitReader.fillBitWindow(s);
+ if (BitReader.readFewBits(s, 1) == 0) {
+ return 16;
+ }
+ int n = BitReader.readFewBits(s, 3);
+ if (n != 0) {
+ return 17 + n;
+ }
+ n = BitReader.readFewBits(s, 3);
+ if (n != 0) {
+ if (n == 1) {
+ if (largeWindowEnabled == 0) {
+ /* Reserved value in regular brotli stream. */
+ return -1;
+ }
+ s.isLargeWindow = 1;
+ /* Check "reserved" bit for future (post-large-window) extensions. */
+ if (BitReader.readFewBits(s, 1) == 1) {
+ return -1;
+ }
+ n = BitReader.readFewBits(s, 6);
+ if (n < MIN_LARGE_WINDOW_BITS || n > MAX_LARGE_WINDOW_BITS) {
+ /* Encoded window bits value is too small or too big. */
+ return -1;
+ }
+ return n;
+ } else {
+ return 8 + n;
+ }
+ }
+ return 17;
+ }
+
+ /**
+ * Switch decoder to "eager" mode.
+ *
+ * In "eager" mode decoder returns as soon as there is enough data to fill output buffer.
+ *
+ * @param s initialized state, before any read is performed.
+ */
+ static void enableEagerOutput(State s) {
+ if (s.runningState != INITIALIZED) {
+ throw new IllegalStateException("State MUST be freshly initialized");
+ }
+ s.isEager = 1;
+ }
+
+ static void enableLargeWindow(State s) {
+ if (s.runningState != INITIALIZED) {
+ throw new IllegalStateException("State MUST be freshly initialized");
+ }
+ s.isLargeWindow = 1;
+ }
+
+ // TODO(eustas): do we need byte views?
+ static void attachDictionaryChunk(State s, byte[] data) {
+ if (s.runningState != INITIALIZED) {
+ throw new IllegalStateException("State MUST be freshly initialized");
+ }
+ if (s.cdNumChunks == 0) {
+ s.cdChunks = new byte[16][];
+ s.cdChunkOffsets = new int[16];
+ s.cdBlockBits = -1;
+ }
+ if (s.cdNumChunks == 15) {
+ throw new IllegalStateException("Too many dictionary chunks");
+ }
+ s.cdChunks[s.cdNumChunks] = data;
+ s.cdNumChunks++;
+ s.cdTotalSize += data.length;
+ s.cdChunkOffsets[s.cdNumChunks] = s.cdTotalSize;
+ }
+
+ /**
+ * Associate input with decoder state.
+ *
+ * @param s uninitialized state without associated input
+ * @param input compressed data source
+ */
+ static void initState(State s, InputStream input) {
+ if (s.runningState != UNINITIALIZED) {
+ throw new IllegalStateException("State MUST be uninitialized");
+ }
+ /* 6 trees + 1 extra "offset" slot to simplify table decoding logic. */
+ s.blockTrees = new int[7 + 3 * (HUFFMAN_TABLE_SIZE_258 + HUFFMAN_TABLE_SIZE_26)];
+ s.blockTrees[0] = 7;
+ s.distRbIdx = 3;
+ final int maxDistanceAlphabetLimit =
+ calculateDistanceAlphabetLimit(MAX_ALLOWED_DISTANCE, 3, 15 << 3);
+ s.distExtraBits = new byte[maxDistanceAlphabetLimit];
+ s.distOffset = new int[maxDistanceAlphabetLimit];
+ s.input = input;
+ BitReader.initBitReader(s);
+ s.runningState = INITIALIZED;
+ }
+
+ static void close(State s) throws IOException {
+ if (s.runningState == UNINITIALIZED) {
+ throw new IllegalStateException("State MUST be initialized");
+ }
+ if (s.runningState == CLOSED) {
+ return;
+ }
+ s.runningState = CLOSED;
+ if (s.input != null) {
+ Utils.closeInput(s.input);
+ s.input = null;
+ }
+ }
+
+ /**
+ * Decodes a number in the range [0..255], by reading 1 - 11 bits.
+ */
+ private static int decodeVarLenUnsignedByte(State s) {
+ BitReader.fillBitWindow(s);
+ if (BitReader.readFewBits(s, 1) != 0) {
+ final int n = BitReader.readFewBits(s, 3);
+ if (n == 0) {
+ return 1;
+ } else {
+ return BitReader.readFewBits(s, n) + (1 << n);
+ }
+ }
+ return 0;
+ }
+
+ private static void decodeMetaBlockLength(State s) {
+ BitReader.fillBitWindow(s);
+ s.inputEnd = BitReader.readFewBits(s, 1);
+ s.metaBlockLength = 0;
+ s.isUncompressed = 0;
+ s.isMetadata = 0;
+ if ((s.inputEnd != 0) && BitReader.readFewBits(s, 1) != 0) {
+ return;
+ }
+ final int sizeNibbles = BitReader.readFewBits(s, 2) + 4;
+ if (sizeNibbles == 7) {
+ s.isMetadata = 1;
+ if (BitReader.readFewBits(s, 1) != 0) {
+ throw new BrotliRuntimeException("Corrupted reserved bit");
+ }
+ final int sizeBytes = BitReader.readFewBits(s, 2);
+ if (sizeBytes == 0) {
+ return;
+ }
+ for (int i = 0; i < sizeBytes; i++) {
+ BitReader.fillBitWindow(s);
+ final int bits = BitReader.readFewBits(s, 8);
+ if (bits == 0 && i + 1 == sizeBytes && sizeBytes > 1) {
+ throw new BrotliRuntimeException("Exuberant nibble");
+ }
+ s.metaBlockLength |= bits << (i * 8);
+ }
+ } else {
+ for (int i = 0; i < sizeNibbles; i++) {
+ BitReader.fillBitWindow(s);
+ final int bits = BitReader.readFewBits(s, 4);
+ if (bits == 0 && i + 1 == sizeNibbles && sizeNibbles > 4) {
+ throw new BrotliRuntimeException("Exuberant nibble");
+ }
+ s.metaBlockLength |= bits << (i * 4);
+ }
+ }
+ s.metaBlockLength++;
+ if (s.inputEnd == 0) {
+ s.isUncompressed = BitReader.readFewBits(s, 1);
+ }
+ }
+
+ /**
+ * Decodes the next Huffman code from bit-stream.
+ */
+ private static int readSymbol(int[] tableGroup, int tableIdx, State s) {
+ int offset = tableGroup[tableIdx];
+ final int val = BitReader.peekBits(s);
+ offset += val & HUFFMAN_TABLE_MASK;
+ final int bits = tableGroup[offset] >> 16;
+ final int sym = tableGroup[offset] & 0xFFFF;
+ if (bits <= HUFFMAN_TABLE_BITS) {
+ s.bitOffset += bits;
+ return sym;
+ }
+ offset += sym;
+ final int mask = (1 << bits) - 1;
+ offset += (val & mask) >>> HUFFMAN_TABLE_BITS;
+ s.bitOffset += ((tableGroup[offset] >> 16) + HUFFMAN_TABLE_BITS);
+ return tableGroup[offset] & 0xFFFF;
+ }
+
+ private static int readBlockLength(int[] tableGroup, int tableIdx, State s) {
+ BitReader.fillBitWindow(s);
+ final int code = readSymbol(tableGroup, tableIdx, s);
+ final int n = BLOCK_LENGTH_N_BITS[code];
+ BitReader.fillBitWindow(s);
+ return BLOCK_LENGTH_OFFSET[code] + BitReader.readBits(s, n);
+ }
+
+ private static void moveToFront(int[] v, int index) {
+ final int value = v[index];
+ for (; index > 0; index--) {
+ v[index] = v[index - 1];
+ }
+ v[0] = value;
+ }
+
+ private static void inverseMoveToFrontTransform(byte[] v, int vLen) {
+ final int[] mtf = new int[256];
+ for (int i = 0; i < 256; i++) {
+ mtf[i] = i;
+ }
+ for (int i = 0; i < vLen; i++) {
+ final 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, State s) {
+ int symbol = 0;
+ int prevCodeLen = DEFAULT_CODE_LENGTH;
+ int repeat = 0;
+ int repeatCodeLen = 0;
+ int space = 32768;
+ final int[] table = new int[32 + 1]; /* Speculative single entry table group. */
+ final int tableIdx = table.length - 1;
+ Huffman.buildHuffmanTable(table, tableIdx, 5, codeLengthCodeLengths, CODE_LENGTH_CODES);
+
+ while (symbol < numSymbols && space > 0) {
+ BitReader.readMoreInput(s);
+ BitReader.fillBitWindow(s);
+ final int p = BitReader.peekBits(s) & 31;
+ s.bitOffset += table[p] >> 16;
+ final 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 {
+ final int extraBits = codeLen - 14;
+ int newLen = 0;
+ if (codeLen == CODE_LENGTH_REPEAT_CODE) {
+ newLen = prevCodeLen;
+ }
+ if (repeatCodeLen != newLen) {
+ repeat = 0;
+ repeatCodeLen = newLen;
+ }
+ final int oldRepeat = repeat;
+ if (repeat > 0) {
+ repeat -= 2;
+ repeat <<= extraBits;
+ }
+ BitReader.fillBitWindow(s);
+ repeat += BitReader.readFewBits(s, extraBits) + 3;
+ final 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(eustas): Pass max_symbol to Huffman table builder instead?
+ Utils.fillIntsWithZeroes(codeLengths, symbol, numSymbols);
+ }
+
+ private static void checkDupes(int[] symbols, int length) {
+ for (int i = 0; i < length - 1; ++i) {
+ for (int j = i + 1; j < length; ++j) {
+ if (symbols[i] == symbols[j]) {
+ throw new BrotliRuntimeException("Duplicate simple Huffman code symbol"); // COV_NF_LINE
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads up to 4 symbols directly and applies predefined histograms.
+ */
+ private static int readSimpleHuffmanCode(int alphabetSizeMax, int alphabetSizeLimit,
+ int[] tableGroup, int tableIdx, State s) {
+ // TODO(eustas): Avoid allocation?
+ final int[] codeLengths = new int[alphabetSizeLimit];
+ final int[] symbols = new int[4];
+
+ final int maxBits = 1 + log2floor(alphabetSizeMax - 1);
+
+ final int numSymbols = BitReader.readFewBits(s, 2) + 1;
+ for (int i = 0; i < numSymbols; i++) {
+ BitReader.fillBitWindow(s);
+ final int symbol = BitReader.readFewBits(s, maxBits);
+ if (symbol >= alphabetSizeLimit) {
+ throw new BrotliRuntimeException("Can't readHuffmanCode"); // COV_NF_LINE
+ }
+ symbols[i] = symbol;
+ }
+ checkDupes(symbols, numSymbols);
+
+ int histogramId = numSymbols;
+ if (numSymbols == 4) {
+ histogramId += BitReader.readFewBits(s, 1);
+ }
+
+ switch (histogramId) {
+ case 1:
+ codeLengths[symbols[0]] = 1;
+ break;
+
+ case 2:
+ codeLengths[symbols[0]] = 1;
+ codeLengths[symbols[1]] = 1;
+ break;
+
+ case 3:
+ codeLengths[symbols[0]] = 1;
+ codeLengths[symbols[1]] = 2;
+ codeLengths[symbols[2]] = 2;
+ break;
+
+ case 4: // uniform 4-symbol histogram
+ codeLengths[symbols[0]] = 2;
+ codeLengths[symbols[1]] = 2;
+ codeLengths[symbols[2]] = 2;
+ codeLengths[symbols[3]] = 2;
+ break;
+
+ case 5: // prioritized 4-symbol histogram
+ codeLengths[symbols[0]] = 1;
+ codeLengths[symbols[1]] = 2;
+ codeLengths[symbols[2]] = 3;
+ codeLengths[symbols[3]] = 3;
+ break;
+
+ default:
+ break;
+ }
+
+ // TODO(eustas): Use specialized version?
+ return Huffman.buildHuffmanTable(
+ tableGroup, tableIdx, HUFFMAN_TABLE_BITS, codeLengths, alphabetSizeLimit);
+ }
+
+ // Decode Huffman-coded code lengths.
+ private static int readComplexHuffmanCode(int alphabetSizeLimit, int skip,
+ int[] tableGroup, int tableIdx, State s) {
+ // TODO(eustas): Avoid allocation?
+ final int[] codeLengths = new int[alphabetSizeLimit];
+ final int[] codeLengthCodeLengths = new int[CODE_LENGTH_CODES];
+ int space = 32;
+ int numCodes = 0;
+ for (int i = skip; i < CODE_LENGTH_CODES && space > 0; i++) {
+ final int codeLenIdx = CODE_LENGTH_CODE_ORDER[i];
+ BitReader.fillBitWindow(s);
+ final int p = BitReader.peekBits(s) & 15;
+ // TODO(eustas): Demultiplex FIXED_TABLE.
+ s.bitOffset += FIXED_TABLE[p] >> 16;
+ final int v = FIXED_TABLE[p] & 0xFFFF;
+ codeLengthCodeLengths[codeLenIdx] = v;
+ if (v != 0) {
+ space -= (32 >> v);
+ numCodes++;
+ }
+ }
+ if (space != 0 && numCodes != 1) {
+ throw new BrotliRuntimeException("Corrupted Huffman code histogram"); // COV_NF_LINE
+ }
+
+ readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSizeLimit, codeLengths, s);
+
+ return Huffman.buildHuffmanTable(
+ tableGroup, tableIdx, HUFFMAN_TABLE_BITS, codeLengths, alphabetSizeLimit);
+ }
+
+ /**
+ * Decodes Huffman table from bit-stream.
+ *
+ * @return number of slots used by resulting Huffman table
+ */
+ private static int readHuffmanCode(int alphabetSizeMax, int alphabetSizeLimit,
+ int[] tableGroup, int tableIdx, State s) {
+ BitReader.readMoreInput(s);
+ BitReader.fillBitWindow(s);
+ final int simpleCodeOrSkip = BitReader.readFewBits(s, 2);
+ if (simpleCodeOrSkip == 1) {
+ return readSimpleHuffmanCode(alphabetSizeMax, alphabetSizeLimit, tableGroup, tableIdx, s);
+ } else {
+ return readComplexHuffmanCode(alphabetSizeLimit, simpleCodeOrSkip, tableGroup, tableIdx, s);
+ }
+ }
+
+ private static int decodeContextMap(int contextMapSize, byte[] contextMap, State s) {
+ BitReader.readMoreInput(s);
+ final int numTrees = decodeVarLenUnsignedByte(s) + 1;
+
+ if (numTrees == 1) {
+ Utils.fillBytesWithZeroes(contextMap, 0, contextMapSize);
+ return numTrees;
+ }
+
+ BitReader.fillBitWindow(s);
+ final int useRleForZeros = BitReader.readFewBits(s, 1);
+ int maxRunLengthPrefix = 0;
+ if (useRleForZeros != 0) {
+ maxRunLengthPrefix = BitReader.readFewBits(s, 4) + 1;
+ }
+ final int alphabetSize = numTrees + maxRunLengthPrefix;
+ final int tableSize = MAX_HUFFMAN_TABLE_SIZE[(alphabetSize + 31) >> 5];
+ /* Speculative single entry table group. */
+ final int[] table = new int[tableSize + 1];
+ final int tableIdx = table.length - 1;
+ readHuffmanCode(alphabetSize, alphabetSize, table, tableIdx, s);
+ for (int i = 0; i < contextMapSize; ) {
+ BitReader.readMoreInput(s);
+ BitReader.fillBitWindow(s);
+ final int code = readSymbol(table, tableIdx, s);
+ if (code == 0) {
+ contextMap[i] = 0;
+ i++;
+ } else if (code <= maxRunLengthPrefix) {
+ BitReader.fillBitWindow(s);
+ int reps = (1 << code) + BitReader.readFewBits(s, 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++;
+ }
+ }
+ BitReader.fillBitWindow(s);
+ if (BitReader.readFewBits(s, 1) == 1) {
+ inverseMoveToFrontTransform(contextMap, contextMapSize);
+ }
+ return numTrees;
+ }
+
+ private static int decodeBlockTypeAndLength(State s, int treeType, int numBlockTypes) {
+ final int[] ringBuffers = s.rings;
+ final int offset = 4 + treeType * 2;
+ BitReader.fillBitWindow(s);
+ int blockType = readSymbol(s.blockTrees, 2 * treeType, s);
+ final int result = readBlockLength(s.blockTrees, 2 * treeType + 1, s);
+
+ if (blockType == 1) {
+ blockType = ringBuffers[offset + 1] + 1;
+ } else if (blockType == 0) {
+ blockType = ringBuffers[offset];
+ } else {
+ blockType -= 2;
+ }
+ if (blockType >= numBlockTypes) {
+ blockType -= numBlockTypes;
+ }
+ ringBuffers[offset] = ringBuffers[offset + 1];
+ ringBuffers[offset + 1] = blockType;
+ return result;
+ }
+
+ private static void decodeLiteralBlockSwitch(State s) {
+ s.literalBlockLength = decodeBlockTypeAndLength(s, 0, s.numLiteralBlockTypes);
+ final int literalBlockType = s.rings[5];
+ s.contextMapSlice = literalBlockType << LITERAL_CONTEXT_BITS;
+ s.literalTreeIdx = s.contextMap[s.contextMapSlice] & 0xFF;
+ final int contextMode = s.contextModes[literalBlockType];
+ s.contextLookupOffset1 = contextMode << 9;
+ s.contextLookupOffset2 = s.contextLookupOffset1 + 256;
+ }
+
+ private static void decodeCommandBlockSwitch(State s) {
+ s.commandBlockLength = decodeBlockTypeAndLength(s, 1, s.numCommandBlockTypes);
+ s.commandTreeIdx = s.rings[7];
+ }
+
+ private static void decodeDistanceBlockSwitch(State s) {
+ s.distanceBlockLength = decodeBlockTypeAndLength(s, 2, s.numDistanceBlockTypes);
+ s.distContextMapSlice = s.rings[9] << DISTANCE_CONTEXT_BITS;
+ }
+
+ private static void maybeReallocateRingBuffer(State s) {
+ int newSize = s.maxRingBufferSize;
+ if (newSize > s.expectedTotalSize) {
+ /* TODO(eustas): Handle 2GB+ cases more gracefully. */
+ final int minimalNewSize = s.expectedTotalSize;
+ while ((newSize >> 1) > minimalNewSize) {
+ newSize >>= 1;
+ }
+ if ((s.inputEnd == 0) && newSize < 16384 && s.maxRingBufferSize >= 16384) {
+ newSize = 16384;
+ }
+ }
+ if (newSize <= s.ringBufferSize) {
+ return;
+ }
+ final int ringBufferSizeWithSlack = newSize + MAX_TRANSFORMED_WORD_LENGTH;
+ final byte[] newBuffer = new byte[ringBufferSizeWithSlack];
+ if (s.ringBuffer.length != 0) {
+ System.arraycopy(s.ringBuffer, 0, newBuffer, 0, s.ringBufferSize);
+ }
+ s.ringBuffer = newBuffer;
+ s.ringBufferSize = newSize;
+ }
+
+ private static void readNextMetablockHeader(State s) {
+ if (s.inputEnd != 0) {
+ s.nextRunningState = FINISHED;
+ s.runningState = INIT_WRITE;
+ return;
+ }
+ // TODO(eustas): Reset? Do we need this?
+ s.literalTreeGroup = new int[0];
+ s.commandTreeGroup = new int[0];
+ s.distanceTreeGroup = new int[0];
+
+ BitReader.readMoreInput(s);
+ decodeMetaBlockLength(s);
+ if ((s.metaBlockLength == 0) && (s.isMetadata == 0)) {
+ return;
+ }
+ if ((s.isUncompressed != 0) || (s.isMetadata != 0)) {
+ BitReader.jumpToByteBoundary(s);
+ s.runningState = (s.isMetadata != 0) ? READ_METADATA : COPY_UNCOMPRESSED;
+ } else {
+ s.runningState = COMPRESSED_BLOCK_START;
+ }
+
+ if (s.isMetadata != 0) {
+ return;
+ }
+ s.expectedTotalSize += s.metaBlockLength;
+ if (s.expectedTotalSize > 1 << 30) {
+ s.expectedTotalSize = 1 << 30;
+ }
+ if (s.ringBufferSize < s.maxRingBufferSize) {
+ maybeReallocateRingBuffer(s);
+ }
+ }
+
+ private static int readMetablockPartition(State s, int treeType, int numBlockTypes) {
+ int offset = s.blockTrees[2 * treeType];
+ if (numBlockTypes <= 1) {
+ s.blockTrees[2 * treeType + 1] = offset;
+ s.blockTrees[2 * treeType + 2] = offset;
+ return 1 << 28;
+ }
+
+ final int blockTypeAlphabetSize = numBlockTypes + 2;
+ offset += readHuffmanCode(
+ blockTypeAlphabetSize, blockTypeAlphabetSize, s.blockTrees, 2 * treeType, s);
+ s.blockTrees[2 * treeType + 1] = offset;
+
+ final int blockLengthAlphabetSize = NUM_BLOCK_LENGTH_CODES;
+ offset += readHuffmanCode(
+ blockLengthAlphabetSize, blockLengthAlphabetSize, s.blockTrees, 2 * treeType + 1, s);
+ s.blockTrees[2 * treeType + 2] = offset;
+
+ return readBlockLength(s.blockTrees, 2 * treeType + 1, s);
+ }
+
+ private static void calculateDistanceLut(State s, int alphabetSizeLimit) {
+ final byte[] distExtraBits = s.distExtraBits;
+ final int[] distOffset = s.distOffset;
+ final int npostfix = s.distancePostfixBits;
+ final int ndirect = s.numDirectDistanceCodes;
+ final int postfix = 1 << npostfix;
+ int bits = 1;
+ int half = 0;
+
+ /* Skip short codes. */
+ int i = NUM_DISTANCE_SHORT_CODES;
+
+ /* Fill direct codes. */
+ for (int j = 0; j < ndirect; ++j) {
+ distExtraBits[i] = 0;
+ distOffset[i] = j + 1;
+ ++i;
+ }
+
+ /* Fill regular distance codes. */
+ while (i < alphabetSizeLimit) {
+ final int base = ndirect + ((((2 + half) << bits) - 4) << npostfix) + 1;
+ /* Always fill the complete group. */
+ for (int j = 0; j < postfix; ++j) {
+ distExtraBits[i] = (byte) bits;
+ distOffset[i] = base + j;
+ ++i;
+ }
+ bits = bits + half;
+ half = half ^ 1;
+ }
+ }
+
+ private static void readMetablockHuffmanCodesAndContextMaps(State s) {
+ s.numLiteralBlockTypes = decodeVarLenUnsignedByte(s) + 1;
+ s.literalBlockLength = readMetablockPartition(s, 0, s.numLiteralBlockTypes);
+ s.numCommandBlockTypes = decodeVarLenUnsignedByte(s) + 1;
+ s.commandBlockLength = readMetablockPartition(s, 1, s.numCommandBlockTypes);
+ s.numDistanceBlockTypes = decodeVarLenUnsignedByte(s) + 1;
+ s.distanceBlockLength = readMetablockPartition(s, 2, s.numDistanceBlockTypes);
+
+ BitReader.readMoreInput(s);
+ BitReader.fillBitWindow(s);
+ s.distancePostfixBits = BitReader.readFewBits(s, 2);
+ s.numDirectDistanceCodes = BitReader.readFewBits(s, 4) << s.distancePostfixBits;
+ // TODO(eustas): Reuse?
+ s.contextModes = new byte[s.numLiteralBlockTypes];
+ for (int i = 0; i < s.numLiteralBlockTypes;) {
+ /* Ensure that less than 256 bits read between readMoreInput. */
+ final int limit = Math.min(i + 96, s.numLiteralBlockTypes);
+ for (; i < limit; ++i) {
+ BitReader.fillBitWindow(s);
+ s.contextModes[i] = (byte) BitReader.readFewBits(s, 2);
+ }
+ BitReader.readMoreInput(s);
+ }
+
+ // TODO(eustas): Reuse?
+ s.contextMap = new byte[s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS];
+ final int numLiteralTrees = decodeContextMap(s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS,
+ s.contextMap, s);
+ s.trivialLiteralContext = 1;
+ for (int j = 0; j < s.numLiteralBlockTypes << LITERAL_CONTEXT_BITS; j++) {
+ if (s.contextMap[j] != j >> LITERAL_CONTEXT_BITS) {
+ s.trivialLiteralContext = 0;
+ break;
+ }
+ }
+
+ // TODO(eustas): Reuse?
+ s.distContextMap = new byte[s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS];
+ final int numDistTrees = decodeContextMap(s.numDistanceBlockTypes << DISTANCE_CONTEXT_BITS,
+ s.distContextMap, s);
+
+ s.literalTreeGroup = decodeHuffmanTreeGroup(NUM_LITERAL_CODES, NUM_LITERAL_CODES,
+ numLiteralTrees, s);
+ s.commandTreeGroup = decodeHuffmanTreeGroup(NUM_COMMAND_CODES, NUM_COMMAND_CODES,
+ s.numCommandBlockTypes, s);
+ int distanceAlphabetSizeMax = calculateDistanceAlphabetSize(
+ s.distancePostfixBits, s.numDirectDistanceCodes, MAX_DISTANCE_BITS);
+ int distanceAlphabetSizeLimit = distanceAlphabetSizeMax;
+ if (s.isLargeWindow == 1) {
+ distanceAlphabetSizeMax = calculateDistanceAlphabetSize(
+ s.distancePostfixBits, s.numDirectDistanceCodes, MAX_LARGE_WINDOW_DISTANCE_BITS);
+ distanceAlphabetSizeLimit = calculateDistanceAlphabetLimit(
+ MAX_ALLOWED_DISTANCE, s.distancePostfixBits, s.numDirectDistanceCodes);
+ }
+ s.distanceTreeGroup = decodeHuffmanTreeGroup(distanceAlphabetSizeMax, distanceAlphabetSizeLimit,
+ numDistTrees, s);
+ calculateDistanceLut(s, distanceAlphabetSizeLimit);
+
+ s.contextMapSlice = 0;
+ s.distContextMapSlice = 0;
+ s.contextLookupOffset1 = s.contextModes[0] * 512;
+ s.contextLookupOffset2 = s.contextLookupOffset1 + 256;
+ s.literalTreeIdx = 0;
+ s.commandTreeIdx = 0;
+
+ s.rings[4] = 1;
+ s.rings[5] = 0;
+ s.rings[6] = 1;
+ s.rings[7] = 0;
+ s.rings[8] = 1;
+ s.rings[9] = 0;
+ }
+
+ private static void copyUncompressedData(State s) {
+ final byte[] ringBuffer = s.ringBuffer;
+
+ // Could happen if block ends at ring buffer end.
+ if (s.metaBlockLength <= 0) {
+ BitReader.reload(s);
+ s.runningState = BLOCK_START;
+ return;
+ }
+
+ final int chunkLength = Math.min(s.ringBufferSize - s.pos, s.metaBlockLength);
+ BitReader.copyRawBytes(s, ringBuffer, s.pos, chunkLength);
+ s.metaBlockLength -= chunkLength;
+ s.pos += chunkLength;
+ if (s.pos == s.ringBufferSize) {
+ s.nextRunningState = COPY_UNCOMPRESSED;
+ s.runningState = INIT_WRITE;
+ return;
+ }
+
+ BitReader.reload(s);
+ s.runningState = BLOCK_START;
+ }
+
+ private static int writeRingBuffer(State s) {
+ final int toWrite = Math.min(s.outputLength - s.outputUsed,
+ s.ringBufferBytesReady - s.ringBufferBytesWritten);
+ // TODO(eustas): DCHECK(toWrite >= 0)
+ if (toWrite != 0) {
+ System.arraycopy(s.ringBuffer, s.ringBufferBytesWritten, s.output,
+ s.outputOffset + s.outputUsed, toWrite);
+ s.outputUsed += toWrite;
+ s.ringBufferBytesWritten += toWrite;
+ }
+
+ if (s.outputUsed < s.outputLength) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private static int[] decodeHuffmanTreeGroup(int alphabetSizeMax, int alphabetSizeLimit,
+ int n, State s) {
+ final int maxTableSize = MAX_HUFFMAN_TABLE_SIZE[(alphabetSizeLimit + 31) >> 5];
+ final int[] group = new int[n + n * maxTableSize];
+ int next = n;
+ for (int i = 0; i < n; ++i) {
+ group[i] = next;
+ next += readHuffmanCode(alphabetSizeMax, alphabetSizeLimit, group, i, s);
+ }
+ return group;
+ }
+
+ // Returns offset in ringBuffer that should trigger WRITE when filled.
+ private static int calculateFence(State s) {
+ int result = s.ringBufferSize;
+ if (s.isEager != 0) {
+ result = Math.min(result, s.ringBufferBytesWritten + s.outputLength - s.outputUsed);
+ }
+ return result;
+ }
+
+ private static void doUseDictionary(State s, int fence) {
+ if (s.distance > MAX_ALLOWED_DISTANCE) {
+ throw new BrotliRuntimeException("Invalid backward reference");
+ }
+ final int address = s.distance - s.maxDistance - 1 - s.cdTotalSize;
+ if (address < 0) {
+ initializeCompoundDictionaryCopy(s, -address - 1, s.copyLength);
+ s.runningState = COPY_FROM_COMPOUND_DICTIONARY;
+ } else {
+ // Force lazy dictionary initialization.
+ final ByteBuffer dictionaryData = Dictionary.getData();
+ final int wordLength = s.copyLength;
+ if (wordLength > Dictionary.MAX_DICTIONARY_WORD_LENGTH) {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ final int shift = Dictionary.sizeBits[wordLength];
+ if (shift == 0) {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ int offset = Dictionary.offsets[wordLength];
+ final int mask = (1 << shift) - 1;
+ final int wordIdx = address & mask;
+ final int transformIdx = address >>> shift;
+ offset += wordIdx * wordLength;
+ final Transform.Transforms transforms = Transform.RFC_TRANSFORMS;
+ if (transformIdx >= transforms.numTransforms) {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ final int len = Transform.transformDictionaryWord(s.ringBuffer, s.pos, dictionaryData,
+ offset, wordLength, transforms, transformIdx);
+ s.pos += len;
+ s.metaBlockLength -= len;
+ if (s.pos >= fence) {
+ s.nextRunningState = MAIN_LOOP;
+ s.runningState = INIT_WRITE;
+ return;
+ }
+ s.runningState = MAIN_LOOP;
+ }
+ }
+
+ private static void initializeCompoundDictionary(State s) {
+ s.cdBlockMap = new byte[1 << CD_BLOCK_MAP_BITS];
+ int blockBits = CD_BLOCK_MAP_BITS;
+ // If this function is executed, then s.cdTotalSize > 0.
+ while (((s.cdTotalSize - 1) >>> blockBits) != 0) {
+ blockBits++;
+ }
+ blockBits -= CD_BLOCK_MAP_BITS;
+ s.cdBlockBits = blockBits;
+ int cursor = 0;
+ int index = 0;
+ while (cursor < s.cdTotalSize) {
+ while (s.cdChunkOffsets[index + 1] < cursor) {
+ index++;
+ }
+ s.cdBlockMap[cursor >>> blockBits] = (byte) index;
+ cursor += 1 << blockBits;
+ }
+ }
+
+ private static void initializeCompoundDictionaryCopy(State s, int address, int length) {
+ if (s.cdBlockBits == -1) {
+ initializeCompoundDictionary(s);
+ }
+ int index = s.cdBlockMap[address >>> s.cdBlockBits];
+ while (address >= s.cdChunkOffsets[index + 1]) {
+ index++;
+ }
+ if (s.cdTotalSize > address + length) {
+ throw new BrotliRuntimeException("Invalid backward reference");
+ }
+ /* Update the recent distances cache */
+ s.distRbIdx = (s.distRbIdx + 1) & 0x3;
+ s.rings[s.distRbIdx] = s.distance;
+ s.metaBlockLength -= length;
+ s.cdBrIndex = index;
+ s.cdBrOffset = address - s.cdChunkOffsets[index];
+ s.cdBrLength = length;
+ s.cdBrCopied = 0;
+ }
+
+ private static int copyFromCompoundDictionary(State s, int fence) {
+ int pos = s.pos;
+ final int origPos = pos;
+ while (s.cdBrLength != s.cdBrCopied) {
+ final int space = fence - pos;
+ final int chunkLength = s.cdChunkOffsets[s.cdBrIndex + 1] - s.cdChunkOffsets[s.cdBrIndex];
+ final int remChunkLength = chunkLength - s.cdBrOffset;
+ int length = s.cdBrLength - s.cdBrCopied;
+ if (length > remChunkLength) {
+ length = remChunkLength;
+ }
+ if (length > space) {
+ length = space;
+ }
+ Utils.copyBytes(
+ s.ringBuffer, pos, s.cdChunks[s.cdBrIndex], s.cdBrOffset, s.cdBrOffset + length);
+ pos += length;
+ s.cdBrOffset += length;
+ s.cdBrCopied += length;
+ if (length == remChunkLength) {
+ s.cdBrIndex++;
+ s.cdBrOffset = 0;
+ }
+ if (pos >= fence) {
+ break;
+ }
+ }
+ return pos - origPos;
+ }
+
+ /**
+ * Actual decompress implementation.
+ */
+ static void decompress(State s) {
+ if (s.runningState == UNINITIALIZED) {
+ throw new IllegalStateException("Can't decompress until initialized");
+ }
+ if (s.runningState == CLOSED) {
+ throw new IllegalStateException("Can't decompress after close");
+ }
+ if (s.runningState == INITIALIZED) {
+ final int windowBits = decodeWindowBits(s);
+ if (windowBits == -1) { /* Reserved case for future expansion. */
+ throw new BrotliRuntimeException("Invalid 'windowBits' code");
+ }
+ s.maxRingBufferSize = 1 << windowBits;
+ s.maxBackwardDistance = s.maxRingBufferSize - 16;
+ s.runningState = BLOCK_START;
+ }
+
+ int fence = calculateFence(s);
+ int ringBufferMask = s.ringBufferSize - 1;
+ byte[] ringBuffer = s.ringBuffer;
+
+ while (s.runningState != FINISHED) {
+ // TODO(eustas): extract cases to methods for the better readability.
+ switch (s.runningState) {
+ case BLOCK_START:
+ if (s.metaBlockLength < 0) {
+ throw new BrotliRuntimeException("Invalid metablock length");
+ }
+ readNextMetablockHeader(s);
+ /* Ring-buffer would be reallocated here. */
+ fence = calculateFence(s);
+ ringBufferMask = s.ringBufferSize - 1;
+ ringBuffer = s.ringBuffer;
+ continue;
+
+ case COMPRESSED_BLOCK_START:
+ readMetablockHuffmanCodesAndContextMaps(s);
+ s.runningState = MAIN_LOOP;
+
+ // fall through
+ case MAIN_LOOP:
+ if (s.metaBlockLength <= 0) {
+ s.runningState = BLOCK_START;
+ continue;
+ }
+ BitReader.readMoreInput(s);
+ if (s.commandBlockLength == 0) {
+ decodeCommandBlockSwitch(s);
+ }
+ s.commandBlockLength--;
+ BitReader.fillBitWindow(s);
+ final int cmdCode = readSymbol(s.commandTreeGroup, s.commandTreeIdx, s) << 2;
+ final short insertAndCopyExtraBits = CMD_LOOKUP[cmdCode];
+ final int insertLengthOffset = CMD_LOOKUP[cmdCode + 1];
+ final int copyLengthOffset = CMD_LOOKUP[cmdCode + 2];
+ s.distanceCode = CMD_LOOKUP[cmdCode + 3];
+ BitReader.fillBitWindow(s);
+ {
+ final int insertLengthExtraBits = insertAndCopyExtraBits & 0xFF;
+ s.insertLength = insertLengthOffset + BitReader.readBits(s, insertLengthExtraBits);
+ }
+ BitReader.fillBitWindow(s);
+ {
+ final int copyLengthExtraBits = insertAndCopyExtraBits >> 8;
+ s.copyLength = copyLengthOffset + BitReader.readBits(s, copyLengthExtraBits);
+ }
+
+ s.j = 0;
+ s.runningState = INSERT_LOOP;
+
+ // fall through
+ case INSERT_LOOP:
+ if (s.trivialLiteralContext != 0) {
+ while (s.j < s.insertLength) {
+ BitReader.readMoreInput(s);
+ if (s.literalBlockLength == 0) {
+ decodeLiteralBlockSwitch(s);
+ }
+ s.literalBlockLength--;
+ BitReader.fillBitWindow(s);
+ ringBuffer[s.pos] = (byte) readSymbol(s.literalTreeGroup, s.literalTreeIdx, s);
+ s.pos++;
+ s.j++;
+ if (s.pos >= fence) {
+ s.nextRunningState = INSERT_LOOP;
+ s.runningState = INIT_WRITE;
+ break;
+ }
+ }
+ } else {
+ int prevByte1 = ringBuffer[(s.pos - 1) & ringBufferMask] & 0xFF;
+ int prevByte2 = ringBuffer[(s.pos - 2) & ringBufferMask] & 0xFF;
+ while (s.j < s.insertLength) {
+ BitReader.readMoreInput(s);
+ if (s.literalBlockLength == 0) {
+ decodeLiteralBlockSwitch(s);
+ }
+ final int literalContext = Context.LOOKUP[s.contextLookupOffset1 + prevByte1]
+ | Context.LOOKUP[s.contextLookupOffset2 + prevByte2];
+ final int literalTreeIdx = s.contextMap[s.contextMapSlice + literalContext] & 0xFF;
+ s.literalBlockLength--;
+ prevByte2 = prevByte1;
+ BitReader.fillBitWindow(s);
+ prevByte1 = readSymbol(s.literalTreeGroup, literalTreeIdx, s);
+ ringBuffer[s.pos] = (byte) prevByte1;
+ s.pos++;
+ s.j++;
+ if (s.pos >= fence) {
+ s.nextRunningState = INSERT_LOOP;
+ s.runningState = INIT_WRITE;
+ break;
+ }
+ }
+ }
+ if (s.runningState != INSERT_LOOP) {
+ continue;
+ }
+ s.metaBlockLength -= s.insertLength;
+ if (s.metaBlockLength <= 0) {
+ s.runningState = MAIN_LOOP;
+ continue;
+ }
+ int distanceCode = s.distanceCode;
+ if (distanceCode < 0) {
+ // distanceCode in untouched; assigning it 0 won't affect distance ring buffer rolling.
+ s.distance = s.rings[s.distRbIdx];
+ } else {
+ BitReader.readMoreInput(s);
+ if (s.distanceBlockLength == 0) {
+ decodeDistanceBlockSwitch(s);
+ }
+ s.distanceBlockLength--;
+ BitReader.fillBitWindow(s);
+ final int distTreeIdx = s.distContextMap[s.distContextMapSlice + distanceCode] & 0xFF;
+ distanceCode = readSymbol(s.distanceTreeGroup, distTreeIdx, s);
+ if (distanceCode < NUM_DISTANCE_SHORT_CODES) {
+ final int index =
+ (s.distRbIdx + DISTANCE_SHORT_CODE_INDEX_OFFSET[distanceCode]) & 0x3;
+ s.distance = s.rings[index] + DISTANCE_SHORT_CODE_VALUE_OFFSET[distanceCode];
+ if (s.distance < 0) {
+ throw new BrotliRuntimeException("Negative distance"); // COV_NF_LINE
+ }
+ } else {
+ final int extraBits = s.distExtraBits[distanceCode];
+ int bits;
+ if (s.bitOffset + extraBits <= BitReader.BITNESS) {
+ bits = BitReader.readFewBits(s, extraBits);
+ } else {
+ BitReader.fillBitWindow(s);
+ bits = BitReader.readBits(s, extraBits);
+ }
+ s.distance = s.distOffset[distanceCode] + (bits << s.distancePostfixBits);
+ }
+ }
+
+ if (s.maxDistance != s.maxBackwardDistance
+ && s.pos < s.maxBackwardDistance) {
+ s.maxDistance = s.pos;
+ } else {
+ s.maxDistance = s.maxBackwardDistance;
+ }
+
+ if (s.distance > s.maxDistance) {
+ s.runningState = USE_DICTIONARY;
+ continue;
+ }
+
+ if (distanceCode > 0) {
+ s.distRbIdx = (s.distRbIdx + 1) & 0x3;
+ s.rings[s.distRbIdx] = s.distance;
+ }
+
+ if (s.copyLength > s.metaBlockLength) {
+ throw new BrotliRuntimeException("Invalid backward reference"); // COV_NF_LINE
+ }
+ s.j = 0;
+ s.runningState = COPY_LOOP;
+
+ // fall through
+ case COPY_LOOP:
+ int src = (s.pos - s.distance) & ringBufferMask;
+ int dst = s.pos;
+ final int copyLength = s.copyLength - s.j;
+ final int srcEnd = src + copyLength;
+ final int dstEnd = dst + copyLength;
+ if ((srcEnd < ringBufferMask) && (dstEnd < ringBufferMask)) {
+ if (copyLength < 12 || (srcEnd > dst && dstEnd > src)) {
+ for (int k = 0; k < copyLength; k += 4) {
+ ringBuffer[dst++] = ringBuffer[src++];
+ ringBuffer[dst++] = ringBuffer[src++];
+ ringBuffer[dst++] = ringBuffer[src++];
+ ringBuffer[dst++] = ringBuffer[src++];
+ }
+ } else {
+ Utils.copyBytesWithin(ringBuffer, dst, src, srcEnd);
+ }
+ s.j += copyLength;
+ s.metaBlockLength -= copyLength;
+ s.pos += copyLength;
+ } else {
+ for (; s.j < s.copyLength;) {
+ ringBuffer[s.pos] =
+ ringBuffer[(s.pos - s.distance) & ringBufferMask];
+ s.metaBlockLength--;
+ s.pos++;
+ s.j++;
+ if (s.pos >= fence) {
+ s.nextRunningState = COPY_LOOP;
+ s.runningState = INIT_WRITE;
+ break;
+ }
+ }
+ }
+ if (s.runningState == COPY_LOOP) {
+ s.runningState = MAIN_LOOP;
+ }
+ continue;
+
+ case USE_DICTIONARY:
+ doUseDictionary(s, fence);
+ continue;
+
+ case COPY_FROM_COMPOUND_DICTIONARY:
+ s.pos += copyFromCompoundDictionary(s, fence);
+ if (s.pos >= fence) {
+ s.nextRunningState = COPY_FROM_COMPOUND_DICTIONARY;
+ s.runningState = INIT_WRITE;
+ return;
+ }
+ s.runningState = MAIN_LOOP;
+ continue;
+
+ case READ_METADATA:
+ while (s.metaBlockLength > 0) {
+ BitReader.readMoreInput(s);
+ // Optimize
+ BitReader.fillBitWindow(s);
+ BitReader.readFewBits(s, 8);
+ s.metaBlockLength--;
+ }
+ s.runningState = BLOCK_START;
+ continue;
+
+ case COPY_UNCOMPRESSED:
+ copyUncompressedData(s);
+ continue;
+
+ case INIT_WRITE:
+ s.ringBufferBytesReady = Math.min(s.pos, s.ringBufferSize);
+ s.runningState = WRITE;
+
+ // fall through
+ case WRITE:
+ if (writeRingBuffer(s) == 0) {
+ // Output buffer is full.
+ return;
+ }
+ if (s.pos >= s.maxBackwardDistance) {
+ s.maxDistance = s.maxBackwardDistance;
+ }
+ // Wrap the ringBuffer.
+ if (s.pos >= s.ringBufferSize) {
+ if (s.pos > s.ringBufferSize) {
+ Utils.copyBytesWithin(ringBuffer, 0, s.ringBufferSize, s.pos);
+ }
+ s.pos &= ringBufferMask;
+ s.ringBufferBytesWritten = 0;
+ }
+ s.runningState = s.nextRunningState;
+ continue;
+
+ default:
+ throw new BrotliRuntimeException("Unexpected state " + String.valueOf(s.runningState));
+ }
+ }
+ if (s.runningState == FINISHED) {
+ if (s.metaBlockLength < 0) {
+ throw new BrotliRuntimeException("Invalid metablock length");
+ }
+ BitReader.jumpToByteBoundary(s);
+ BitReader.checkHealth(s, 1);
+ }
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/Decoder.java b/firka_wear/android/app/src/main/java/org/brotli/dec/Decoder.java
new file mode 100644
index 00000000..e33f5a9a
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/Decoder.java
@@ -0,0 +1,72 @@
+package org.brotli.dec;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Decoder {
+ private static long decodeBytes(InputStream input, OutputStream output, byte[] buffer)
+ throws IOException {
+ long totalOut = 0;
+ int readBytes;
+ BrotliInputStream in = new BrotliInputStream(input);
+ in.enableLargeWindow();
+ try {
+ while ((readBytes = in.read(buffer)) >= 0) {
+ output.write(buffer, 0, readBytes);
+ totalOut += readBytes;
+ }
+ } finally {
+ in.close();
+ }
+ return totalOut;
+ }
+
+ private static void decompress(String fromPath, String toPath, byte[] buffer) throws IOException {
+ long start;
+ long bytesDecoded;
+ long end;
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = new FileInputStream(fromPath);
+ out = new FileOutputStream(toPath);
+ start = System.nanoTime();
+ bytesDecoded = decodeBytes(in, out, buffer);
+ end = System.nanoTime();
+ } finally {
+ if (in != null) {
+ in.close(); // Hopefully, does not throw exception.
+ }
+ if (out != null) {
+ out.close();
+ }
+ }
+
+ double timeDelta = (end - start) / 1000000000.0;
+ if (timeDelta <= 0) {
+ return;
+ }
+ double mbDecoded = bytesDecoded / (1024.0 * 1024.0);
+ System.out.println(mbDecoded / timeDelta + " MiB/s");
+ }
+
+ public static void main(String... args) throws IOException {
+ if (args.length != 2 && args.length != 3) {
+ System.out.println("Usage: decoder Dictionary content is loaded from binary resource when {@link #getData()} is executed for the
+ * first time. Consequently, it saves memory and CPU in case dictionary is not required.
+ *
+ * One possible drawback is that multiple threads that need dictionary data may be blocked (only
+ * once in each classworld). To avoid this, it is enough to call {@link #getData()} proactively.
+ */
+public final class Dictionary {
+ static final int MIN_DICTIONARY_WORD_LENGTH = 4;
+ static final int MAX_DICTIONARY_WORD_LENGTH = 31;
+
+ private static ByteBuffer data = ByteBuffer.allocateDirect(0);
+ static final int[] offsets = new int[32];
+ static final int[] sizeBits = new int[32];
+
+ private static class DataLoader {
+ static final boolean OK;
+
+ static {
+ boolean ok = true;
+ try {
+ Class.forName(Dictionary.class.getPackage().getName() + ".DictionaryData");
+ } catch (Throwable ex) {
+ ok = false;
+ }
+ OK = ok;
+ }
+ }
+
+ public static void setData(ByteBuffer newData, int[] newSizeBits) {
+ if ((Utils.isDirect(newData) == 0) || (Utils.isReadOnly(newData) == 0)) {
+ throw new BrotliRuntimeException("newData must be a direct read-only byte buffer");
+ }
+ // TODO: is that so?
+ if (newSizeBits.length > MAX_DICTIONARY_WORD_LENGTH) {
+ throw new BrotliRuntimeException(
+ "sizeBits length must be at most " + String.valueOf(MAX_DICTIONARY_WORD_LENGTH));
+ }
+ for (int i = 0; i < MIN_DICTIONARY_WORD_LENGTH; ++i) {
+ if (newSizeBits[i] != 0) {
+ throw new BrotliRuntimeException(
+ "first " + String.valueOf(MIN_DICTIONARY_WORD_LENGTH) + " must be 0");
+ }
+ }
+ final int[] dictionaryOffsets = Dictionary.offsets;
+ final int[] dictionarySizeBits = Dictionary.sizeBits;
+ System.arraycopy(newSizeBits, 0, dictionarySizeBits, 0, newSizeBits.length);
+ int pos = 0;
+ final int limit = newData.capacity();
+ for (int i = 0; i < newSizeBits.length; ++i) {
+ dictionaryOffsets[i] = pos;
+ final int bits = dictionarySizeBits[i];
+ if (bits != 0) {
+ if (bits >= 31) {
+ throw new BrotliRuntimeException("newSizeBits values must be less than 31");
+ }
+ pos += i << bits;
+ if (pos <= 0 || pos > limit) {
+ throw new BrotliRuntimeException("newSizeBits is inconsistent: overflow");
+ }
+ }
+ }
+ for (int i = newSizeBits.length; i < 32; ++i) {
+ dictionaryOffsets[i] = pos;
+ }
+ if (pos != limit) {
+ throw new BrotliRuntimeException("newSizeBits is inconsistent: underflow");
+ }
+ Dictionary.data = newData;
+ }
+
+ public static ByteBuffer getData() {
+ if (data.capacity() != 0) {
+ return data;
+ }
+ if (!DataLoader.OK) {
+ throw new BrotliRuntimeException("brotli dictionary is not set");
+ }
+ /* Might have been set when {@link DictionaryData} was loaded.*/
+ return data;
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/DictionaryData.java b/firka_wear/android/app/src/main/java/org/brotli/dec/DictionaryData.java
new file mode 100644
index 00000000..ad96f38e
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/DictionaryData.java
@@ -0,0 +1,75 @@
+/* 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.nio.ByteBuffer;
+
+/**
+ * Built-in dictionary data.
+ *
+ * When this class is loaded, it sets its data: {@link Dictionary#setData(ByteBuffer)}.
+ */
+final class DictionaryData {
+ private static final String DATA0 = "wjnfgltmojefofewab`h`lgfgbwbpkltlmozpjwf`jwzlsfmivpwojhfeqfftlqhwf{wzfbqlufqalgzolufelqnallhsobzojufojmfkfosklnfpjgfnlqftlqgolmdwkfnujftejmgsbdfgbzpevookfbgwfqnfb`kbqfbeqlnwqvfnbqhbaofvslmkjdkgbwfobmgmftpfufmmf{w`bpfalwkslpwvpfgnbgfkbmgkfqftkbwmbnfOjmhaoldpjyfabpfkfognbhfnbjmvpfq$*#(klogfmgptjwkMftpqfbgtfqfpjdmwbhfkbufdbnfpffm`boosbwktfoosovpnfmvejonsbqwiljmwkjpojpwdllgmffgtbzptfpwilapnjmgboploldlqj`kvpfpobpwwfbnbqnzellghjmdtjoofbpwtbqgafpwejqfSbdfhmltbtbz-smdnlufwkbmolbgdjufpfoemlwfnv`keffgnbmzql`hj`lmlm`follhkjgfgjfgKlnfqvofklpwbib{jmel`ovaobtpofppkboeplnfpv`kylmf233&lmfp`bqfWjnfqb`faovfelvqtffheb`fklsfdbufkbqgolpwtkfmsbqhhfswsbpppkjsqllnKWNOsobmWzsfglmfpbufhffseobdojmhplogejufwllhqbwfwltmivnswkvpgbqh`bqgejofefbqpwbzhjoowkbweboobvwlfufq-`lnwbohpklsulwfgffsnlgfqfpwwvqmalqmabmgefooqlpfvqo+phjmqlof`lnfb`wpbdfpnffwdlog-isdjwfnubqzefowwkfmpfmggqlsUjft`lsz2-3!?,b=pwlsfopfojfpwlvqsb`h-djesbpw`pp !2s{#plojg# -dje!#bow>!wqbmpsbqfmwjmelqnbwjlmbssoj`bwjlm!#lm`oj`h>!fpwbaojpkfgbgufqwjpjmd-smd!#bow>!fmujqlmnfmwsfqelqnbm`fbssqlsqjbwf%bns8ngbpk8jnnfgjbwfoz?,pwqlmd=?,qbwkfq#wkbmwfnsfqbwvqfgfufolsnfmw`lnsfwjwjlmsob`fklogfqujpjajojwz9`lszqjdkw!=3!#kfjdkw>!fufm#wklvdkqfsob`fnfmwgfpwjmbwjlm@lqslqbwjlm?vo#`obpp>!Bppl`jbwjlmjmgjujgvbopsfqpsf`wjufpfwWjnflvw+vqo+kwws9,,nbwkfnbwj`pnbqdjm.wls9fufmwvbooz#gfp`qjswjlm*#ml.qfsfbw`loof`wjlmp-ISD\u007Fwkvna\u007Fsbqwj`jsbwf,kfbg=?algzeolbw9ofew8?oj#`obpp>!kvmgqfgp#le\t\tKltfufq/#`lnslpjwjlm`ofbq9alwk8`llsfqbwjlmtjwkjm#wkf#obafo#elq>!alqgfq.wls9Mft#Yfbobmgqf`lnnfmgfgsklwldqbskzjmwfqfpwjmd%ow8pvs%dw8`lmwqlufqpzMfwkfqobmgpbowfqmbwjufnb{ofmdwk>!ptjwyfqobmgGfufolsnfmwfppfmwjbooz\t\tBowklvdk#?,wf{wbqfb=wkvmgfqajqgqfsqfpfmwfg%bns8mgbpk8psf`vobwjlm`lnnvmjwjfpofdjpobwjlmfof`wqlmj`p\t\n?gju#jg>!joovpwqbwfgfmdjmffqjmdwfqqjwlqjfpbvwklqjwjfpgjpwqjavwfg5!#kfjdkw>!pbmp.pfqje8`bsbaof#le#gjpbssfbqfgjmwfqb`wjufollhjmd#elqjw#tlvog#afBedkbmjpwbmtbp#`qfbwfgNbwk-eollq+pvqqlvmgjmd`bm#bopl#aflapfqubwjlmnbjmwfmbm`ffm`lvmwfqfg?k1#`obpp>!nlqf#qf`fmwjw#kbp#affmjmubpjlm#le*-dfwWjnf+*evmgbnfmwboGfpsjwf#wkf!=?gju#jg>!jmpsjqbwjlmf{bnjmbwjlmsqfsbqbwjlmf{sobmbwjlm?jmsvw#jg>!?,b=?,psbm=ufqpjlmp#lejmpwqvnfmwpafelqf#wkf##>#$kwws9,,Gfp`qjswjlmqfobwjufoz#-pvapwqjmd+fb`k#le#wkff{sfqjnfmwpjmeovfmwjbojmwfdqbwjlmnbmz#sflsofgvf#wl#wkf#`lnajmbwjlmgl#mlw#kbufNjggof#Fbpw?mlp`qjsw=?`lszqjdkw!#sfqkbsp#wkfjmpwjwvwjlmjm#Gf`fnafqbqqbmdfnfmwnlpw#ebnlvpsfqplmbojwz`qfbwjlm#leojnjwbwjlmpf{`ovpjufozplufqfjdmwz.`lmwfmw!=\t?wg#`obpp>!vmgfqdqlvmgsbqboofo#wlgl`wqjmf#lel``vsjfg#azwfqnjmloldzQfmbjppbm`fb#mvnafq#lepvsslqw#elqf{solqbwjlmqf`ldmjwjlmsqfgf`fpplq?jnd#pq`>!,?k2#`obpp>!svaoj`bwjlmnbz#bopl#afpsf`jbojyfg?,ejfogpfw=sqldqfppjufnjoojlmp#lepwbwfp#wkbwfmelq`fnfmwbqlvmg#wkf#lmf#bmlwkfq-sbqfmwMlgfbdqj`vowvqfBowfqmbwjufqfpfbq`kfqpwltbqgp#wkfNlpw#le#wkfnbmz#lwkfq#+fpsf`jbooz?wg#tjgwk>!8tjgwk9233&jmgfsfmgfmw?k0#`obpp>!#lm`kbmdf>!*-bgg@obpp+jmwfqb`wjlmLmf#le#wkf#gbvdkwfq#leb``fpplqjfpaqbm`kfp#le\u000E\t?gju#jg>!wkf#obqdfpwgf`obqbwjlmqfdvobwjlmpJmelqnbwjlmwqbmpobwjlmgl`vnfmwbqzjm#lqgfq#wl!=\t?kfbg=\t?!#kfjdkw>!2b`qlpp#wkf#lqjfmwbwjlm*8?,p`qjsw=jnsofnfmwfg`bm#af#pffmwkfqf#tbp#bgfnlmpwqbwf`lmwbjmfq!=`lmmf`wjlmpwkf#Aqjwjpktbp#tqjwwfm\"jnslqwbmw8s{8#nbqdjm.elooltfg#azbajojwz#wl#`lnsoj`bwfggvqjmd#wkf#jnnjdqbwjlmbopl#`boofg?k7#`obpp>!gjpwjm`wjlmqfsob`fg#azdlufqmnfmwpol`bwjlm#lejm#Mlufnafqtkfwkfq#wkf?,s=\t?,gju=b`rvjpjwjlm`boofg#wkf#sfqpf`vwjlmgfpjdmbwjlmxelmw.pjyf9bssfbqfg#jmjmufpwjdbwff{sfqjfm`fgnlpw#ojhfoztjgfoz#vpfggjp`vppjlmpsqfpfm`f#le#+gl`vnfmw-f{wfmpjufozJw#kbp#affmjw#glfp#mlw`lmwqbqz#wljmkbajwbmwpjnsqlufnfmwp`klobqpkjs`lmpvnswjlmjmpwqv`wjlmelq#f{bnsoflmf#lq#nlqfs{8#sbggjmdwkf#`vqqfmwb#pfqjfp#lebqf#vpvboozqlof#jm#wkfsqfujlvpoz#gfqjubwjufpfujgfm`f#lef{sfqjfm`fp`lolqp`kfnfpwbwfg#wkbw`fqwjej`bwf?,b=?,gju=\t#pfof`wfg>!kjdk#p`klloqfpslmpf#wl`lnelqwbaofbglswjlm#lewkqff#zfbqpwkf#`lvmwqzjm#Efaqvbqzpl#wkbw#wkfsflsof#tkl#sqlujgfg#az?sbqbn#mbnfbeef`wfg#azjm#wfqnp#lebssljmwnfmwJPL.;;6:.2!tbp#alqm#jmkjpwlqj`bo#qfdbqgfg#bpnfbpvqfnfmwjp#abpfg#lm#bmg#lwkfq#9#evm`wjlm+pjdmjej`bmw`fofaqbwjlmwqbmpnjwwfg,ip,irvfqz-jp#hmltm#bpwkflqfwj`bo#wbajmgf{>!jw#`lvog#af?mlp`qjsw=\tkbujmd#affm\u000E\t?kfbg=\u000E\t?#%rvlw8Wkf#`lnsjobwjlmkf#kbg#affmsqlgv`fg#azskjolplskfq`lmpwqv`wfgjmwfmgfg#wlbnlmd#lwkfq`lnsbqfg#wlwl#pbz#wkbwFmdjmffqjmdb#gjeefqfmwqfefqqfg#wlgjeefqfm`fpafojfe#wkbwsklwldqbskpjgfmwjezjmdKjpwlqz#le#Qfsvaoj`#lemf`fppbqjozsqlabajojwzwf`kmj`boozofbujmd#wkfpsf`wb`vobqeqb`wjlm#lefof`wqj`jwzkfbg#le#wkfqfpwbvqbmwpsbqwmfqpkjsfnskbpjp#lmnlpw#qf`fmwpkbqf#tjwk#pbzjmd#wkbwejoofg#tjwkgfpjdmfg#wljw#jp#lewfm!=?,jeqbnf=bp#elooltp9nfqdfg#tjwkwkqlvdk#wkf`lnnfq`jbo#sljmwfg#lvwlsslqwvmjwzujft#le#wkfqfrvjqfnfmwgjujpjlm#lesqldqbnnjmdkf#qf`fjufgpfwJmwfqubo!=?,psbm=?,jm#Mft#Zlqhbggjwjlmbo#`lnsqfppjlm\t\t?gju#jg>!jm`lqslqbwf8?,p`qjsw=?bwwb`kFufmwaf`bnf#wkf#!#wbqdfw>!\\`bqqjfg#lvwPlnf#le#wkfp`jfm`f#bmgwkf#wjnf#le@lmwbjmfq!=nbjmwbjmjmd@kqjpwlskfqNv`k#le#wkftqjwjmdp#le!#kfjdkw>!1pjyf#le#wkfufqpjlm#le#nj{wvqf#le#afwtffm#wkfF{bnsofp#lefgv`bwjlmbo`lnsfwjwjuf#lmpvanjw>!gjqf`wlq#legjpwjm`wjuf,GWG#[KWNO#qfobwjmd#wlwfmgfm`z#wlsqlujm`f#letkj`k#tlvoggfpsjwf#wkfp`jfmwjej`#ofdjpobwvqf-jmmfqKWNO#boofdbwjlmpBdqj`vowvqftbp#vpfg#jmbssqlb`k#wljmwfoojdfmwzfbqp#obwfq/pbmp.pfqjegfwfqnjmjmdSfqelqnbm`fbssfbqbm`fp/#tkj`k#jp#elvmgbwjlmpbaaqfujbwfgkjdkfq#wkbmp#eqln#wkf#jmgjujgvbo#`lnslpfg#lepvsslpfg#wl`objnp#wkbwbwwqjavwjlmelmw.pjyf92fofnfmwp#leKjpwlqj`bo#kjp#aqlwkfqbw#wkf#wjnfbmmjufqpbqzdlufqmfg#azqfobwfg#wl#vowjnbwfoz#jmmlubwjlmpjw#jp#pwjoo`bm#lmoz#afgfejmjwjlmpwlDNWPwqjmdB#mvnafq#lejnd#`obpp>!Fufmwvbooz/tbp#`kbmdfgl``vqqfg#jmmfjdkalqjmdgjpwjmdvjpktkfm#kf#tbpjmwqlgv`jmdwfqqfpwqjboNbmz#le#wkfbqdvfp#wkbwbm#Bnfqj`bm`lmrvfpw#letjgfpsqfbg#tfqf#hjoofgp`qffm#bmg#Jm#lqgfq#wlf{sf`wfg#wlgfp`fmgbmwpbqf#ol`bwfgofdjpobwjufdfmfqbwjlmp#ab`hdqlvmgnlpw#sflsofzfbqp#bewfqwkfqf#jp#mlwkf#kjdkfpweqfrvfmwoz#wkfz#gl#mlwbqdvfg#wkbwpkltfg#wkbwsqfglnjmbmwwkfloldj`boaz#wkf#wjnf`lmpjgfqjmdpklqw.ojufg?,psbm=?,b=`bm#af#vpfgufqz#ojwwoflmf#le#wkf#kbg#boqfbgzjmwfqsqfwfg`lnnvmj`bwfefbwvqfp#ledlufqmnfmw/?,mlp`qjsw=fmwfqfg#wkf!#kfjdkw>!0Jmgfsfmgfmwslsvobwjlmpobqdf.p`bof-#Bowklvdk#vpfg#jm#wkfgfpwqv`wjlmslppjajojwzpwbqwjmd#jmwtl#lq#nlqff{sqfppjlmppvalqgjmbwfobqdfq#wkbmkjpwlqz#bmg?,lswjlm=\u000E\t@lmwjmfmwbofojnjmbwjmdtjoo#mlw#afsqb`wj`f#lejm#eqlmw#lepjwf#le#wkffmpvqf#wkbwwl#`qfbwf#bnjppjppjssjslwfmwjboozlvwpwbmgjmdafwwfq#wkbmtkbw#jp#mltpjwvbwfg#jmnfwb#mbnf>!WqbgjwjlmbopvddfpwjlmpWqbmpobwjlmwkf#elqn#lebwnlpskfqj`jgfloldj`bofmwfqsqjpfp`bo`vobwjmdfbpw#le#wkfqfnmbmwp#lesovdjmpsbdf,jmgf{-sks #evm`wjlm+*-isd!#tjgwk>!`lmejdvqbwjlm-smd!#tjgwk>!?algz#`obpp>!Nbwk-qbmgln+*`lmwfnslqbqz#Vmjwfg#Pwbwfp`jq`vnpwbm`fp-bssfmg@kjog+lqdbmjybwjlmp?psbm#`obpp>!!=?jnd#pq`>!,gjpwjmdvjpkfgwklvpbmgp#le#`lnnvmj`bwjlm`ofbq!=?,gju=jmufpwjdbwjlmebuj`lm-j`l!#nbqdjm.qjdkw9abpfg#lm#wkf#Nbppb`kvpfwwpwbaof#alqgfq>jmwfqmbwjlmbobopl#hmltm#bpsqlmvm`jbwjlmab`hdqlvmg9 esbggjmd.ofew9Elq#f{bnsof/#njp`foobmflvp%ow8,nbwk%dw8spz`kloldj`bojm#sbqwj`vobqfbq`k!#wzsf>!elqn#nfwklg>!bp#lsslpfg#wlPvsqfnf#@lvqwl``bpjlmbooz#Bggjwjlmbooz/Mlqwk#Bnfqj`bs{8ab`hdqlvmglsslqwvmjwjfpFmwfqwbjmnfmw-wlOltfq@bpf+nbmveb`wvqjmdsqlefppjlmbo#`lnajmfg#tjwkElq#jmpwbm`f/`lmpjpwjmd#le!#nb{ofmdwk>!qfwvqm#ebopf8`lmp`jlvpmfppNfgjwfqqbmfbmf{wqblqgjmbqzbppbppjmbwjlmpvapfrvfmwoz#avwwlm#wzsf>!wkf#mvnafq#lewkf#lqjdjmbo#`lnsqfkfmpjufqfefqp#wl#wkf?,vo=\t?,gju=\tskjolplskj`bool`bwjlm-kqfetbp#svaojpkfgPbm#Eqbm`jp`l+evm`wjlm+*x\t?gju#jg>!nbjmplskjpwj`bwfgnbwkfnbwj`bo#,kfbg=\u000E\t?algzpvddfpwp#wkbwgl`vnfmwbwjlm`lm`fmwqbwjlmqfobwjlmpkjspnbz#kbuf#affm+elq#f{bnsof/Wkjp#bqwj`of#jm#plnf#`bpfpsbqwp#le#wkf#gfejmjwjlm#leDqfbw#Aqjwbjm#`foosbggjmd>frvjubofmw#wlsob`fklogfq>!8#elmw.pjyf9#ivpwjej`bwjlmafojfufg#wkbwpveefqfg#eqlnbwwfnswfg#wl#ofbgfq#le#wkf`qjsw!#pq`>!,+evm`wjlm+*#xbqf#bubjobaof\t\n?ojmh#qfo>!#pq`>$kwws9,,jmwfqfpwfg#jm`lmufmwjlmbo#!#bow>!!#,=?,bqf#dfmfqboozkbp#bopl#affmnlpw#slsvobq#`lqqfpslmgjmd`qfgjwfg#tjwkwzof>!alqgfq9?,b=?,psbm=?,-dje!#tjgwk>!?jeqbnf#pq`>!wbaof#`obpp>!jmojmf.aol`h8b``lqgjmd#wl#wldfwkfq#tjwkbssql{jnbwfozsbqojbnfmwbqznlqf#bmg#nlqfgjpsobz9mlmf8wqbgjwjlmboozsqfglnjmbmwoz%maps8\u007F%maps8%maps8?,psbm=#`foopsb`jmd>?jmsvw#mbnf>!lq!#`lmwfmw>!`lmwqlufqpjbosqlsfqwz>!ld9,{.pkl`htbuf.gfnlmpwqbwjlmpvqqlvmgfg#azMfufqwkfofpp/tbp#wkf#ejqpw`lmpjgfqbaof#Bowklvdk#wkf#`loobalqbwjlmpklvog#mlw#afsqlslqwjlm#le?psbm#pwzof>!hmltm#bp#wkf#pklqwoz#bewfqelq#jmpwbm`f/gfp`qjafg#bp#,kfbg=\t?algz#pwbqwjmd#tjwkjm`qfbpjmdoz#wkf#eb`w#wkbwgjp`vppjlm#lenjggof#le#wkfbm#jmgjujgvbogjeej`vow#wl#sljmw#le#ujftklnlpf{vbojwzb``fswbm`f#le?,psbm=?,gju=nbmveb`wvqfqplqjdjm#le#wkf`lnnlmoz#vpfgjnslqwbm`f#legfmlnjmbwjlmpab`hdqlvmg9# ofmdwk#le#wkfgfwfqnjmbwjlmb#pjdmjej`bmw!#alqgfq>!3!=qfulovwjlmbqzsqjm`jsofp#lejp#`lmpjgfqfgtbp#gfufolsfgJmgl.Fvqlsfbmuvomfqbaof#wlsqlslmfmwp#lebqf#plnfwjnfp`olpfq#wl#wkfMft#Zlqh#@jwz#mbnf>!pfbq`kbwwqjavwfg#wl`lvqpf#le#wkfnbwkfnbwj`jbmaz#wkf#fmg#lebw#wkf#fmg#le!#alqgfq>!3!#wf`kmloldj`bo-qfnluf@obpp+aqbm`k#le#wkffujgfm`f#wkbw\"Xfmgje^..=\u000E\tJmpwjwvwf#le#jmwl#b#pjmdofqfpsf`wjufoz-bmg#wkfqfelqfsqlsfqwjfp#lejp#ol`bwfg#jmplnf#le#tkj`kWkfqf#jp#bopl`lmwjmvfg#wl#bssfbqbm`f#le#%bns8mgbpk8#gfp`qjafp#wkf`lmpjgfqbwjlmbvwklq#le#wkfjmgfsfmgfmwozfrvjssfg#tjwkglfp#mlw#kbuf?,b=?b#kqfe>!`lmevpfg#tjwk?ojmh#kqfe>!,bw#wkf#bdf#lebssfbq#jm#wkfWkfpf#jm`ovgfqfdbqgofpp#le`lvog#af#vpfg#pwzof>%rvlw8pfufqbo#wjnfpqfsqfpfmw#wkfalgz=\t?,kwno=wklvdkw#wl#afslsvobwjlm#leslppjajojwjfpsfq`fmwbdf#leb``fpp#wl#wkfbm#bwwfnsw#wlsqlgv`wjlm#leirvfqz,irvfqzwtl#gjeefqfmwafolmd#wl#wkffpwbaojpknfmwqfsob`jmd#wkfgfp`qjswjlm!#gfwfqnjmf#wkfbubjobaof#elqB``lqgjmd#wl#tjgf#qbmdf#le\n?gju#`obpp>!nlqf#`lnnlmozlqdbmjpbwjlmpevm`wjlmbojwztbp#`lnsofwfg#%bns8ngbpk8#sbqwj`jsbwjlmwkf#`kbqb`wfqbm#bggjwjlmbobssfbqp#wl#afeb`w#wkbw#wkfbm#f{bnsof#lepjdmjej`bmwozlmnlvpflufq>!af`bvpf#wkfz#bpzm`#>#wqvf8sqlaofnp#tjwkpffnp#wl#kbufwkf#qfpvow#le#pq`>!kwws9,,ebnjojbq#tjwkslppfppjlm#leevm`wjlm#+*#xwllh#sob`f#jmbmg#plnfwjnfppvapwbmwjbooz?psbm=?,psbm=jp#lewfm#vpfgjm#bm#bwwfnswdqfbw#gfbo#leFmujqlmnfmwbopv``fppevooz#ujqwvbooz#boo13wk#`fmwvqz/sqlefppjlmbopmf`fppbqz#wl#gfwfqnjmfg#az`lnsbwjajojwzaf`bvpf#jw#jpGj`wjlmbqz#lenlgjej`bwjlmpWkf#elooltjmdnbz#qfefq#wl9@lmpfrvfmwoz/Jmwfqmbwjlmbobowklvdk#plnfwkbw#tlvog#aftlqog$p#ejqpw`obppjejfg#bpalwwln#le#wkf+sbqwj`vobqozbojdm>!ofew!#nlpw#`lnnlmozabpjp#elq#wkfelvmgbwjlm#le`lmwqjavwjlmpslsvobqjwz#le`fmwfq#le#wkfwl#qfgv`f#wkfivqjpgj`wjlmpbssql{jnbwjlm#lmnlvpflvw>!Mft#Wfpwbnfmw`loof`wjlm#le?,psbm=?,b=?,jm#wkf#Vmjwfgejon#gjqf`wlq.pwqj`w-gwg!=kbp#affm#vpfgqfwvqm#wl#wkfbowklvdk#wkjp`kbmdf#jm#wkfpfufqbo#lwkfqavw#wkfqf#bqfvmsqf`fgfmwfgjp#pjnjobq#wlfpsf`jbooz#jmtfjdkw9#alog8jp#`boofg#wkf`lnsvwbwjlmbojmgj`bwf#wkbwqfpwqj`wfg#wl\n?nfwb#mbnf>!bqf#wzsj`booz`lmeoj`w#tjwkKltfufq/#wkf#Bm#f{bnsof#le`lnsbqfg#tjwkrvbmwjwjfp#leqbwkfq#wkbm#b`lmpwfoobwjlmmf`fppbqz#elqqfslqwfg#wkbwpsf`jej`bwjlmslojwj`bo#bmg%maps8%maps8?qfefqfm`fp#wlwkf#pbnf#zfbqDlufqmnfmw#ledfmfqbwjlm#lekbuf#mlw#affmpfufqbo#zfbqp`lnnjwnfmw#wl\n\n?vo#`obpp>!ujpvbojybwjlm2:wk#`fmwvqz/sqb`wjwjlmfqpwkbw#kf#tlvogbmg#`lmwjmvfgl``vsbwjlm#lejp#gfejmfg#bp`fmwqf#le#wkfwkf#bnlvmw#le=?gju#pwzof>!frvjubofmw#legjeefqfmwjbwfaqlvdkw#balvwnbqdjm.ofew9#bvwlnbwj`boozwklvdkw#le#bpPlnf#le#wkfpf\t?gju#`obpp>!jmsvw#`obpp>!qfsob`fg#tjwkjp#lmf#le#wkffgv`bwjlm#bmgjmeovfm`fg#azqfsvwbwjlm#bp\t?nfwb#mbnf>!b``lnnlgbwjlm?,gju=\t?,gju=obqdf#sbqw#leJmpwjwvwf#elqwkf#pl.`boofg#bdbjmpw#wkf#Jm#wkjp#`bpf/tbp#bssljmwfg`objnfg#wl#afKltfufq/#wkjpGfsbqwnfmw#lewkf#qfnbjmjmdfeef`w#lm#wkfsbqwj`vobqoz#gfbo#tjwk#wkf\t?gju#pwzof>!bonlpw#botbzpbqf#`vqqfmwozf{sqfppjlm#leskjolplskz#leelq#nlqf#wkbm`jujojybwjlmplm#wkf#jpobmgpfof`wfgJmgf{`bm#qfpvow#jm!#ubovf>!!#,=wkf#pwqv`wvqf#,=?,b=?,gju=Nbmz#le#wkfpf`bvpfg#az#wkfle#wkf#Vmjwfgpsbm#`obpp>!n`bm#af#wqb`fgjp#qfobwfg#wlaf`bnf#lmf#lejp#eqfrvfmwozojujmd#jm#wkfwkflqfwj`boozElooltjmd#wkfQfulovwjlmbqzdlufqmnfmw#jmjp#gfwfqnjmfgwkf#slojwj`bojmwqlgv`fg#jmpveej`jfmw#wlgfp`qjswjlm!=pklqw#pwlqjfppfsbqbwjlm#lebp#wl#tkfwkfqhmltm#elq#jwptbp#jmjwjboozgjpsobz9aol`hjp#bm#f{bnsofwkf#sqjm`jsbo`lmpjpwp#le#bqf`ldmjyfg#bp,algz=?,kwno=b#pvapwbmwjboqf`lmpwqv`wfgkfbg#le#pwbwfqfpjpwbm`f#wlvmgfqdqbgvbwfWkfqf#bqf#wtldqbujwbwjlmbobqf#gfp`qjafgjmwfmwjlmboozpfqufg#bp#wkf`obpp>!kfbgfqlsslpjwjlm#wlevmgbnfmwboozglnjmbwfg#wkfbmg#wkf#lwkfqboojbm`f#tjwktbp#elq`fg#wlqfpsf`wjufoz/bmg#slojwj`bojm#pvsslqw#lesflsof#jm#wkf13wk#`fmwvqz-bmg#svaojpkfgolbg@kbqwafbwwl#vmgfqpwbmgnfnafq#pwbwfpfmujqlmnfmwboejqpw#kboe#le`lvmwqjfp#bmgbq`kjwf`wvqboaf#`lmpjgfqfg`kbqb`wfqjyfg`ofbqJmwfqubobvwklqjwbwjufEfgfqbwjlm#letbp#pv``ffgfgbmg#wkfqf#bqfb#`lmpfrvfm`fwkf#Sqfpjgfmwbopl#jm`ovgfgeqff#plewtbqfpv``fppjlm#legfufolsfg#wkftbp#gfpwqlzfgbtbz#eqln#wkf8\t?,p`qjsw=\t?bowklvdk#wkfzelooltfg#az#bnlqf#sltfqevoqfpvowfg#jm#bVmjufqpjwz#leKltfufq/#nbmzwkf#sqfpjgfmwKltfufq/#plnfjp#wklvdkw#wlvmwjo#wkf#fmgtbp#bmmlvm`fgbqf#jnslqwbmwbopl#jm`ovgfp=?jmsvw#wzsf>wkf#`fmwfq#le#GL#MLW#BOWFQvpfg#wl#qfefqwkfnfp, 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.
+ *
+ * @return number of slots used by resulting Huffman table
+ */
+ static int buildHuffmanTable(int[] tableGroup, int tableIdx, int rootBits, int[] codeLengths,
+ int codeLengthsSize) {
+ final int tableOffset = tableGroup[tableIdx];
+ int key; // Reversed prefix code.
+ final int[] sorted = new int[codeLengthsSize]; // Symbols sorted by code length.
+ // TODO(eustas): fill with zeroes?
+ final int[] count = new int[MAX_LENGTH + 1]; // Number of codes of each length.
+ final 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++) {
+ tableGroup[tableOffset + key] = sorted[0];
+ }
+ return totalSize;
+ }
+
+ // 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(tableGroup, tableOffset + key, step, tableSize,
+ len << 16 | sorted[symbol++]);
+ key = getNextKey(key, len);
+ }
+ }
+
+ // Fill in 2nd level tables and add pointers to root table.
+ final 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;
+ tableGroup[tableOffset + low] =
+ (tableBits + rootBits) << 16 | (currentOffset - tableOffset - low);
+ }
+ replicateValue(tableGroup, currentOffset + (key >> rootBits), step, tableSize,
+ (len - rootBits) << 16 | sorted[symbol++]);
+ key = getNextKey(key, len);
+ }
+ }
+ return totalSize;
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/State.java b/firka_wear/android/app/src/main/java/org/brotli/dec/State.java
new file mode 100644
index 00000000..94db93ab
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/State.java
@@ -0,0 +1,100 @@
+/* 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.InputStream;
+
+final class State {
+ byte[] ringBuffer;
+ byte[] contextModes;
+ byte[] contextMap;
+ byte[] distContextMap;
+ byte[] distExtraBits;
+ byte[] output;
+ byte[] byteBuffer; // BitReader
+
+ short[] shortBuffer; // BitReader
+
+ int[] intBuffer; // BitReader
+ int[] rings;
+ int[] blockTrees;
+ int[] literalTreeGroup;
+ int[] commandTreeGroup;
+ int[] distanceTreeGroup;
+ int[] distOffset;
+
+ long accumulator64; // BitReader: pre-fetched bits.
+
+ int runningState; // Default value is 0 == Decode.UNINITIALIZED
+ int nextRunningState;
+ int accumulator32; // BitReader: pre-fetched bits.
+ int bitOffset; // BitReader: bit-reading position in accumulator.
+ int halfOffset; // BitReader: offset of next item in intBuffer/shortBuffer.
+ int tailBytes; // BitReader: number of bytes in unfinished half.
+ int endOfStreamReached; // BitReader: input stream is finished.
+ int metaBlockLength;
+ int inputEnd;
+ int isUncompressed;
+ int isMetadata;
+ int literalBlockLength;
+ int numLiteralBlockTypes;
+ int commandBlockLength;
+ int numCommandBlockTypes;
+ int distanceBlockLength;
+ int numDistanceBlockTypes;
+ int pos;
+ int maxDistance;
+ int distRbIdx;
+ int trivialLiteralContext;
+ int literalTreeIdx;
+ int commandTreeIdx;
+ int j;
+ int insertLength;
+ int contextMapSlice;
+ int distContextMapSlice;
+ int contextLookupOffset1;
+ int contextLookupOffset2;
+ int distanceCode;
+ int numDirectDistanceCodes;
+ int distancePostfixBits;
+ int distance;
+ int copyLength;
+ int maxBackwardDistance;
+ int maxRingBufferSize;
+ int ringBufferSize;
+ int expectedTotalSize;
+ int outputOffset;
+ int outputLength;
+ int outputUsed;
+ int ringBufferBytesWritten;
+ int ringBufferBytesReady;
+ int isEager;
+ int isLargeWindow;
+
+ // Compound dictionary
+ int cdNumChunks;
+ int cdTotalSize;
+ int cdBrIndex;
+ int cdBrOffset;
+ int cdBrLength;
+ int cdBrCopied;
+ byte[][] cdChunks;
+ int[] cdChunkOffsets;
+ int cdBlockBits;
+ byte[] cdBlockMap;
+
+ InputStream /* @Nullable */ input; // BitReader
+
+ State() {
+ this.ringBuffer = new byte[0];
+ this.rings = new int[10];
+ this.rings[0] = 16;
+ this.rings[1] = 15;
+ this.rings[2] = 11;
+ this.rings[3] = 4;
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/Transform.java b/firka_wear/android/app/src/main/java/org/brotli/dec/Transform.java
new file mode 100644
index 00000000..6a57a9ec
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/Transform.java
@@ -0,0 +1,236 @@
+/* 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.nio.ByteBuffer;
+
+/**
+ * Transformations on dictionary words.
+ *
+ * Transform descriptor is a triplet: {prefix, operator, suffix}.
+ * "prefix" and "suffix" are short strings inserted before and after transformed dictionary word.
+ * "operator" is applied to dictionary word itself.
+ *
+ * Some operators has "built-in" parameters, i.e. parameter is defined by operator ordinal. Other
+ * operators have "external" parameters, supplied via additional table encoded in shared dictionary.
+ *
+ * Operators:
+ * - IDENTITY (0): dictionary word is inserted "as is"
+ * - OMIT_LAST_N (1 - 9): last N octets of dictionary word are not inserted; N == ordinal
+ * - OMIT_FIRST_M (12-20): first M octets of dictionary word are not inserted; M == ordinal - 11
+ * - UPPERCASE_FIRST (10): first "scalar" is XOR'ed with number 32
+ * - UPPERCASE_ALL (11): all "scalars" are XOR'ed with number 32
+ * - SHIFT_FIRST (21): first "scalar" is shifted by number form parameter table
+ * - SHIFT_ALL (22): all "scalar" is shifted by number form parameter table
+ *
+ * Here "scalar" is a variable length character coding similar to UTF-8 encoding.
+ * UPPERCASE_XXX / SHIFT_XXX operators were designed to change the case of UTF-8 encoded characters.
+ * While UPPERCASE_XXX works well only on ASCII charset, SHIFT is much more generic and could be
+ * used for most (all?) alphabets.
+ */
+final class Transform {
+
+ static final class Transforms {
+ final int numTransforms;
+ final int[] triplets;
+ final byte[] prefixSuffixStorage;
+ final int[] prefixSuffixHeads;
+ final short[] params;
+
+ Transforms(int numTransforms, int prefixSuffixLen, int prefixSuffixCount) {
+ this.numTransforms = numTransforms;
+ this.triplets = new int[numTransforms * 3];
+ this.params = new short[numTransforms];
+ this.prefixSuffixStorage = new byte[prefixSuffixLen];
+ this.prefixSuffixHeads = new int[prefixSuffixCount + 1];
+ }
+ }
+
+ static final int NUM_RFC_TRANSFORMS = 121;
+ static final Transforms RFC_TRANSFORMS = new Transforms(NUM_RFC_TRANSFORMS, 167, 50);
+
+ private static final int OMIT_FIRST_LAST_LIMIT = 9;
+
+ private static final int IDENTITY = 0;
+ private static final int OMIT_LAST_BASE = IDENTITY + 1 - 1; // there is no OMIT_LAST_0.
+ private static final int UPPERCASE_FIRST = OMIT_LAST_BASE + OMIT_FIRST_LAST_LIMIT + 1;
+ private static final int UPPERCASE_ALL = UPPERCASE_FIRST + 1;
+ private static final int OMIT_FIRST_BASE = UPPERCASE_ALL + 1 - 1; // there is no OMIT_FIRST_0.
+ private static final int SHIFT_FIRST = OMIT_FIRST_BASE + OMIT_FIRST_LAST_LIMIT + 1;
+ private static final int SHIFT_ALL = SHIFT_FIRST + 1;
+
+ // Bundle of 0-terminated strings.
+ private static final String PREFIX_SUFFIX_SRC = "# #s #, #e #.# the #.com/#\u00C2\u00A0# of # and"
+ + " # in # to #\"#\">#\n#]# for # a # that #. # with #'# from # by #. The # on # as # is #ing"
+ + " #\n\t#:#ed #(# at #ly #=\"# of the #. This #,# not #er #al #='#ful #ive #less #est #ize #"
+ + "ous #";
+ private static final String TRANSFORMS_SRC = " !! ! , *! &! \" ! ) * * - ! # ! #!*! "
+ + "+ ,$ ! - % . / # 0 1 . \" 2 3!* 4% ! # / 5 6 7 8 0 1 & $ 9 + : "
+ + " ; < ' != > ?! 4 @ 4 2 & A *# ( B C& ) % ) !*# *-% A +! *. D! %' & E *6 F "
+ + " G% ! *A *% H! D I!+! J!+ K +- *4! A L!*4 M N +6 O!*% +.! K *G P +%( ! G *D +D "
+ + " Q +# *K!*G!+D!+# +G +A +4!+% +K!+4!*D!+K!*K";
+
+ private static void unpackTransforms(byte[] prefixSuffix,
+ int[] prefixSuffixHeads, int[] transforms, String prefixSuffixSrc, String transformsSrc) {
+ final int n = prefixSuffixSrc.length();
+ int index = 1;
+ int j = 0;
+ for (int i = 0; i < n; ++i) {
+ final char c = prefixSuffixSrc.charAt(i);
+ if (c == 35) { // == #
+ prefixSuffixHeads[index++] = j;
+ } else {
+ prefixSuffix[j++] = (byte) c;
+ }
+ }
+
+ for (int i = 0; i < NUM_RFC_TRANSFORMS * 3; ++i) {
+ transforms[i] = transformsSrc.charAt(i) - 32;
+ }
+ }
+
+ static {
+ unpackTransforms(RFC_TRANSFORMS.prefixSuffixStorage, RFC_TRANSFORMS.prefixSuffixHeads,
+ RFC_TRANSFORMS.triplets, PREFIX_SUFFIX_SRC, TRANSFORMS_SRC);
+ }
+
+ static int transformDictionaryWord(byte[] dst, int dstOffset, ByteBuffer src, int srcOffset,
+ int len, Transforms transforms, int transformIndex) {
+ int offset = dstOffset;
+ final int[] triplets = transforms.triplets;
+ final byte[] prefixSuffixStorage = transforms.prefixSuffixStorage;
+ final int[] prefixSuffixHeads = transforms.prefixSuffixHeads;
+ final int transformOffset = 3 * transformIndex;
+ final int prefixIdx = triplets[transformOffset];
+ final int transformType = triplets[transformOffset + 1];
+ final int suffixIdx = triplets[transformOffset + 2];
+ int prefix = prefixSuffixHeads[prefixIdx];
+ final int prefixEnd = prefixSuffixHeads[prefixIdx + 1];
+ int suffix = prefixSuffixHeads[suffixIdx];
+ final int suffixEnd = prefixSuffixHeads[suffixIdx + 1];
+
+ int omitFirst = transformType - OMIT_FIRST_BASE;
+ int omitLast = transformType - OMIT_LAST_BASE;
+ if (omitFirst < 1 || omitFirst > OMIT_FIRST_LAST_LIMIT) {
+ omitFirst = 0;
+ }
+ if (omitLast < 1 || omitLast > OMIT_FIRST_LAST_LIMIT) {
+ omitLast = 0;
+ }
+
+ // Copy prefix.
+ while (prefix != prefixEnd) {
+ dst[offset++] = prefixSuffixStorage[prefix++];
+ }
+
+ // Copy trimmed word.
+ if (omitFirst > len) {
+ omitFirst = len;
+ }
+ srcOffset += omitFirst;
+ len -= omitFirst;
+ len -= omitLast;
+ int i = len;
+ while (i > 0) {
+ dst[offset++] = src.get(srcOffset++);
+ i--;
+ }
+
+ // Ferment.
+ if (transformType == UPPERCASE_FIRST || transformType == UPPERCASE_ALL) {
+ int uppercaseOffset = offset - len;
+ if (transformType == UPPERCASE_FIRST) {
+ len = 1;
+ }
+ while (len > 0) {
+ final int c0 = dst[uppercaseOffset] & 0xFF;
+ if (c0 < 0xC0) {
+ if (c0 >= 97 && c0 <= 122) { // in [a..z] range
+ dst[uppercaseOffset] ^= (byte) 32;
+ }
+ uppercaseOffset += 1;
+ len -= 1;
+ } else if (c0 < 0xE0) {
+ dst[uppercaseOffset + 1] ^= (byte) 32;
+ uppercaseOffset += 2;
+ len -= 2;
+ } else {
+ dst[uppercaseOffset + 2] ^= (byte) 5;
+ uppercaseOffset += 3;
+ len -= 3;
+ }
+ }
+ } else if (transformType == SHIFT_FIRST || transformType == SHIFT_ALL) {
+ int shiftOffset = offset - len;
+ final short param = transforms.params[transformIndex];
+ /* Limited sign extension: scalar < (1 << 24). */
+ int scalar = (param & 0x7FFF) + (0x1000000 - (param & 0x8000));
+ while (len > 0) {
+ int step = 1;
+ final int c0 = dst[shiftOffset] & 0xFF;
+ if (c0 < 0x80) {
+ /* 1-byte rune / 0sssssss / 7 bit scalar (ASCII). */
+ scalar += c0;
+ dst[shiftOffset] = (byte) (scalar & 0x7F);
+ } else if (c0 < 0xC0) {
+ /* Continuation / 10AAAAAA. */
+ } else if (c0 < 0xE0) {
+ /* 2-byte rune / 110sssss AAssssss / 11 bit scalar. */
+ if (len >= 2) {
+ final byte c1 = dst[shiftOffset + 1];
+ scalar += (c1 & 0x3F) | ((c0 & 0x1F) << 6);
+ dst[shiftOffset] = (byte) (0xC0 | ((scalar >> 6) & 0x1F));
+ dst[shiftOffset + 1] = (byte) ((c1 & 0xC0) | (scalar & 0x3F));
+ step = 2;
+ } else {
+ step = len;
+ }
+ } else if (c0 < 0xF0) {
+ /* 3-byte rune / 1110ssss AAssssss BBssssss / 16 bit scalar. */
+ if (len >= 3) {
+ final byte c1 = dst[shiftOffset + 1];
+ final byte c2 = dst[shiftOffset + 2];
+ scalar += (c2 & 0x3F) | ((c1 & 0x3F) << 6) | ((c0 & 0x0F) << 12);
+ dst[shiftOffset] = (byte) (0xE0 | ((scalar >> 12) & 0x0F));
+ dst[shiftOffset + 1] = (byte) ((c1 & 0xC0) | ((scalar >> 6) & 0x3F));
+ dst[shiftOffset + 2] = (byte) ((c2 & 0xC0) | (scalar & 0x3F));
+ step = 3;
+ } else {
+ step = len;
+ }
+ } else if (c0 < 0xF8) {
+ /* 4-byte rune / 11110sss AAssssss BBssssss CCssssss / 21 bit scalar. */
+ if (len >= 4) {
+ final byte c1 = dst[shiftOffset + 1];
+ final byte c2 = dst[shiftOffset + 2];
+ final byte c3 = dst[shiftOffset + 3];
+ scalar += (c3 & 0x3F) | ((c2 & 0x3F) << 6) | ((c1 & 0x3F) << 12) | ((c0 & 0x07) << 18);
+ dst[shiftOffset] = (byte) (0xF0 | ((scalar >> 18) & 0x07));
+ dst[shiftOffset + 1] = (byte) ((c1 & 0xC0) | ((scalar >> 12) & 0x3F));
+ dst[shiftOffset + 2] = (byte) ((c2 & 0xC0) | ((scalar >> 6) & 0x3F));
+ dst[shiftOffset + 3] = (byte) ((c3 & 0xC0) | (scalar & 0x3F));
+ step = 4;
+ } else {
+ step = len;
+ }
+ }
+ shiftOffset += step;
+ len -= step;
+ if (transformType == SHIFT_FIRST) {
+ len = 0;
+ }
+ }
+ }
+
+ // Copy suffix.
+ while (suffix != suffixEnd) {
+ dst[offset++] = prefixSuffixStorage[suffix++];
+ }
+
+ return offset - dstOffset;
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/dec/Utils.java b/firka_wear/android/app/src/main/java/org/brotli/dec/Utils.java
new file mode 100644
index 00000000..cc4a9f0d
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/dec/Utils.java
@@ -0,0 +1,119 @@
+/* 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.io.UnsupportedEncodingException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+
+/**
+ * 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.
+ *
+ * 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 fillBytesWithZeroes(byte[] dest, int start, int end) {
+ int cursor = start;
+ while (cursor < end) {
+ int step = Math.min(cursor + 1024, end) - cursor;
+ System.arraycopy(BYTE_ZEROES, 0, dest, cursor, step);
+ cursor += step;
+ }
+ }
+
+ /**
+ * Fills int array with zeroes.
+ *
+ * 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 fillIntsWithZeroes(int[] dest, int start, int end) {
+ int cursor = start;
+ while (cursor < end) {
+ int step = Math.min(cursor + 1024, end) - cursor;
+ System.arraycopy(INT_ZEROES, 0, dest, cursor, step);
+ cursor += step;
+ }
+ }
+
+ static void copyBytes(byte[] dst, int target, byte[] src, int start, int end) {
+ System.arraycopy(src, start, dst, target, end - start);
+ }
+
+ static void copyBytesWithin(byte[] bytes, int target, int start, int end) {
+ System.arraycopy(bytes, start, bytes, target, end - start);
+ }
+
+ static int readInput(InputStream src, byte[] dst, int offset, int length) {
+ try {
+ return src.read(dst, offset, length);
+ } catch (IOException e) {
+ throw new BrotliRuntimeException("Failed to read input", e);
+ }
+ }
+
+ static void closeInput(InputStream src) throws IOException {
+ src.close();
+ }
+
+ static byte[] toUsAsciiBytes(String src) {
+ try {
+ // NB: String#getBytes(String) is present in JDK 1.1, while other variants require JDK 1.6 and
+ // above.
+ return src.getBytes("US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e); // cannot happen
+ }
+ }
+
+ static ByteBuffer asReadOnlyBuffer(ByteBuffer src) {
+ return src.asReadOnlyBuffer();
+ }
+
+ static int isReadOnly(ByteBuffer src) {
+ return src.isReadOnly() ? 1 : 0;
+ }
+
+ static int isDirect(ByteBuffer src) {
+ return src.isDirect() ? 1 : 0;
+ }
+
+ // Crazy pills factory: code compiled for JDK8 does not work on JRE9.
+ static void flipBuffer(Buffer buffer) {
+ buffer.flip();
+ }
+
+ static int isDebugMode() {
+ boolean assertsEnabled = Boolean.parseBoolean(System.getProperty("BROTLI_ENABLE_ASSERTS"));
+ return assertsEnabled ? 1 : 0;
+ }
+
+ // See BitReader.LOG_BITNESS
+ static int getLogBintness() {
+ boolean isLongExpensive = Boolean.parseBoolean(System.getProperty("BROTLI_32_BIT_CPU"));
+ return isLongExpensive ? 5 : 6;
+ }
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/enc/PreparedDictionary.java b/firka_wear/android/app/src/main/java/org/brotli/enc/PreparedDictionary.java
new file mode 100644
index 00000000..51978015
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/enc/PreparedDictionary.java
@@ -0,0 +1,16 @@
+/* Copyright 2018 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.enc;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Prepared dictionary data provider.
+ */
+public interface PreparedDictionary {
+ ByteBuffer getData();
+}
diff --git a/firka_wear/android/app/src/main/java/org/brotli/enc/PreparedDictionaryGenerator.java b/firka_wear/android/app/src/main/java/org/brotli/enc/PreparedDictionaryGenerator.java
new file mode 100644
index 00000000..3813429c
--- /dev/null
+++ b/firka_wear/android/app/src/main/java/org/brotli/enc/PreparedDictionaryGenerator.java
@@ -0,0 +1,185 @@
+/* Copyright 2017 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.enc;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Java prepared (raw) dictionary producer.
+ */
+public class PreparedDictionaryGenerator {
+
+ private static final int MAGIC = 0xDEBCEDE0;
+ private static final long HASH_MULTIPLIER = 0x1fe35a7bd3579bd3L;
+
+ private static class PreparedDictionaryImpl implements PreparedDictionary {
+ private final ByteBuffer data;
+
+ private PreparedDictionaryImpl(ByteBuffer data) {
+ this.data = data;
+ }
+
+ @Override
+ public ByteBuffer getData() {
+ return data;
+ }
+ }
+
+ // Disallow instantiation.
+ private PreparedDictionaryGenerator() { }
+
+ public static PreparedDictionary generate(ByteBuffer src) {
+ return generate(src, 17, 3, 40, 5);
+ }
+
+ public static PreparedDictionary generate(ByteBuffer src,
+ int bucketBits, int slotBits, int hashBits, int blockBits) {
+ ((Buffer) src).clear(); // Just in case...
+ if (blockBits > 12) {
+ throw new IllegalArgumentException("blockBits is too big");
+ }
+ if (bucketBits >= 24) {
+ throw new IllegalArgumentException("bucketBits is too big");
+ }
+ if (bucketBits - slotBits >= 16) {
+ throw new IllegalArgumentException("slotBits is too small");
+ }
+ int bucketLimit = 1 << blockBits;
+ int numBuckets = 1 << bucketBits;
+ int numSlots = 1 << slotBits;
+ int slotMask = numSlots - 1;
+ int hashShift = 64 - bucketBits;
+ long hashMask = (~0L) >>> (64 - hashBits);
+ int sourceSize = src.capacity();
+ if (sourceSize < 8) {
+ throw new IllegalArgumentException("src is too short");
+ }
+
+ /* Step 1: create "bloated" hasher. */
+ short[] num = new short[numBuckets];
+ int[] bucketHeads = new int[numBuckets];
+ int[] nextBucket = new int[sourceSize];
+
+ long accumulator = 0;
+ for (int i = 0; i < 7; ++i) {
+ accumulator |= (src.get(i) & 0xFFL) << (8 * i);
+ }
+ accumulator <<= 8;
+ /* TODO(eustas): apply custom "store" order. */
+ for (int i = 0; i + 7 < sourceSize; ++i) {
+ accumulator = (accumulator >>> 8) | ((src.get(i + 7) & 0xFFL) << 56);
+ long h = (accumulator & hashMask) * HASH_MULTIPLIER;
+ int key = (int) (h >>> hashShift);
+ int count = num[key];
+ nextBucket[i] = (count == 0) ? -1 : bucketHeads[key];
+ bucketHeads[key] = i;
+ count++;
+ if (count > bucketLimit) {
+ count = bucketLimit;
+ }
+ num[key] = (short) count;
+ }
+
+ /* Step 2: find slot limits. */
+ int[] slotLimit = new int[numSlots];
+ int[] slotSize = new int[numSlots];
+ int totalItems = 0;
+ for (int i = 0; i < numSlots; ++i) {
+ boolean overflow = false;
+ slotLimit[i] = bucketLimit;
+ while (true) {
+ overflow = false;
+ int limit = slotLimit[i];
+ int count = 0;
+ for (int j = i; j < numBuckets; j += numSlots) {
+ int size = num[j];
+ /* Last chain may span behind 64K limit; overflow happens only if
+ we are about to use 0xFFFF+ as item offset. */
+ if (count >= 0xFFFF) {
+ overflow = true;
+ break;
+ }
+ if (size > limit) {
+ size = limit;
+ }
+ count += size;
+ }
+ if (!overflow) {
+ slotSize[i] = count;
+ totalItems += count;
+ break;
+ }
+ slotLimit[i]--;
+ }
+ }
+
+ /* Step 3: transfer data to "slim" hasher. */
+ int part0 = 6 * 4;
+ int part1 = numSlots * 4;
+ int part2 = numBuckets * 2;
+ int part3 = totalItems * 4;
+ int allocSize = part0 + part1 + part2 + part3 + sourceSize;
+ ByteBuffer flat = ByteBuffer.allocateDirect(allocSize);
+ ByteBuffer pointer = flat.slice();
+ pointer.order(ByteOrder.nativeOrder());
+
+ IntBuffer struct = pointer.asIntBuffer();
+ pointer.position(pointer.position() + part0);
+ IntBuffer slotOffsets = pointer.asIntBuffer();
+ pointer.position(pointer.position() + part1);
+ ShortBuffer heads = pointer.asShortBuffer();
+ pointer.position(pointer.position() + part2);
+ IntBuffer items = pointer.asIntBuffer();
+ pointer.position(pointer.position() + part3);
+ ByteBuffer sourceCopy = pointer.slice();
+
+ /* magic */ struct.put(0, MAGIC);
+ /* source_offset */ struct.put(1, totalItems);
+ /* source_size */ struct.put(2, sourceSize);
+ /* hash_bits */ struct.put(3, hashBits);
+ /* bucket_bits */ struct.put(4, bucketBits);
+ /* slot_bits */ struct.put(5, slotBits);
+
+ totalItems = 0;
+ for (int i = 0; i < numSlots; ++i) {
+ slotOffsets.put(i, totalItems);
+ totalItems += slotSize[i];
+ slotSize[i] = 0;
+ }
+
+ for (int i = 0; i < numBuckets; ++i) {
+ int slot = i & slotMask;
+ int count = num[i];
+ if (count > slotLimit[slot]) {
+ count = slotLimit[slot];
+ }
+ if (count == 0) {
+ heads.put(i, (short) 0xFFFF);
+ continue;
+ }
+ int cursor = slotSize[slot];
+ heads.put(i, (short) cursor);
+ cursor += slotOffsets.get(slot);
+ slotSize[slot] += count;
+ int pos = bucketHeads[i];
+ for (int j = 0; j < count; j++) {
+ items.put(cursor++, pos);
+ pos = nextBucket[pos];
+ }
+ cursor--;
+ items.put(cursor, items.get(cursor) | 0x80000000);
+ }
+
+ sourceCopy.put(src);
+
+ return new PreparedDictionaryImpl(flat);
+ }
+}
diff --git a/firka_wear/android/app/src/main/kotlin/app/firka/naplo/AppMain.kt b/firka_wear/android/app/src/main/kotlin/app/firka/naplo/AppMain.kt
new file mode 100644
index 00000000..6a4ad1b1
--- /dev/null
+++ b/firka_wear/android/app/src/main/kotlin/app/firka/naplo/AppMain.kt
@@ -0,0 +1,88 @@
+package app.firka.naplo
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.os.Build
+import android.util.Log
+import org.brotli.dec.BrotliInputStream
+import org.json.JSONObject
+import java.io.File
+import java.io.FileOutputStream
+import java.security.MessageDigest
+import java.util.zip.ZipFile
+
+class AppMain : Application() {
+
+ private fun File.sha256(): String {
+ if (!exists()) return "0000000000000000000000000000000000000000000000000000000000000000"
+
+ val md = MessageDigest.getInstance("SHA-256")
+ val digest = md.digest(this.readBytes())
+ return digest.fold("") { str, it -> str + "%02x".format(it) }
+ }
+
+ @SuppressLint("UnsafeDynamicallyLoadedCode")
+ override fun onCreate() {
+ super.onCreate()
+
+ val abi = Build.SUPPORTED_ABIS[0]
+
+ val apks = File(applicationInfo.nativeLibraryDir, "../..").absoluteFile
+ .listFiles()!!
+ .filter { file -> file.name.endsWith(".apk") }
+ .toList()
+
+ var nativesApkN: ZipFile? = null
+ for (apk in apks) {
+ if (nativesApkN != null) break
+
+ val zip = ZipFile(apk)
+ val entries = zip.entries()
+
+ while (entries.hasMoreElements()) {
+ val entry = entries.nextElement()
+
+ entry.name.endsWith("$abi/index.so")
+ zip.close()
+ nativesApkN = ZipFile(apk)
+ break
+ }
+
+ zip.close()
+ }
+
+ if (nativesApkN == null) {
+ throw Exception("Can't find native libraries")
+ }
+ val nativesApk: ZipFile = nativesApkN
+
+ val compressedLibsIndex = nativesApk.getInputStream(
+ nativesApk.getEntry("lib/$abi/index.so")
+ )
+ val compressedLibs = JSONObject(compressedLibsIndex.readBytes().toString(Charsets.UTF_8))
+
+ for (so in compressedLibs.keys()) {
+ val soFile = File(cacheDir, so)
+
+ if (soFile.sha256() == compressedLibs.getString(so)) {
+ System.load(soFile.absolutePath)
+ return
+ }
+
+ Log.d("AppMain", "Decompressing: $so")
+ val brInput = nativesApk.getInputStream(
+ nativesApk.getEntry("lib/$abi/${so.replace(".so", "-br.so")}")
+ )
+ val soOutput = FileOutputStream(soFile)
+
+ val brIn = BrotliInputStream(brInput)
+ brIn.copyTo(soOutput)
+
+ brInput.close()
+ soOutput.close()
+
+ System.load(soFile.absolutePath)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/firka_wear/android/app/src/main/kotlin/app/firka/naplo/MainActivity.kt b/firka_wear/android/app/src/main/kotlin/app/firka/naplo/MainActivity.kt
new file mode 100644
index 00000000..858230c4
--- /dev/null
+++ b/firka_wear/android/app/src/main/kotlin/app/firka/naplo/MainActivity.kt
@@ -0,0 +1,92 @@
+package app.firka.naplo
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import androidx.core.app.NotificationCompat
+import androidx.wear.ongoing.OngoingActivity
+import androidx.wear.ongoing.Status
+import io.flutter.embedding.android.FlutterActivity
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
+
+
+class MainActivity : FlutterActivity() {
+
+ private val channel = "firka.app/main"
+ private val channelId = "ongoing_activity"
+ private val notificationId = 1000
+
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
+ as NotificationManager
+
+ notificationManager.createNotificationChannel(
+ NotificationChannel(
+ channelId,
+ "Ongoing Activity",
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+ )
+
+ val notificationBuilder = NotificationCompat.Builder(this, channelId)
+ .setSmallIcon(R.drawable.ic_notification)
+ .setOngoing(true)
+
+ val ongoingActivityStatus = Status.Builder()
+ // Sets the text used across various surfaces.
+ .addTemplate("Firka")
+ .build()
+
+ val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!
+ val activityPendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val ongoingActivity = OngoingActivity.Builder(applicationContext,
+ notificationId, notificationBuilder)
+ .setStaticIcon(R.drawable.ic_notification)
+ .setTouchIntent(activityPendingIntent)
+ .setStatus(ongoingActivityStatus)
+ .build()
+
+ ongoingActivity.apply(applicationContext)
+
+
+ super.configureFlutterEngine(flutterEngine)
+ MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler {
+ call, result ->
+ when (call.method) {
+ "get_info" -> {
+ result.success("${Build.MODEL};" +
+ "${Build.VERSION.RELEASE};" +
+ "${Build.VERSION.SDK_INT}")
+ }
+ "activity_update" -> {
+ notificationManager.notify(notificationId, notificationBuilder.build())
+ result.success(null)
+ }
+ "activity_cancel" -> {
+ notificationManager.cancel(notificationId)
+ result.success(null)
+ }
+ else -> {
+ result.notImplemented()
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_SECURE)
+ }
+
+}
diff --git a/firka_wear/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png b/firka_wear/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png
new file mode 100644
index 0000000000000000000000000000000000000000..a4b37589b9e4ad1e752d48ea1ffaf0d52b1dab9b
GIT binary patch
literal 4550
zcmeAS@N?(olHy`uVBq!ia0y~yU|0mg9Bd2>42M36Ni#4A_IkQFhE&XXJD0sA!sbqhpnlvpfNlpw#---?,bnlmdaqbjmalgz#mlmf8abpfg`bqqzgqbewqfefqsbdf\\klnf-nfwfqgfobzgqfbnsqlufiljmw?,wq=gqvdp?\"..#bsqjojgfboboofmf{b`welqwk`lgfpoldj`Ujft#pffnpaobmhslqwp#+133pbufg\\ojmhdlbopdqbmwdqffhklnfpqjmdpqbwfg03s{8tklpfsbqpf+*8!#Aol`hojmv{ilmfpsj{fo$*8!=*8je+.ofewgbujgklqpfEl`vpqbjpfal{fpWqb`hfnfmw?,fn=abq!=-pq`>wltfqbow>!`baofkfmqz17s{8pfwvsjwbozpkbqsnjmlqwbpwftbmwpwkjp-qfpfwtkffodjqop,`pp,233&8`ovappwveeajaofulwfp#2333hlqfb~*8\u000E\tabmgprvfvf>#x~8;3s{8`hjmdx\u000E\t\n\nbkfbg`ol`hjqjpkojhf#qbwjlpwbwpElqn!zbkll*X3^8Balvwejmgp?,k2=gfavdwbphpVQO#>`foop~*+*821s{8sqjnfwfoopwvqmp3{533-isd!psbjmafb`kwb{fpnj`qlbmdfo..=?,djewppwfuf.ojmhalgz-~*8\t\nnlvmw#+2::EBR?,qldfqeqbmh@obpp1;s{8effgp?k2=?p`lwwwfpwp11s{8gqjmh*#\u007F\u007F#oftjppkboo 30:8#elq#olufgtbpwf33s{8ib9\u000Fnpjnlm?elmwqfsoznffwpvmwfq`kfbswjdkwAqbmg*#\">#gqfpp`ojspqllnplmhfznlajonbjm-Mbnf#sobwfevmmzwqffp`ln,!2-isdtnlgfsbqbnPWBQWofew#jggfm/#132*8\t~\telqn-ujqvp`kbjqwqbmptlqpwSbdfpjwjlmsbw`k?\"..\tl.`b`ejqnpwlvqp/333#bpjbmj((*xbglaf$*X3^jg>23alwk8nfmv#-1-nj-smd!hfujm`lb`k@kjogaqv`f1-isdVQO*(-isd\u007Fpvjwfpoj`fkbqqz213!#ptffwwq=\u000E\tmbnf>gjfdlsbdf#ptjpp..=\t\t eee8!=Old-`ln!wqfbwpkffw*#%%#27s{8poffsmwfmwejofgib9\u000Fojg>!`Mbnf!tlqpfpklwp.al{.gfowb\t%ow8afbqp97;Y?gbwb.qvqbo?,b=#psfmgabhfqpklsp>#!!8sks!=`wjlm20s{8aqjbmkfoolpjyf>l>&1E#iljmnbzaf?jnd#jnd!=/#eipjnd!#!*X3^NWlsAWzsf!mftozGbmph`yf`kwqbjohmltp?,k6=ebr!=yk.`m23*8\t.2!*8wzsf>aovfpwqvozgbujp-ip$8=\u000E\t?\"pwffo#zlv#k1=\u000E\telqn#ifpvp233nfmv-\u000E\t\n\u000E\ttbofpqjphpvnfmwggjmda.ojhwfb`kdje!#ufdbpgbmphffpwjpkrjspvlnjplaqfgfpgffmwqfwlglpsvfgfb/]lpfpw/Mwjfmfkbpwblwqlpsbqwfglmgfmvfulkb`fqelqnbnjpnlnfilqnvmglbrv/Ag/Abpp/_olbzvgbef`kbwlgbpwbmwlnfmlpgbwlplwqbppjwjlnv`klbklqbovdbqnbzlqfpwlpklqbpwfmfqbmwfpelwlpfpwbpsb/Apmvfubpbovgelqlpnfgjlrvjfmnfpfpslgfq`kjofpfq/Muf`fpgf`jqilp/Efpwbqufmwbdqvslkf`klfoolpwfmdlbnjdl`lpbpmjufodfmwfnjpnbbjqfpivojlwfnbpkb`jbebulqivmjlojaqfsvmwlavfmlbvwlqbaqjoavfmbwf{wlnbqylpbafqojpwbovfdl`/_nlfmfqlivfdlsfq/Vkbafqfpwlzmvm`bnvifqubolqevfqbojaqldvpwbjdvboulwlp`bplpdv/Absvfglplnlpbujplvpwfggfafmml`kfavp`bebowbfvqlppfqjfgj`kl`vqpl`obuf`bpbpof/_msobylobqdllaqbpujpwbbslzlivmwlwqbwbujpwl`qfbq`bnslkfnlp`jm`l`bqdlsjplplqgfmkb`fm/Mqfbgjp`lsfgql`fq`bsvfgbsbsfonfmlq/Vwjo`obqlilqdf`boofslmfqwbqgfmbgjfnbq`bpjdvffoobppjdol`l`kfnlwlpnbgqf`obpfqfpwlmj/]lrvfgbsbpbqabm`lkjilpujbifsbaol/Epwfujfmfqfjmlgfibqelmgl`bmbomlqwfofwqb`bvpbwlnbqnbmlpovmfpbvwlpujoobufmglsfpbqwjslpwfmdbnbq`loofubsbgqfvmjglubnlpylmbpbnalpabmgbnbqjbbavplnv`kbpvajqqjlibujujqdqbgl`kj`bboo/Ailufmgj`kbfpwbmwbofppbojqpvfolsfplpejmfpoobnbavp`l/Epwboofdbmfdqlsobybkvnlqsbdbqivmwbglaofjpobpalopbab/]lkbaobov`kb/mqfbgj`fmivdbqmlwbpuboofboo/M`bqdbglolqbabilfpw/Edvpwlnfmwfnbqjlejqnb`lpwlej`kbsobwbkldbqbqwfpofzfpbrvfonvpflabpfpsl`lpnjwbg`jfol`kj`lnjfgldbmbqpbmwlfwbsbgfafpsobzbqfgfppjfwf`lqwf`lqfbgvgbpgfpflujfilgfpfbbdvbp%rvlw8glnbjm`lnnlmpwbwvpfufmwpnbpwfqpzpwfnb`wjlmabmmfqqfnlufp`qloovsgbwfdolabonfgjvnejowfqmvnafq`kbmdfqfpvowsvaoj`p`qffm`kllpfmlqnbowqbufojppvfpplvq`fwbqdfwpsqjmdnlgvofnlajofptjw`ksklwlpalqgfqqfdjlmjwpfoepl`jbob`wjuf`lovnmqf`lqgelooltwjwof=fjwkfqofmdwkebnjozeqjfmgobzlvwbvwklq`qfbwfqfujftpvnnfqpfqufqsobzfgsobzfqf{sbmgsloj`zelqnbwglvaofsljmwppfqjfpsfqplmojujmdgfpjdmnlmwkpelq`fpvmjrvftfjdkwsflsoffmfqdzmbwvqfpfbq`kejdvqfkbujmd`vpwlnleepfwofwwfqtjmgltpvanjwqfmgfqdqlvspvsolbgkfbowknfwklgujgflpp`klloevwvqfpkbgltgfabwfubovfpLaif`wlwkfqpqjdkwpofbdvf`kqlnfpjnsofmlwj`fpkbqfgfmgjmdpfbplmqfslqwlmojmfprvbqfavwwlmjnbdfpfmbaofnlujmdobwfpwtjmwfqEqbm`fsfqjlgpwqlmdqfsfbwOlmglmgfwbjoelqnfggfnbmgpf`vqfsbppfgwlddofsob`fpgfuj`fpwbwj``jwjfppwqfbnzfooltbwwb`hpwqffweojdkwkjggfmjmel!=lsfmfgvpfevouboofz`bvpfpofbgfqpf`qfwpf`lmggbnbdfpslqwpf{`fswqbwjmdpjdmfgwkjmdpfeef`wejfogppwbwfpleej`fujpvbofgjwlqulovnfQfslqwnvpfvnnlujfpsbqfmwb``fppnlpwoznlwkfq!#jg>!nbqhfwdqlvmg`kbm`fpvqufzafelqfpznalonlnfmwpsff`knlwjlmjmpjgfnbwwfq@fmwfqlaif`wf{jpwpnjggofFvqlsfdqltwkofdb`znbmmfqfmlvdk`bqffqbmptfqlqjdjmslqwbo`ojfmwpfof`wqbmgln`olpfgwlsj`p`lnjmdebwkfqlswjlmpjnsozqbjpfgfp`bsf`klpfm`kvq`kgfejmfqfbplm`lqmfqlvwsvwnfnlqzjeqbnfsloj`fnlgfopMvnafqgvqjmdleefqppwzofphjoofgojpwfg`boofgpjoufqnbqdjmgfofwfafwwfqaqltpfojnjwpDolabopjmdoftjgdfw`fmwfqavgdfwmltqbs`qfgjw`objnpfmdjmfpbefwz`klj`fpsjqjw.pwzofpsqfbgnbhjmdmffgfgqvppjbsofbpff{wfmwP`qjswaqlhfmbooltp`kbqdfgjujgfeb`wlqnfnafq.abpfgwkflqz`lmejdbqlvmgtlqhfgkfosfg@kvq`kjnsb`wpklvogbotbzpoldl!#alwwlnojpw!=*xubq#sqfej{lqbmdfKfbgfq-svpk+`lvsofdbqgfmaqjgdfobvm`kQfujftwbhjmdujpjlmojwwofgbwjmdAvwwlmafbvwzwkfnfpelqdlwPfbq`kbm`klqbonlpwolbgfg@kbmdfqfwvqmpwqjmdqfolbgNlajofjm`lnfpvssozPlvq`flqgfqpujftfg%maps8`lvqpfBalvw#jpobmg?kwno#`llhjfmbnf>!bnbylmnlgfqmbguj`fjm?,b=9#Wkf#gjboldklvpfpAFDJM#Nf{j`lpwbqwp`fmwqfkfjdkwbggjmdJpobmgbppfwpFnsjqfP`kllofeelqwgjqf`wmfbqoznbmvboPfof`w-\t\tLmfiljmfgnfmv!=SkjojsbtbqgpkbmgofjnslqwLeej`fqfdbqgphjoopmbwjlmPslqwpgfdqfftffhoz#+f-d-afkjmggl`wlqolddfgvmjwfg?,a=?,afdjmpsobmwpbppjpwbqwjpwjppvfg033s{\u007F`bmbgbbdfm`zp`kfnfqfnbjmAqbyjopbnsofoldl!=afzlmg.p`bofb``fswpfqufgnbqjmfEllwfq`bnfqb?,k2=\t\\elqn!ofbufppwqfpp!#,=\u000E\t-dje!#lmolbgolbgfqL{elqgpjpwfqpvqujuojpwfmefnbofGfpjdmpjyf>!bssfbowf{w!=ofufopwkbmhpkjdkfqelq`fgbmjnbobmzlmfBeqj`bbdqffgqf`fmwSflsof?aq#,=tlmgfqsqj`fpwvqmfg\u007F\u007F#x~8nbjm!=jmojmfpvmgbztqbs!=ebjofg`fmpvpnjmvwfafb`lmrvlwfp263s{\u007Ffpwbwfqfnlwffnbjo!ojmhfgqjdkw8pjdmboelqnbo2-kwnopjdmvssqjm`feolbw9-smd!#elqvn-B``fppsbsfqpplvmgpf{wfmgKfjdkwpojgfqVWE.;!%bns8#Afelqf-#TjwkpwvgjlltmfqpnbmbdfsqlejwiRvfqzbmmvbosbqbnpalvdkwebnlvpdlldofolmdfqj((*#xjpqbfopbzjmdgf`jgfklnf!=kfbgfqfmpvqfaqbm`ksjf`fpaol`h8pwbwfgwls!=?qb`jmdqfpjyf..%dw8sb`jwzpf{vboavqfbv-isd!#23/333lawbjmwjwofpbnlvmw/#Jm`-`lnfgznfmv!#ozqj`pwlgbz-jmgffg`lvmwz\\oldl-EbnjozollhfgNbqhfwopf#jeSobzfqwvqhfz*8ubq#elqfpwdjujmdfqqlqpGlnbjm~fopfxjmpfqwAold?,ellwfqoldjm-ebpwfqbdfmwp?algz#23s{#3sqbdnbeqjgbzivmjlqgloobqsob`fg`lufqpsovdjm6/333#sbdf!=alpwlm-wfpw+bubwbqwfpwfg\\`lvmwelqvnpp`kfnbjmgf{/ejoofgpkbqfpqfbgfqbofqw+bssfbqPvanjwojmf!=algz!=\t)#WkfWklvdkpffjmdifqpfzMftp?,ufqjezf{sfqwjmivqztjgwk>@llhjfPWBQW#b`qlpp\\jnbdfwkqfbgmbwjufsl`hfwal{!=\tPzpwfn#Gbujg`bm`fqwbaofpsqlufgBsqjo#qfboozgqjufqjwfn!=nlqf!=albqgp`lolqp`bnsvpejqpw#\u007F\u007F#X^8nfgjb-dvjwbqejmjpktjgwk9pkltfgLwkfq#-sks!#bppvnfobzfqptjoplmpwlqfpqfojfeptfgfm@vpwlnfbpjoz#zlvq#Pwqjmd\t\tTkjowbzolq`ofbq9qfplqweqfm`kwklvdk!*#(#!?algz=avzjmdaqbmgpNfnafqmbnf!=lssjmdpf`wlq6s{8!=upsb`fslpwfqnbilq#`leeffnbqwjmnbwvqfkbssfm?,mbu=hbmpbpojmh!=Jnbdfp>ebopftkjof#kpsb`f3%bns8#\t\tJm##sltfqSlophj.`lolqilqgbmAlwwlnPwbqw#.`lvmw1-kwnomftp!=32-isdLmojmf.qjdkwnjoofqpfmjlqJPAM#33/333#dvjgfpubovf*f`wjlmqfsbjq-{no!##qjdkwp-kwno.aol`hqfdF{s9klufqtjwkjmujqdjmsklmfp?,wq=\u000Evpjmd#\t\nubq#=$*8\t\n?,wg=\t?,wq=\tabkbpbaqbpjodbofdlnbdzbqslophjpqsphj4]4C5d\bTA\nzk\u000BBl\bQ\u007F\u000BUm\u0005Gx\bSM\nmC\bTA\twQ\nd}\bW@\bTl\bTF\ti@\tcT\u000BBM\u000B|j\u0004BV\tqw\tcC\bWI\npa\tfM\n{Z\u0005{X\bTF\bVV\bVK\t\u007Fm\u0004kF\t[]\bPm\bTv\nsI\u000Bpg\t[I\bQp\u0004mx\u000B_W\n^M\npe\u000BQ}\u000BGu\nel\npe\u0004Ch\u0004BV\bTA\tSo\nzk\u000BGL\u000BxD\nd[\u0005Jz\u0005MY\bQp\u0004li\nfl\npC\u0005{B\u0005Nt\u000BwT\ti_\bTg\u0004QQ\n|p\u000BXN\bQS\u000BxD\u0004QC\bWZ\tpD\u000BVS\bTW\u0005Nt\u0004Yh\nzu\u0004Kj\u0005N}\twr\tHa\n_D\tj`\u000BQ}\u000BWp\nxZ\u0004{c\tji\tBU\nbD\u0004a|\tTn\tpV\nZd\nmC\u000BEV\u0005{X\tc}\tTo\bWl\bUd\tIQ\tcg\u000Bxs\nXW\twR\u000Bek\tc}\t]y\tJn\nrp\neg\npV\nz\\\u0005{W\npl\nz\\\nzU\tPc\t`{\bV@\nc|\bRw\ti_\bVb\nwX\tHv\u0004Su\bTF\u000B_W\u000BWs\u000BsI\u0005m\u007F\nTT\ndc\tUS\t}f\tiZ\bWz\tc}\u0004MD\tBe\tiD\u000B@@\bTl\bPv\t}t\u0004Sw\u0004M`\u000BnU\tkW\u000Bed\nqo\u000BxY\tA|\bTz\u000By`\u0004BR\u0004BM\tia\u0004XU\nyu\u0004n^\tfL\tiI\nXW\tfD\bWz\bW@\tyj\t\u007Fm\tav\tBN\u000Bb\\\tpD\bTf\nY[\tJn\bQy\t[^\u000BWc\u000Byu\u0004Dl\u0004CJ\u000BWj\u000BHR\t`V\u000BuW\tQy\np@\u000BGu\u0005pl\u0004Jm\bW[\nLP\nxC\n`m\twQ\u0005ui\u0005\u007FR\nbI\twQ\tBZ\tWV\u0004BR\npg\tcg\u0005ti\u0004CW\n_y\tRg\bQa\u000BQB\u000BWc\nYb\u0005le\ngE\u0004Su\nL[\tQ\u007F\tea\tdj\u000B]W\nb~\u0004M`\twL\bTV\bVH\nt\u007F\npl\t|b\u0005s_\bU|\bTa\u0004oQ\u0005lv\u0004Sk\u0004M`\bTv\u000BK}\nfl\tcC\u0004oQ\u0004BR\tHk\t|d\bQp\tHK\tBZ\u000BHR\bPv\u000BLx\u000BEZ\bT\u007F\bTv\tiD\u0005oD\u0005MU\u000BwB\u0004Su\u0005k`\u0004St\ntC\tPl\tKg\noi\tjY\u000BxY\u0004h}\nzk\bWZ\t\u007Fm\u000Be`\tTB\tfE\nzk\t`z\u0004Yh\nV|\tHK\tAJ\tAJ\bUL\tp\\\tql\nYc\u0004Kd\nfy\u0004Yh\t[I\u000BDg\u0004Jm\n]n\nlb\bUd\n{Z\tlu\tfs\u0004oQ\bTW\u0004Jm\u000BwB\tea\u0004Yh\u0004BC\tsb\tTn\nzU\n_y\u000BxY\tQ]\ngw\u0004mt\tO\\\ntb\bWW\bQy\tmI\tV[\ny\\\naB\u000BRb\twQ\n]Q\u0004QJ\bWg\u000BWa\bQj\ntC\bVH\nYm\u000Bxs\bVK\nel\bWI\u000BxY\u0004Cq\ntR\u000BHV\bTl\bVw\tay\bQa\bVV\t}t\tdj\nr|\tp\\\twR\n{i\nTT\t[I\ti[\tAJ\u000Bxs\u000B_W\td{\u000BQ}\tcg\tTz\tA|\tCj\u000BLm\u0005N}\u0005m\u007F\nbK\tdZ\tp\\\t`V\tsV\np@\tiD\twQ\u000BQ}\bTf\u0005ka\u0004Jm\u000B@@\bV`\tzp\n@N\u0004Sw\tiI\tcg\noi\u0004Su\bVw\u0004lo\u0004Cy\tc}\u000Bb\\\tsU\u0004BA\bWI\bTf\nxS\tVp\nd|\bTV\u000BbC\tNo\u0005Ju\nTC\t|`\n{Z\tD]\bU|\tc}\u0005lm\bTl\tBv\tPl\tc}\bQp\t\u007Fm\nLk\tkj\n@N\u0004Sb\u0004KO\tj_\tp\\\nzU\bTl\bTg\bWI\tcf\u0004XO\bWW\ndz\u0004li\tBN\nd[\bWO\u0004MD\u000BKC\tdj\tI_\bVV\ny\\\u000BLm\u0005xl\txB\tkV\u000Bb\\\u000BJW\u000BVS\tVx\u000BxD\td{\u0004MD\bTa\t|`\u000BPz\u0004R}\u000BWs\u0004BM\nsI\u0004CN\bTa\u0004Jm\npe\ti_\npV\nrh\tRd\tHv\n~A\nxR\u000BWh\u000BWk\nxS\u000BAz\u000BwX\nbI\u0004oQ\tfw\nqI\nV|\nun\u0005z\u007F\u000Bpg\td\\\u000BoA\u0005{D\ti_\u0005xB\bT\u007F\t`V\u0005qr\tTT\u0004g]\u0004CA\u000BuR\tVJ\tT`\npw\u000BRb\tI_\nCx\u0004Ro\u000BsI\u0004Cj\u0004Kh\tBv\tWV\u0004BB\u0005oD\u0005{D\nhc\u0004Km\u000B^R\tQE\n{I\np@\nc|\u0005Gt\tc}\u0004Dl\nzU\u0005qN\tsV\u0005k}\tHh\u000B|j\nqo\u0005u|\tQ]\u000Bek\u0005\u007FZ\u0004M`\u0004St\npe\tdj\bVG\u000BeE\t\u007Fm\u000BWc\u0004|I\n[W\tfL\bT\u007F\tBZ\u0004Su\u000BKa\u0004Cq\u0005Nt\u0004Y[\nqI\bTv\tfM\ti@\t}f\u0004B\\\tQy\u000BBl\bWg\u0004XD\u0005kc\u000Bx[\bVV\tQ]\t\u007Fa\tPy\u000BxD\nfI\t}f\u0005oD\tdj\tSG\u0005ls\t~D\u0004CN\n{Z\t\\v\n_D\nhc\u000Bx_\u0004C[\tAJ\nLM\tVx\u0004CI\tbj\tc^\tcF\ntC\u0004Sx\twr\u0004XA\bU\\\t|a\u000BK\\\bTV\bVj\nd|\tfs\u0004CX\ntb\bRw\tVx\tAE\tA|\bT\u007F\u0005Nt\u000BDg\tVc\bTl\u0004d@\npo\t\u007FM\tcF\npe\tiZ\tBo\bSq\nfH\u0004l`\bTx\bWf\tHE\u000BF{\tcO\tfD\nlm\u000BfZ\nlm\u000BeU\tdG\u0004BH\bTV\tSi\u0005MW\nwX\nz\\\t\\c\u0004CX\nd}\tl}\bQp\bTV\tF~\bQ\u007F\t`i\ng@\u0005nO\bUd\bTl\nL[\twQ\tji\ntC\t|J\nLU\naB\u000BxY\u0004Kj\tAJ\u0005uN\ti[\npe\u0004Sk\u000BDg\u000Bx]\bVb\bVV\nea\tkV\nqI\bTa\u0004Sk\nAO\tpD\ntb\nts\nyi\bVg\ti_\u000B_W\nLk\u0005Nt\tyj\tfM\u0004R\u007F\tiI\bTl\u000BwX\tsV\u000BMl\nyu\tAJ\bVj\u0004KO\tWV\u000BA}\u000BW\u007F\nrp\tiD\u000B|o\u0005lv\u000BsI\u0004BM\td~\tCU\bVb\u0004eV\npC\u000BwT\tj`\tc}\u000Bxs\u000Bps\u000Bvh\tWV\u000BGg\u000BAe\u000BVK\u000B]W\trg\u000BWc\u0005F`\tBr\u000Bb\\\tdZ\bQp\nqI\u0004kF\nLk\u000BAR\bWI\bTg\tbs\tdw\n{L\n_y\tiZ\bTA\tlg\bVV\bTl\tdk\n`k\ta{\ti_\u0005{A\u0005wj\twN\u000B@@\bTe\ti_\n_D\twL\nAH\u000BiK\u000Bek\n[]\tp_\tyj\bTv\tUS\t[r\n{I\nps\u0005Gt\u000BVK\npl\u0004S}\u000BWP\t|d\u0004MD\u000BHV\bT\u007F\u0004R}\u0004M`\bTV\bVH\u0005lv\u0004Ch\bW[\u0004Ke\tR{\u000B^R\tab\tBZ\tVA\tB`\nd|\nhs\u0004Ke\tBe\u0004Oi\tR{\td\\\u0005nB\bWZ\tdZ\tVJ\u0005Os\t\u007Fm\u0004uQ\u000BhZ\u0004Q@\u0004QQ\nfI\bW[\u0004B\\\u0004li\nzU\nMd\u0004M`\nxS\bVV\n\\}\u000BxD\t\u007Fm\bTp\u0004IS\nc|\tkV\u0005i~\tV{\u000BhZ\t|b\bWt\n@R\u000BoA\u000BnU\bWI\tea\tB`\tiD\tc}\tTz\u0004BR\u000BQB\u0005Nj\tCP\t[I\bTv\t`W\u0005uN\u000Bpg\u000Bpg\u000BWc\tiT\tbs\twL\tU_\tc\\\t|h\u000BKa\tNr\tfL\nq|\nzu\nz\\\tNr\bUg\t|b\u0004m`\bTv\nyd\nrp\bWf\tUX\u0004BV\nzk\nd}\twQ\t}f\u0004Ce\u000Bed\bTW\bSB\nxU\tcn\bTb\ne\u007F\ta\\\tSG\bU|\npV\nN\\\u0004Kn\u000BnU\tAt\tpD\u000B^R\u000BIr\u0004b[\tR{\tdE\u000BxD\u000BWK\u000BWA\bQL\bW@\u0004Su\bUd\nDM\tPc\u0004CA\u0004Dl\u0004oQ\tHs\u0005wi\u0004ub\n\u007Fa\bQp\u0005Ob\nLP\bTl\u0004Y[\u000BK}\tAJ\bQ\u007F\u0004n^\u000BsA\bSM\nqM\bWZ\n^W\u000Bz{\u0004S|\tfD\bVK\bTv\bPv\u0004BB\tCP\u0004dF\tid\u000Bxs\u0004mx\u000Bws\tcC\ntC\tyc\u0005M`\u000BW\u007F\nrh\bQp\u000BxD\u0004\\o\nsI\u0004_k\nzu\u0004kF\tfD\u0004Xs\u0004XO\tjp\bTv\u0004BS\u0005{B\tBr\nzQ\nbI\tc{\u0004BD\u0004BV\u0005nO\bTF\tca\u0005Jd\tfL\tPV\tI_\nlK\u0004`o\twX\npa\tgu\bP}\u0005{^\bWf\n{I\tBN\npa\u0004Kl\u000Bpg\tcn\tfL\u000Bvh\u0004Cq\bTl\u000BnU\bSq\u0004Cm\twR\bUJ\npe\nyd\nYg\u0004Cy\u000BKW\tfD\nea\u0004oQ\tj_\tBv\u0004nM\u000BID\bTa\nzA\u0005pl\n]n\bTa\tR{\tfr\n_y\bUg\u0005{X\u0005kk\u000BxD\u0004|I\u0005xl\nfy\u0004Ce\u000BwB\nLk\u000Bd]\noi\n}h\tQ]\npe\bVw\u0004Hk\u0004OQ\nzk\tAJ\npV\bPv\ny\\\tA{\u0004Oi\bSB\u0004XA\u000BeE\tjp\nq}\tiD\u0005qN\u000B^R\t\u007Fm\tiZ\tBr\bVg\noi\n\\X\tU_\nc|\u000BHV\bTf\tTn\u0004\\N\u0004\\N\nuB\u0005lv\nyu\tTd\bTf\bPL\u000B]W\tdG\nA`\nw^\ngI\npe\tdw\nz\\\u0005ia\bWZ\tcF\u0004Jm\n{Z\bWO\u0004_k\u0004Df\u0004RR\td\\\bVV\u000Bxs\u0004BN\u0005ti\u0004lm\tTd\t]y\u000BHV\tSo\u000B|j\u0004XX\tA|\u000BZ^\u000BGu\bTW\u0005M`\u0004kF\u000BhZ\u000BVK\tdG\u000BBl\tay\nxU\u0005qE\u0005nO\bVw\nqI\u0004CX\ne\u007F\tPl\bWO\u000BLm\tdL\u0005uH\u0004Cm\tdT\u0004fn\u000BwB\u0005ka\u000BnU\n@M\nyT\tHv\t\\}\u0004Kh\td~\u0004Yh\u0005k}\neR\td\\\bWI\t|b\tHK\tiD\bTW\u0005MY\npl\bQ_\twr\u000BAx\tHE\bTg\bSq\u0005vp\u000Bb\\\bWO\nOl\nsI\nfy\u000BID\t\\c\n{Z\n^~\npe\nAO\tTT\u000Bxv\u0004k_\bWO\u000B|j\u000BwB\tQy\ti@\tPl\tHa\tdZ\u0005k}\u0004ra\tUT\u000BJc\u000Bed\np@\tQN\nd|\tkj\tHk\u0004M`\noi\twr\td\\\nlq\no_\nlb\nL[\tac\u0004BB\u0004BH\u0004Cm\npl\tIQ\bVK\u000Bxs\n`e\u000BiK\npa\u0004Oi\tUS\bTp\tfD\nPG\u0005kk\u0004XA\nz\\\neg\u000BWh\twR\u0005qN\nqS\tcn\u0004lo\nxS\n^W\tBU\nt\u007F\tHE\tp\\\tfF\tfw\bVV\bW@\tak\u000BVK\u0005ls\tVJ\bVV\u000BeE\u0004\\o\nyX\nYm\u0004M`\u0005lL\nd|\nzk\tA{\u0005sE\twQ\u0004XT\nt\u007F\tPl\t]y\u000BwT\u0005{p\u0004MD\u000Bb\\\tQ]\u0004Kj\tJn\nAH\u000BRb\tBU\tHK\t\\c\nfI\u0005m\u007F\nqM\n@R\tSo\noi\u0004BT\tHv\n_y\u0004Kh\tBZ\t]i\bUJ\tV{\u0004Sr\nbI\u000BGg\ta_\bTR\nfI\nfl\t[K\tII\u0004S|\u000BuW\tiI\bWI\nqI\u000B|j\u0004BV\bVg\bWZ\u0004kF\u000Bx]\bTA\tab\tfr\ti@\tJd\tJd\u000Bps\nAO\bTa\u0005xu\tiD\nzk\t|d\t|`\bW[\tlP\tdG\bVV\u000Bw}\u000BqO\ti[\bQ\u007F\bTz\u000BVF\twN\u0005ts\tdw\bTv\neS\ngi\tNr\u0005yS\npe\bVV\bSq\n`m\tyj\tBZ\u000BWX\bSB\tc\\\nUR\t[J\tc_\u0004nM\bWQ\u000BAx\nMd\tBr\u0005ui\u000BxY\bSM\u000BWc\u000B|j\u000Bxs\t}Q\tBO\bPL\bWW\tfM\nAO\tPc\u000BeU\u0004e^\bTg\nqI\tac\bPv\tcF\u0004oQ\tQ\u007F\u000BhZ\u0005ka\nz\\\tiK\tBU\n`k\tCP\u0004S|\u0004M`\n{I\tS{\u0004_O\tBZ\u0004Zi\u0004Sk\tps\tp\\\nYu\n]s\nxC\bWt\nbD\tkV\u000BGu\u0005yS\nqA\t[r\neK\u0004M`\tdZ\u0005lL\bUg\bTl\nbD\tUS\u000Bb\\\tpV\ncc\u0004S\\\tct\t`z\bPL\u000BWs\nA`\neg\bSq\u0005uE\u0004CR\u000BDg\t`W\u000Bz{\u000BWc\u0004Sk\u0004Sk\tbW\bUg\tea\nxZ\tiI\tUX\tVJ\nqn\tS{\u000BRb\bTQ\npl\u0005Gt\u000BuW\u0005uj\npF\nqI\tfL\t[I\tia\u0004XO\nyu\u000BDg\u000Bed\tq{\u0004VG\bQ\u007F\u0005ka\tVj\tkV\txB\nd|\np@\tQN\tPc\tps\u0004]j\tkV\toU\bTp\nzU\u0005nB\u000BB]\ta{\bV@\n]n\u0004m`\tcz\tR{\u0004m`\bQa\u000BwT\bSM\u0005MY\u0005qN\tdj\u0005~s\u000BQ}\u0005MY\u000BMB\tBv\twR\bRg\u000BQ}\tql\u000BKC\nrm\u0005xu\u0004CC\u000BwB\u000Bvh\tBq\u0004Xq\npV\ti_\u0005Ob\u0005uE\nbd\nqo\u000B{i\nC~\tBL\u000BeE\u0005uH\bVj\u0004Ey\u0004Gz\u000BzR\u000B{i\tcf\n{Z\n]n\u0004XA\u000BGu\u000BnU\thS\u000BGI\nCc\tHE\bTA\tHB\u0004BH\u0004Cj\nCc\bTF\tHE\nXI\tA{\bQ\u007F\tc\\\u000BmO\u000BWX\nfH\np@\u0005MY\bTF\nlK\tBt\nzU\tTT\u0004Km\u000BwT\npV\ndt\u000ByI\tVx\tQ\u007F\tRg\tTd\nzU\bRS\nLM\twA\u0004nM\tTn\ndS\t]g\nLc\u000BwB\t}t\t[I\tCP\u0004kX\u000BFm\u000BhZ\u0005m\u007F\ti[\np@\u000BQ}\u000BW\u007F\t|d\nMO\nMd\tf_\tfD\tcJ\tHz\u000BRb\tio\tPy\u0004Y[\nxU\tct\u000B@@\tww\bPv\u0004BM\u0004FF\ntb\u0005v|\u000BKm\tBq\tBq\u0004Kh\u0004`o\nZd\u0004XU\ti]\t|`\tSt\u0004B\\\bQ\u007F\u000B_W\tTJ\nqI\t|a\tA{\u000BuP\u0004MD\tPl\nxR\tfL\u000Bws\tc{\td\\\bV`\neg\tHK\u0005kc\nd|\bVV\ny\\\u0005kc\ti]\bVG\t`V\tss\tI_\tAE\tbs\tdu\nel\tpD\u000BW\u007F\nqs\u0005lv\bSM\u0004Zi\u000BVK\u0005ia\u000BQB\tQ\u007F\n{Z\bPt\u000BKl\nlK\nhs\ndS\bVK\u0005mf\nd^\tkV\tcO\nc|\bVH\t\\]\bTv\bSq\tmI\u000BDg\tVJ\tcn\ny\\\bVg\bTv\nyX\bTF\t]]\bTp\noi\nhs\u000BeU\nBf\tdj\u0005Mr\n|p\t\\g\t]r\bVb\u0005{D\nd[\u0004XN\tfM\tO\\\u0005s_\tcf\tiZ\u0004XN\u000BWc\tqv\n`m\tU^\u0005oD\nd|\u000BGg\tdE\u000Bwf\u0004lo\u0004u}\nd|\u0005oQ\t`i\u0004Oi\u000BxD\ndZ\nCx\u0004Yw\nzk\ntb\ngw\tyj\tB`\nyX\u000Bps\ntC\u000BpP\u000Bqw\bPu\bPX\tDm\npw\u0005Nj\tss\taG\u000Bxs\bPt\noL\u0004Gz\tOk\ti@\ti]\u0004eC\tIQ\tii\tdj\u000B@J\t|d\u0005uh\bWZ\u000BeU\u000BnU\bTa\tcC\u0004g]\nzk\u0004Yh\bVK\nLU\np@\ntb\ntR\tCj\u000BNP\ti@\bP{\n\\}\n{c\nwX\tfL\bVG\tc{\t|`\tAJ\t|C\tfD\u0005ln\t|d\tbs\nqI\u0005{B\u000BAx\np@\nzk\u000BRb\u0005Os\u000BWS\u0004e^\u000BD_\tBv\u000BWd\bVb\u000Bxs\u000BeE\bRw\n]n\n|p\u000Bg|\tfw\u0005kc\bTI\u0005ka\n\\T\u0004Sp\tju\u000Bps\npe\u0005u|\u000BGr\bVe\tCU\u0004]M\u0004XU\u000BxD\bTa\tIQ\u000BWq\tCU\tam\tdj\bSo\u0004Sw\u000BnU\u0004Ch\tQ]\u0005s_\bPt\tfS\bTa\t\\}\n@O\u0004Yc\tUZ\bTx\npe\u000BnU\nzU\t|}\tiD\nz\\\bSM\u000BxD\u0004BR\nzQ\tQN\u0004]M\u0004Yh\nLP\u000BFm\u000BLX\u0005vc\u000Bql\u0005ka\tHK\bVb\ntC\nCy\bTv\nuV\u0004oQ\t`z\t[I\tB`\u000BRb\tyj\tsb\u000BWs\bTl\tkV\u000Bed\ne\u007F\u0005lL\u000BxN\t\u007Fm\nJn\tjY\u000BxD\bVb\bSq\u000Byu\twL\u000BXL\bTA\tpg\tAt\tnD\u0004XX\twR\npl\nhw\u0005yS\nps\tcO\bW[\u000B|j\u0004XN\tsV\tp\\\tBe\nb~\nAJ\n]e\u0005k`\u0005qN\tdw\tWV\tHE\u000BEV\u0005Jz\tid\tB`\tzh\u0005E]\tfD\bTg\u0005qN\bTa\tja\u0004Cv\bSM\nhc\bUe\u0005t_\tie\u0004g]\twQ\nPn\bVB\tjw\bVg\u000BbE\tBZ\u000BRH\bP{\tjp\n\\}\ta_\tcC\t|a\u000BD]\tBZ\ti[\tfD\u000BxW\no_\td\\\n_D\ntb\t\\c\tAJ\nlK\u0004oQ\u0004lo\u000BLx\u000BM@\bWZ\u0004Kn\u000Bpg\nTi\nIv\n|r\u000B@}\u0005Jz\u0005Lm\u0005Wh\u0005k}\u0005ln\u000BxD\n]s\u0004gc\u000Bps\tBr\bTW\u000BBM\u0005tZ\nBY\u0004DW\tjf\u000BSW\u0004C}\nqo\tdE\tmv\tIQ\bPP\bUb\u0005lv\u0004BC\nzQ\t[I\u000Bgl\nig\bUs\u0004BT\u000BbC\bSq\tsU\tiW\nJn\tSY\tHK\trg\npV\u000BID\u000B|j\u0004KO\t`S\t|a`vbmglfmujbqnbgqjgavp`bqjmj`jlwjfnslslqrvf`vfmwbfpwbglsvfgfmivfdlp`lmwqbfpw/Mmmlnaqfwjfmfmsfqejonbmfqbbnjdlp`jvgbg`fmwqlbvmrvfsvfgfpgfmwqlsqjnfqsqf`jlpfd/Vmavfmlpuloufqsvmwlppfnbmbkba/Abbdlpwlmvfulpvmjglp`bqolpfrvjslmj/]lpnv`klpbodvmb`lqqfljnbdfmsbqwjqbqqjabnbq/Abklnaqffnsoflufqgbg`bnajlnv`kbpevfqlmsbpbglo/Amfbsbqf`fmvfubp`vqplpfpwbabrvjfqlojaqlp`vbmwlb``fplnjdvfoubqjlp`vbwqlwjfmfpdqvslppfq/Mmfvqlsbnfgjlpeqfmwfb`fq`bgfn/Mplefqwb`l`kfpnlgfoljwbojbofwqbpbod/Vm`lnsqb`vbofpf{jpwf`vfqslpjfmglsqfmpboofdbqujbifpgjmfqlnvq`jbslgq/Msvfpwlgjbqjlsvfaolrvjfqfnbmvfosqlsjl`qjpjp`jfqwlpfdvqlnvfqwfevfmwf`fqqbqdqbmgffef`wlsbqwfpnfgjgbsqlsjbleqf`fwjfqqbf.nbjoubqjbpelqnbpevwvqllaifwlpfdvjqqjfpdlmlqnbpnjpnlp/Vmj`l`bnjmlpjwjlpqby/_mgfajglsqvfabwlofglwfm/Abifp/Vpfpsfql`l`jmblqjdfmwjfmgb`jfmwl`/Mgjykbaobqpfq/Abobwjmbevfqybfpwjoldvfqqbfmwqbq/E{jwlo/_sfybdfmgbu/Agflfujwbqsbdjmbnfwqlpibujfqsbgqfpe/M`jo`bafyb/Mqfbppbojgbfmu/Alibs/_mbavplpajfmfpwf{wlpoofubqsvfgbmevfqwf`ln/Vm`obpfpkvnbmlwfmjglajoablvmjgbgfpw/Mpfgjwbq`qfbgl
A%>3}ln+UDv5fVY1nDo))nJN?n8lb=3+b}lI?*;jl2uQ@M!
zUDtu7%ol$woUH#x+v@T2r#Cm**w}1HJuUX-%}wL`**7m8>yd13WM)sgu_5us#^m-J
z|9+%RdhuCm%cp-S&wm&$5u7HGe(b5U;jhQ(R
uc+~#=Rl3g3
zF#Z4aRnzBtH1#-HB`!&n&QFOlDolzFnfUwax>)Jt<9(u