1 /*
2  * Copyright 2017 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.internal;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 
24 import com.google.common.io.ByteStreams;
25 import java.io.ByteArrayOutputStream;
26 import java.io.OutputStream;
27 import java.util.Arrays;
28 import java.util.zip.CRC32;
29 import java.util.zip.DataFormatException;
30 import java.util.zip.GZIPOutputStream;
31 import java.util.zip.ZipException;
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 import org.junit.runners.JUnit4;
37 
38 /** Unit tests for {@link GzipInflatingBuffer}. */
39 @RunWith(JUnit4.class)
40 public class GzipInflatingBufferTest {
41   private static final String UNCOMPRESSABLE_FILE = "/io/grpc/internal/uncompressable.bin";
42 
43   private static final int GZIP_HEADER_MIN_SIZE = 10;
44   private static final int GZIP_TRAILER_SIZE = 8;
45   private static final int GZIP_HEADER_FLAG_INDEX = 3;
46 
47   public static final int GZIP_MAGIC = 0x8b1f;
48 
49   private static final int FTEXT = 1;
50   private static final int FHCRC = 2;
51   private static final int FEXTRA = 4;
52   private static final int FNAME = 8;
53   private static final int FCOMMENT = 16;
54 
55   private static final int TRUNCATED_DATA_SIZE = 10;
56 
57   private byte[] originalData;
58   private byte[] gzippedData;
59   private byte[] gzipHeader;
60   private byte[] deflatedBytes;
61   private byte[] gzipTrailer;
62   private byte[] truncatedData;
63   private byte[] gzippedTruncatedData;
64 
65   private GzipInflatingBuffer gzipInflatingBuffer;
66 
67   @Before
setUp()68   public void setUp() {
69     gzipInflatingBuffer = new GzipInflatingBuffer();
70     try {
71       originalData = ByteStreams.toByteArray(getClass().getResourceAsStream(UNCOMPRESSABLE_FILE));
72       truncatedData = Arrays.copyOf(originalData, TRUNCATED_DATA_SIZE);
73 
74       ByteArrayOutputStream gzippedOutputStream = new ByteArrayOutputStream();
75       OutputStream gzippingOutputStream = new GZIPOutputStream(gzippedOutputStream);
76       gzippingOutputStream.write(originalData);
77       gzippingOutputStream.close();
78       gzippedData = gzippedOutputStream.toByteArray();
79       gzippedOutputStream.close();
80 
81       gzipHeader = Arrays.copyOf(gzippedData, GZIP_HEADER_MIN_SIZE);
82       deflatedBytes =
83           Arrays.copyOfRange(
84               gzippedData, GZIP_HEADER_MIN_SIZE, gzippedData.length - GZIP_TRAILER_SIZE);
85       gzipTrailer =
86           Arrays.copyOfRange(
87               gzippedData, gzippedData.length - GZIP_TRAILER_SIZE, gzippedData.length);
88 
89       ByteArrayOutputStream truncatedGzippedOutputStream = new ByteArrayOutputStream();
90       OutputStream smallerGzipCompressingStream =
91           new GZIPOutputStream(truncatedGzippedOutputStream);
92       smallerGzipCompressingStream.write(truncatedData);
93       smallerGzipCompressingStream.close();
94       gzippedTruncatedData = truncatedGzippedOutputStream.toByteArray();
95       truncatedGzippedOutputStream.close();
96     } catch (Exception e) {
97       throw new RuntimeException("Failed to set up compressed data", e);
98     }
99   }
100 
101   @After
tearDown()102   public void tearDown() {
103     gzipInflatingBuffer.close();
104   }
105 
106   @Test
gzipInflateWorks()107   public void gzipInflateWorks() throws Exception {
108     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
109 
110     byte[] b = new byte[originalData.length];
111     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
112     assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
113     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
114   }
115 
116   @Test
splitGzipStreamWorks()117   public void splitGzipStreamWorks() throws Exception {
118     int initialBytes = 100;
119     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData, 0, initialBytes));
120 
121     byte[] b = new byte[originalData.length];
122     int n = gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
123     assertTrue("inflated bytes expected", n > 0);
124     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
125     assertEquals(initialBytes, gzipInflatingBuffer.getAndResetBytesConsumed());
126 
127     gzipInflatingBuffer.addGzippedBytes(
128         ReadableBuffers.wrap(gzippedData, initialBytes, gzippedData.length - initialBytes));
129     int bytesRemaining = originalData.length - n;
130     assertEquals(bytesRemaining, gzipInflatingBuffer.inflateBytes(b, n, bytesRemaining));
131     assertEquals(gzippedData.length - initialBytes, gzipInflatingBuffer.getAndResetBytesConsumed());
132     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
133   }
134 
135   @Test
inflateBytesObeysOffsetAndLength()136   public void inflateBytesObeysOffsetAndLength() throws Exception {
137     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
138 
139     int offset = 10;
140     int length = 100;
141     byte[] b = new byte[offset + length + offset];
142     assertEquals(length, gzipInflatingBuffer.inflateBytes(b, offset, length));
143     assertTrue(
144         "bytes written before offset",
145         Arrays.equals(new byte[offset], Arrays.copyOfRange(b, 0, offset)));
146     assertTrue(
147         "inflated data does not match",
148         Arrays.equals(
149             Arrays.copyOfRange(originalData, 0, length),
150             Arrays.copyOfRange(b, offset, offset + length)));
151     assertTrue(
152         "bytes written beyond length",
153         Arrays.equals(
154             new byte[offset], Arrays.copyOfRange(b, offset + length, offset + length + offset)));
155   }
156 
157   @Test
concatenatedStreamsWorks()158   public void concatenatedStreamsWorks() throws Exception {
159     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
160     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
161     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
162     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
163 
164     byte[] b = new byte[originalData.length];
165     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
166     assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
167     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
168 
169     assertEquals(
170         truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
171     assertEquals(gzippedTruncatedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
172     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
173 
174     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
175     assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
176     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
177 
178     assertEquals(
179         truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
180     assertEquals(gzippedTruncatedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
181     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
182   }
183 
184   @Test
requestingTooManyBytesStillReturnsEndOfBlock()185   public void requestingTooManyBytesStillReturnsEndOfBlock() throws Exception {
186     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
187 
188     int len = 2 * originalData.length;
189     byte[] b = new byte[len];
190     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, len));
191     assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
192     assertTrue(gzipInflatingBuffer.isStalled());
193     assertTrue(
194         "inflated data does not match",
195         Arrays.equals(originalData, Arrays.copyOf(b, originalData.length)));
196   }
197 
198   @Test
closeStopsDecompression()199   public void closeStopsDecompression() throws Exception {
200     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
201 
202     byte[] b = new byte[1];
203     gzipInflatingBuffer.inflateBytes(b, 0, 1);
204     gzipInflatingBuffer.close();
205     try {
206       gzipInflatingBuffer.inflateBytes(b, 0, 1);
207       fail("Expected IllegalStateException");
208     } catch (IllegalStateException expectedException) {
209       assertEquals("GzipInflatingBuffer is closed", expectedException.getMessage());
210     }
211   }
212 
213   @Test
isStalledReturnsTrueAtEndOfStream()214   public void isStalledReturnsTrueAtEndOfStream() throws Exception {
215     int bytesToWithhold = 10;
216 
217     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
218 
219     byte[] b = new byte[originalData.length];
220     gzipInflatingBuffer.inflateBytes(b, 0, originalData.length - bytesToWithhold);
221     assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
222 
223     gzipInflatingBuffer.inflateBytes(b, originalData.length - bytesToWithhold, bytesToWithhold);
224     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
225     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
226   }
227 
228   @Test
isStalledReturnsFalseBetweenStreams()229   public void isStalledReturnsFalseBetweenStreams() throws Exception {
230     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
231     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
232 
233     byte[] b = new byte[originalData.length];
234     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
235     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
236     assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
237 
238     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
239     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
240     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
241   }
242 
243   @Test
isStalledReturnsFalseBetweenSmallStreams()244   public void isStalledReturnsFalseBetweenSmallStreams() throws Exception {
245     // Use small streams to make sure that they all fit in the inflater buffer
246     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
247     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
248 
249     byte[] b = new byte[truncatedData.length];
250     assertEquals(
251         truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
252     assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
253     assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
254 
255     assertEquals(
256         truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
257     assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
258     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
259   }
260 
261   @Test
isStalledReturnsTrueWithPartialNextHeaderAvailable()262   public void isStalledReturnsTrueWithPartialNextHeaderAvailable() throws Exception {
263     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
264     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[1]));
265 
266     byte[] b = new byte[truncatedData.length];
267     assertEquals(
268         truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
269     assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
270     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
271     assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData());
272   }
273 
274   @Test
isStalledWorksWithAllHeaderFlags()275   public void isStalledWorksWithAllHeaderFlags() throws Exception {
276     gzipHeader[GZIP_HEADER_FLAG_INDEX] =
277         (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT);
278     int len = 1025;
279     byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
280     byte[] fExtra = new byte[len];
281     byte[] zeroTerminatedBytes = new byte[len];
282     for (int i = 0; i < len - 1; i++) {
283       zeroTerminatedBytes[i] = 1;
284     }
285     ByteArrayOutputStream newHeader = new ByteArrayOutputStream();
286     newHeader.write(gzipHeader);
287     newHeader.write(fExtraLen);
288     newHeader.write(fExtra);
289     newHeader.write(zeroTerminatedBytes); // FNAME
290     newHeader.write(zeroTerminatedBytes); // FCOMMENT
291     byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray());
292 
293     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
294 
295     addInTwoChunksAndVerifyIsStalled(gzipHeader);
296     addInTwoChunksAndVerifyIsStalled(fExtraLen);
297     addInTwoChunksAndVerifyIsStalled(fExtra);
298     addInTwoChunksAndVerifyIsStalled(zeroTerminatedBytes);
299     addInTwoChunksAndVerifyIsStalled(zeroTerminatedBytes);
300     addInTwoChunksAndVerifyIsStalled(headerCrc16);
301 
302     byte[] b = new byte[originalData.length];
303     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
304     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
305 
306     addInTwoChunksAndVerifyIsStalled(gzipTrailer);
307   }
308 
309   @Test
hasPartialData()310   public void hasPartialData() throws Exception {
311     assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData());
312     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
313     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[1]));
314 
315     byte[] b = new byte[originalData.length];
316     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
317     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
318     assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData());
319   }
320 
321   @Test
hasPartialDataWithoutGzipTrailer()322   public void hasPartialDataWithoutGzipTrailer() throws Exception {
323     assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData());
324     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
325     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
326 
327     byte[] b = new byte[originalData.length];
328     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
329     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
330     assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData());
331   }
332 
333   @Test
inflatingCompleteGzipStreamConsumesTrailer()334   public void inflatingCompleteGzipStreamConsumesTrailer() throws Exception {
335     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
336 
337     byte[] b = new byte[originalData.length];
338     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
339     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
340     assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData());
341   }
342 
343   @Test
bytesConsumedForPartiallyInflatedBlock()344   public void bytesConsumedForPartiallyInflatedBlock() throws Exception {
345     int bytesToWithhold = 1;
346     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
347 
348     byte[] b = new byte[truncatedData.length];
349     assertEquals(
350         truncatedData.length - bytesToWithhold,
351         gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length - bytesToWithhold));
352     assertEquals(
353         gzippedTruncatedData.length - bytesToWithhold - GZIP_TRAILER_SIZE,
354         gzipInflatingBuffer.getAndResetBytesConsumed());
355     assertEquals(
356         bytesToWithhold,
357         gzipInflatingBuffer.inflateBytes(
358             b, truncatedData.length - bytesToWithhold, bytesToWithhold));
359     assertEquals(
360         bytesToWithhold + GZIP_TRAILER_SIZE, gzipInflatingBuffer.getAndResetBytesConsumed());
361     assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
362   }
363 
364   @Test
getAndResetCompressedBytesConsumedReportsHeaderFlagBytes()365   public void getAndResetCompressedBytesConsumedReportsHeaderFlagBytes() throws Exception {
366     gzipHeader[GZIP_HEADER_FLAG_INDEX] =
367         (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT);
368     int len = 1025;
369     byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
370     byte[] fExtra = new byte[len];
371     byte[] zeroTerminatedBytes = new byte[len];
372     for (int i = 0; i < len - 1; i++) {
373       zeroTerminatedBytes[i] = 1;
374     }
375     ByteArrayOutputStream newHeader = new ByteArrayOutputStream();
376     newHeader.write(gzipHeader);
377     newHeader.write(fExtraLen);
378     newHeader.write(fExtra);
379     newHeader.write(zeroTerminatedBytes); // FNAME
380     newHeader.write(zeroTerminatedBytes); // FCOMMENT
381     byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray());
382 
383     byte[] b = new byte[originalData.length];
384     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
385     assertEquals(0, gzipInflatingBuffer.getAndResetBytesConsumed());
386 
387     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
388     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
389     assertEquals(gzipHeader.length, gzipInflatingBuffer.getAndResetBytesConsumed());
390 
391     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
392     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
393     assertEquals(fExtraLen.length, gzipInflatingBuffer.getAndResetBytesConsumed());
394 
395     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtra));
396     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
397     assertEquals(fExtra.length, gzipInflatingBuffer.getAndResetBytesConsumed());
398 
399     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
400     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
401     assertEquals(zeroTerminatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed());
402 
403     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
404     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
405     assertEquals(zeroTerminatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed());
406 
407     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16));
408     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
409     assertEquals(headerCrc16.length, gzipInflatingBuffer.getAndResetBytesConsumed());
410 
411     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
412     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
413     assertEquals(deflatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed());
414 
415     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
416     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
417     assertEquals(gzipTrailer.length, gzipInflatingBuffer.getAndResetBytesConsumed());
418   }
419 
420   @Test
getAndResetDeflatedBytesConsumedExcludesGzipMetadata()421   public void getAndResetDeflatedBytesConsumedExcludesGzipMetadata() throws Exception {
422     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
423 
424     byte[] b = new byte[originalData.length];
425     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
426     assertEquals(
427         gzippedData.length - GZIP_HEADER_MIN_SIZE - GZIP_TRAILER_SIZE,
428         gzipInflatingBuffer.getAndResetDeflatedBytesConsumed());
429   }
430 
431   @Test
wrongHeaderMagicShouldFail()432   public void wrongHeaderMagicShouldFail() throws Exception {
433     gzipHeader[1] = (byte) ~(GZIP_MAGIC >> 8);
434     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
435     try {
436       byte[] b = new byte[1];
437       gzipInflatingBuffer.inflateBytes(b, 0, 1);
438       fail("Expected ZipException");
439     } catch (ZipException expectedException) {
440       assertEquals("Not in GZIP format", expectedException.getMessage());
441     }
442   }
443 
444   @Test
wrongHeaderCompressionMethodShouldFail()445   public void wrongHeaderCompressionMethodShouldFail() throws Exception {
446     gzipHeader[2] = 7; // Should be 8
447     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
448     try {
449       byte[] b = new byte[1];
450       gzipInflatingBuffer.inflateBytes(b, 0, 1);
451       fail("Expected ZipException");
452     } catch (ZipException expectedException) {
453       assertEquals("Unsupported compression method", expectedException.getMessage());
454     }
455   }
456 
457   @Test
allHeaderFlagsWork()458   public void allHeaderFlagsWork() throws Exception {
459     gzipHeader[GZIP_HEADER_FLAG_INDEX] =
460         (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT);
461     int len = 1025;
462     byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
463     byte[] fExtra = new byte[len];
464     byte[] zeroTerminatedBytes = new byte[len];
465     for (int i = 0; i < len - 1; i++) {
466       zeroTerminatedBytes[i] = 1;
467     }
468     ByteArrayOutputStream newHeader = new ByteArrayOutputStream();
469     newHeader.write(gzipHeader);
470     newHeader.write(fExtraLen);
471     newHeader.write(fExtra);
472     newHeader.write(zeroTerminatedBytes); // FNAME
473     newHeader.write(zeroTerminatedBytes); // FCOMMENT
474     byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray());
475     newHeader.write(headerCrc16);
476 
477     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(newHeader.toByteArray()));
478     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
479     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
480 
481     byte[] b = new byte[originalData.length];
482     gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
483     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
484   }
485 
486   @Test
headerFTextFlagIsIgnored()487   public void headerFTextFlagIsIgnored() throws Exception {
488     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT);
489     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
490     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
491     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
492 
493     byte[] b = new byte[originalData.length];
494     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
495     assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
496     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
497   }
498 
499   @Test
headerFhcrcFlagWorks()500   public void headerFhcrcFlagWorks() throws Exception {
501     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FHCRC);
502 
503     byte[] headerCrc16 = getHeaderCrc16Bytes(gzipHeader);
504 
505     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
506     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16));
507     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
508     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
509 
510     byte[] b = new byte[originalData.length];
511     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
512     assertEquals(
513         gzippedData.length + headerCrc16.length, gzipInflatingBuffer.getAndResetBytesConsumed());
514     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
515   }
516 
517   @Test
headerInvalidFhcrcFlagFails()518   public void headerInvalidFhcrcFlagFails() throws Exception {
519     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FHCRC);
520 
521     byte[] headerCrc16 = getHeaderCrc16Bytes(gzipHeader);
522     headerCrc16[0] = (byte) ~headerCrc16[0];
523 
524     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
525     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16));
526     try {
527       byte[] b = new byte[1];
528       gzipInflatingBuffer.inflateBytes(b, 0, 1);
529       fail("Expected ZipException");
530     } catch (ZipException expectedException) {
531       assertEquals("Corrupt GZIP header", expectedException.getMessage());
532     }
533   }
534 
535   @Test
headerFExtraFlagWorks()536   public void headerFExtraFlagWorks() throws Exception {
537     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
538 
539     int len = 1025;
540     byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
541     byte[] fExtra = new byte[len];
542 
543     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
544     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
545     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtra));
546     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
547     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
548 
549     byte[] b = new byte[originalData.length];
550     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
551     assertEquals(
552         gzippedData.length + fExtraLen.length + fExtra.length,
553         gzipInflatingBuffer.getAndResetBytesConsumed());
554     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
555   }
556 
557   @Test
headerFExtraFlagWithZeroLenWorks()558   public void headerFExtraFlagWithZeroLenWorks() throws Exception {
559     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
560     byte[] fExtraLen = new byte[2];
561 
562     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
563     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
564     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
565     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
566 
567     byte[] b = new byte[originalData.length];
568     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
569     assertEquals(
570         gzippedData.length + fExtraLen.length, gzipInflatingBuffer.getAndResetBytesConsumed());
571     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
572   }
573 
574   @Test
headerFExtraFlagWithMissingExtraLenFails()575   public void headerFExtraFlagWithMissingExtraLenFails() throws Exception {
576     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
577 
578     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
579     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
580     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
581 
582     try {
583       byte[] b = new byte[originalData.length];
584       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
585       fail("Expected DataFormatException");
586     } catch (DataFormatException expectedException) {
587       assertTrue(
588           "wrong exception message",
589           expectedException.getMessage().startsWith("Inflater data format exception:"));
590     }
591   }
592 
593   @Test
headerFExtraFlagWithMissingExtraBytesFails()594   public void headerFExtraFlagWithMissingExtraBytesFails() throws Exception {
595     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
596 
597     int len = 5;
598     byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
599 
600     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
601     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
602     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
603     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
604 
605     try {
606       byte[] b = new byte[originalData.length];
607       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
608       fail("Expected DataFormatException");
609     } catch (DataFormatException expectedException) {
610       assertTrue(
611           "wrong exception message",
612           expectedException.getMessage().startsWith("Inflater data format exception:"));
613     }
614   }
615 
616   @Test
headerFNameFlagWorks()617   public void headerFNameFlagWorks() throws Exception {
618     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FNAME);
619     int len = 1025;
620     byte[] zeroTerminatedBytes = new byte[len];
621     for (int i = 0; i < len - 1; i++) {
622       zeroTerminatedBytes[i] = 1;
623     }
624 
625     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
626     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
627     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
628     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
629 
630     byte[] b = new byte[originalData.length];
631     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
632     assertEquals(gzippedData.length + len, gzipInflatingBuffer.getAndResetBytesConsumed());
633     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
634   }
635 
636   @Test
headerFNameFlagWithMissingBytesFail()637   public void headerFNameFlagWithMissingBytesFail() throws Exception {
638     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FNAME);
639     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
640     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
641     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
642 
643     try {
644       byte[] b = new byte[originalData.length];
645       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
646       fail("Expected DataFormatException");
647     } catch (DataFormatException expectedException) {
648       assertTrue(
649           "wrong exception message",
650           expectedException.getMessage().startsWith("Inflater data format exception:"));
651     }
652   }
653 
654   @Test
headerFCommentFlagWorks()655   public void headerFCommentFlagWorks() throws Exception {
656     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FCOMMENT);
657     int len = 1025;
658     byte[] zeroTerminatedBytes = new byte[len];
659     for (int i = 0; i < len - 1; i++) {
660       zeroTerminatedBytes[i] = 1;
661     }
662 
663     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
664     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
665     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
666     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
667 
668     byte[] b = new byte[originalData.length];
669     assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
670     assertEquals(gzippedData.length + len, gzipInflatingBuffer.getAndResetBytesConsumed());
671     assertTrue("inflated data does not match", Arrays.equals(originalData, b));
672   }
673 
674   @Test
headerFCommentFlagWithMissingBytesFail()675   public void headerFCommentFlagWithMissingBytesFail() throws Exception {
676     gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FCOMMENT);
677 
678     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
679     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
680     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
681     try {
682       byte[] b = new byte[originalData.length];
683       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
684       fail("Expected DataFormatException");
685     } catch (DataFormatException expectedException) {
686       assertTrue(
687           "wrong exception message",
688           expectedException.getMessage().startsWith("Inflater data format exception:"));
689     }
690   }
691 
692   @Test
wrongTrailerCrcShouldFail()693   public void wrongTrailerCrcShouldFail() throws Exception {
694     gzipTrailer[0] = (byte) ~gzipTrailer[0];
695     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
696     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
697     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
698 
699     try {
700       byte[] b = new byte[originalData.length];
701       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
702       fail("Expected ZipException");
703     } catch (ZipException expectedException) {
704       assertEquals("Corrupt GZIP trailer", expectedException.getMessage());
705     }
706   }
707 
708   @Test
wrongTrailerISizeShouldFail()709   public void wrongTrailerISizeShouldFail() throws Exception {
710     gzipTrailer[GZIP_TRAILER_SIZE - 1] = (byte) ~gzipTrailer[GZIP_TRAILER_SIZE - 1];
711     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
712     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
713     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
714 
715     try {
716       byte[] b = new byte[originalData.length];
717       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
718       fail("Expected ZipException");
719     } catch (ZipException expectedException) {
720       assertEquals("Corrupt GZIP trailer", expectedException.getMessage());
721     }
722   }
723 
724   @Test
invalidDeflateBlockShouldFail()725   public void invalidDeflateBlockShouldFail() throws Exception {
726     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
727     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[10]));
728 
729     try {
730       byte[] b = new byte[originalData.length];
731       gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
732       fail("Expected DataFormatException");
733     } catch (DataFormatException expectedException) {
734       assertTrue(
735           "wrong exception message",
736           expectedException.getMessage().startsWith("Inflater data format exception:"));
737     }
738   }
739 
addInTwoChunksAndVerifyIsStalled(byte[] input)740   private void addInTwoChunksAndVerifyIsStalled(byte[] input) throws Exception {
741     byte[] b = new byte[1];
742 
743     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(input, 0, input.length - 1));
744     assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
745 
746     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
747     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
748 
749     gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(input, input.length - 1, 1));
750     assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
751 
752     assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
753     assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
754   }
755 
getHeaderCrc16Bytes(byte[] headerBytes)756   private byte[] getHeaderCrc16Bytes(byte[] headerBytes) {
757     CRC32 crc = new CRC32();
758     crc.update(headerBytes);
759     byte[] headerCrc16 = {(byte) crc.getValue(), (byte) (crc.getValue() >> 8)};
760     return headerCrc16;
761   }
762 }
763