1 /* Copyright 2015 Google Inc. All Rights Reserved.
2 
3    Distributed under MIT license.
4    See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
5 */
6 
7 package org.brotli.dec;
8 
9 import java.io.IOException;
10 import java.io.InputStream;
11 
12 /**
13  * {@link InputStream} decorator that decompresses brotli data.
14  *
15  * <p> Not thread-safe.
16  */
17 public class BrotliInputStream extends InputStream {
18 
19   public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 16384;
20 
21   /**
22    * Internal buffer used for efficient byte-by-byte reading.
23    */
24   private byte[] buffer;
25 
26   /**
27    * Number of decoded but still unused bytes in internal buffer.
28    */
29   private int remainingBufferBytes;
30 
31   /**
32    * Next unused byte offset.
33    */
34   private int bufferOffset;
35 
36   /**
37    * Decoder state.
38    */
39   private final State state = new State();
40 
41   /**
42    * Creates a {@link InputStream} wrapper that decompresses brotli data.
43    *
44    * <p> For byte-by-byte reading ({@link #read()}) internal buffer with
45    * {@link #DEFAULT_INTERNAL_BUFFER_SIZE} size is allocated and used.
46    *
47    * <p> Will block the thread until first kilobyte of data of source is available.
48    *
49    * @param source underlying data source
50    * @throws IOException in case of corrupted data or source stream problems
51    */
BrotliInputStream(InputStream source)52   public BrotliInputStream(InputStream source) throws IOException {
53     this(source, DEFAULT_INTERNAL_BUFFER_SIZE);
54   }
55 
56   /**
57    * Creates a {@link InputStream} wrapper that decompresses brotli data.
58    *
59    * <p> For byte-by-byte reading ({@link #read()}) internal buffer of specified size is
60    * allocated and used.
61    *
62    * <p> Will block the thread until first kilobyte of data of source is available.
63    *
64    * @param source compressed data source
65    * @param byteReadBufferSize size of internal buffer used in case of
66    *        byte-by-byte reading
67    * @throws IOException in case of corrupted data or source stream problems
68    */
BrotliInputStream(InputStream source, int byteReadBufferSize)69   public BrotliInputStream(InputStream source, int byteReadBufferSize) throws IOException {
70     if (byteReadBufferSize <= 0) {
71       throw new IllegalArgumentException("Bad buffer size:" + byteReadBufferSize);
72     } else if (source == null) {
73       throw new IllegalArgumentException("source is null");
74     }
75     this.buffer = new byte[byteReadBufferSize];
76     this.remainingBufferBytes = 0;
77     this.bufferOffset = 0;
78     try {
79       Decode.initState(state, source);
80     } catch (BrotliRuntimeException ex) {
81       throw new IOException("Brotli decoder initialization failed", ex);
82     }
83   }
84 
85   /**
86    * {@inheritDoc}
87    */
88   @Override
close()89   public void close() throws IOException {
90     Decode.close(state);
91   }
92 
93   /**
94    * {@inheritDoc}
95    */
96   @Override
read()97   public int read() throws IOException {
98     if (bufferOffset >= remainingBufferBytes) {
99       remainingBufferBytes = read(buffer, 0, buffer.length);
100       bufferOffset = 0;
101       if (remainingBufferBytes == -1) {
102         return -1;
103       }
104     }
105     return buffer[bufferOffset++] & 0xFF;
106   }
107 
108   /**
109    * {@inheritDoc}
110    */
111   @Override
read(byte[] destBuffer, int destOffset, int destLen)112   public int read(byte[] destBuffer, int destOffset, int destLen) throws IOException {
113     if (destOffset < 0) {
114       throw new IllegalArgumentException("Bad offset: " + destOffset);
115     } else if (destLen < 0) {
116       throw new IllegalArgumentException("Bad length: " + destLen);
117     } else if (destOffset + destLen > destBuffer.length) {
118       throw new IllegalArgumentException(
119           "Buffer overflow: " + (destOffset + destLen) + " > " + destBuffer.length);
120     } else if (destLen == 0) {
121       return 0;
122     }
123     int copyLen = Math.max(remainingBufferBytes - bufferOffset, 0);
124     if (copyLen != 0) {
125       copyLen = Math.min(copyLen, destLen);
126       System.arraycopy(buffer, bufferOffset, destBuffer, destOffset, copyLen);
127       bufferOffset += copyLen;
128       destOffset += copyLen;
129       destLen -= copyLen;
130       if (destLen == 0) {
131         return copyLen;
132       }
133     }
134     try {
135       state.output = destBuffer;
136       state.outputOffset = destOffset;
137       state.outputLength = destLen;
138       state.outputUsed = 0;
139       Decode.decompress(state);
140       if (state.outputUsed == 0) {
141         return -1;
142       }
143       return state.outputUsed + copyLen;
144     } catch (BrotliRuntimeException ex) {
145       throw new IOException("Brotli stream decoding failed", ex);
146     }
147 
148     // <{[INJECTED CODE]}>
149   }
150 }
151