| /* |
| * Copyright 2017 The gRPC Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package io.grpc.internal; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import com.google.common.io.ByteStreams; |
| import java.io.ByteArrayOutputStream; |
| import java.io.OutputStream; |
| import java.util.Arrays; |
| import java.util.zip.CRC32; |
| import java.util.zip.DataFormatException; |
| import java.util.zip.GZIPOutputStream; |
| import java.util.zip.ZipException; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Unit tests for {@link GzipInflatingBuffer}. */ |
| @RunWith(JUnit4.class) |
| public class GzipInflatingBufferTest { |
| private static final String UNCOMPRESSABLE_FILE = "/io/grpc/internal/uncompressable.bin"; |
| |
| private static final int GZIP_HEADER_MIN_SIZE = 10; |
| private static final int GZIP_TRAILER_SIZE = 8; |
| private static final int GZIP_HEADER_FLAG_INDEX = 3; |
| |
| public static final int GZIP_MAGIC = 0x8b1f; |
| |
| private static final int FTEXT = 1; |
| private static final int FHCRC = 2; |
| private static final int FEXTRA = 4; |
| private static final int FNAME = 8; |
| private static final int FCOMMENT = 16; |
| |
| private static final int TRUNCATED_DATA_SIZE = 10; |
| |
| private byte[] originalData; |
| private byte[] gzippedData; |
| private byte[] gzipHeader; |
| private byte[] deflatedBytes; |
| private byte[] gzipTrailer; |
| private byte[] truncatedData; |
| private byte[] gzippedTruncatedData; |
| |
| private GzipInflatingBuffer gzipInflatingBuffer; |
| |
| @Before |
| public void setUp() { |
| gzipInflatingBuffer = new GzipInflatingBuffer(); |
| try { |
| originalData = ByteStreams.toByteArray(getClass().getResourceAsStream(UNCOMPRESSABLE_FILE)); |
| truncatedData = Arrays.copyOf(originalData, TRUNCATED_DATA_SIZE); |
| |
| ByteArrayOutputStream gzippedOutputStream = new ByteArrayOutputStream(); |
| OutputStream gzippingOutputStream = new GZIPOutputStream(gzippedOutputStream); |
| gzippingOutputStream.write(originalData); |
| gzippingOutputStream.close(); |
| gzippedData = gzippedOutputStream.toByteArray(); |
| gzippedOutputStream.close(); |
| |
| gzipHeader = Arrays.copyOf(gzippedData, GZIP_HEADER_MIN_SIZE); |
| deflatedBytes = |
| Arrays.copyOfRange( |
| gzippedData, GZIP_HEADER_MIN_SIZE, gzippedData.length - GZIP_TRAILER_SIZE); |
| gzipTrailer = |
| Arrays.copyOfRange( |
| gzippedData, gzippedData.length - GZIP_TRAILER_SIZE, gzippedData.length); |
| |
| ByteArrayOutputStream truncatedGzippedOutputStream = new ByteArrayOutputStream(); |
| OutputStream smallerGzipCompressingStream = |
| new GZIPOutputStream(truncatedGzippedOutputStream); |
| smallerGzipCompressingStream.write(truncatedData); |
| smallerGzipCompressingStream.close(); |
| gzippedTruncatedData = truncatedGzippedOutputStream.toByteArray(); |
| truncatedGzippedOutputStream.close(); |
| } catch (Exception e) { |
| throw new RuntimeException("Failed to set up compressed data", e); |
| } |
| } |
| |
| @After |
| public void tearDown() { |
| gzipInflatingBuffer.close(); |
| } |
| |
| @Test |
| public void gzipInflateWorks() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void splitGzipStreamWorks() throws Exception { |
| int initialBytes = 100; |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData, 0, initialBytes)); |
| |
| byte[] b = new byte[originalData.length]; |
| int n = gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| assertTrue("inflated bytes expected", n > 0); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| assertEquals(initialBytes, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes( |
| ReadableBuffers.wrap(gzippedData, initialBytes, gzippedData.length - initialBytes)); |
| int bytesRemaining = originalData.length - n; |
| assertEquals(bytesRemaining, gzipInflatingBuffer.inflateBytes(b, n, bytesRemaining)); |
| assertEquals(gzippedData.length - initialBytes, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void inflateBytesObeysOffsetAndLength() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| int offset = 10; |
| int length = 100; |
| byte[] b = new byte[offset + length + offset]; |
| assertEquals(length, gzipInflatingBuffer.inflateBytes(b, offset, length)); |
| assertTrue( |
| "bytes written before offset", |
| Arrays.equals(new byte[offset], Arrays.copyOfRange(b, 0, offset))); |
| assertTrue( |
| "inflated data does not match", |
| Arrays.equals( |
| Arrays.copyOfRange(originalData, 0, length), |
| Arrays.copyOfRange(b, offset, offset + length))); |
| assertTrue( |
| "bytes written beyond length", |
| Arrays.equals( |
| new byte[offset], Arrays.copyOfRange(b, offset + length, offset + length + offset))); |
| } |
| |
| @Test |
| public void concatenatedStreamsWorks() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| |
| assertEquals( |
| truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length)); |
| assertEquals(gzippedTruncatedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| |
| assertEquals( |
| truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length)); |
| assertEquals(gzippedTruncatedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void requestingTooManyBytesStillReturnsEndOfBlock() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| int len = 2 * originalData.length; |
| byte[] b = new byte[len]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, len)); |
| assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue(gzipInflatingBuffer.isStalled()); |
| assertTrue( |
| "inflated data does not match", |
| Arrays.equals(originalData, Arrays.copyOf(b, originalData.length))); |
| } |
| |
| @Test |
| public void closeStopsDecompression() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| byte[] b = new byte[1]; |
| gzipInflatingBuffer.inflateBytes(b, 0, 1); |
| gzipInflatingBuffer.close(); |
| try { |
| gzipInflatingBuffer.inflateBytes(b, 0, 1); |
| fail("Expected IllegalStateException"); |
| } catch (IllegalStateException expectedException) { |
| assertEquals("GzipInflatingBuffer is closed", expectedException.getMessage()); |
| } |
| } |
| |
| @Test |
| public void isStalledReturnsTrueAtEndOfStream() throws Exception { |
| int bytesToWithhold = 10; |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length - bytesToWithhold); |
| assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled()); |
| |
| gzipInflatingBuffer.inflateBytes(b, originalData.length - bytesToWithhold, bytesToWithhold); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| } |
| |
| @Test |
| public void isStalledReturnsFalseBetweenStreams() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled()); |
| |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| } |
| |
| @Test |
| public void isStalledReturnsFalseBetweenSmallStreams() throws Exception { |
| // Use small streams to make sure that they all fit in the inflater buffer |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData)); |
| |
| byte[] b = new byte[truncatedData.length]; |
| assertEquals( |
| truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(truncatedData, b)); |
| assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled()); |
| |
| assertEquals( |
| truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(truncatedData, b)); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| } |
| |
| @Test |
| public void isStalledReturnsTrueWithPartialNextHeaderAvailable() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[1])); |
| |
| byte[] b = new byte[truncatedData.length]; |
| assertEquals( |
| truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(truncatedData, b)); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData()); |
| } |
| |
| @Test |
| public void isStalledWorksWithAllHeaderFlags() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = |
| (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT); |
| int len = 1025; |
| byte[] fExtraLen = {(byte) len, (byte) (len >> 8)}; |
| byte[] fExtra = new byte[len]; |
| byte[] zeroTerminatedBytes = new byte[len]; |
| for (int i = 0; i < len - 1; i++) { |
| zeroTerminatedBytes[i] = 1; |
| } |
| ByteArrayOutputStream newHeader = new ByteArrayOutputStream(); |
| newHeader.write(gzipHeader); |
| newHeader.write(fExtraLen); |
| newHeader.write(fExtra); |
| newHeader.write(zeroTerminatedBytes); // FNAME |
| newHeader.write(zeroTerminatedBytes); // FCOMMENT |
| byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray()); |
| |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| |
| addInTwoChunksAndVerifyIsStalled(gzipHeader); |
| addInTwoChunksAndVerifyIsStalled(fExtraLen); |
| addInTwoChunksAndVerifyIsStalled(fExtra); |
| addInTwoChunksAndVerifyIsStalled(zeroTerminatedBytes); |
| addInTwoChunksAndVerifyIsStalled(zeroTerminatedBytes); |
| addInTwoChunksAndVerifyIsStalled(headerCrc16); |
| |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| |
| addInTwoChunksAndVerifyIsStalled(gzipTrailer); |
| } |
| |
| @Test |
| public void hasPartialData() throws Exception { |
| assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData()); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[1])); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData()); |
| } |
| |
| @Test |
| public void hasPartialDataWithoutGzipTrailer() throws Exception { |
| assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData()); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData()); |
| } |
| |
| @Test |
| public void inflatingCompleteGzipStreamConsumesTrailer() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData()); |
| } |
| |
| @Test |
| public void bytesConsumedForPartiallyInflatedBlock() throws Exception { |
| int bytesToWithhold = 1; |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData)); |
| |
| byte[] b = new byte[truncatedData.length]; |
| assertEquals( |
| truncatedData.length - bytesToWithhold, |
| gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length - bytesToWithhold)); |
| assertEquals( |
| gzippedTruncatedData.length - bytesToWithhold - GZIP_TRAILER_SIZE, |
| gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertEquals( |
| bytesToWithhold, |
| gzipInflatingBuffer.inflateBytes( |
| b, truncatedData.length - bytesToWithhold, bytesToWithhold)); |
| assertEquals( |
| bytesToWithhold + GZIP_TRAILER_SIZE, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(truncatedData, b)); |
| } |
| |
| @Test |
| public void getAndResetCompressedBytesConsumedReportsHeaderFlagBytes() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = |
| (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT); |
| int len = 1025; |
| byte[] fExtraLen = {(byte) len, (byte) (len >> 8)}; |
| byte[] fExtra = new byte[len]; |
| byte[] zeroTerminatedBytes = new byte[len]; |
| for (int i = 0; i < len - 1; i++) { |
| zeroTerminatedBytes[i] = 1; |
| } |
| ByteArrayOutputStream newHeader = new ByteArrayOutputStream(); |
| newHeader.write(gzipHeader); |
| newHeader.write(fExtraLen); |
| newHeader.write(fExtra); |
| newHeader.write(zeroTerminatedBytes); // FNAME |
| newHeader.write(zeroTerminatedBytes); // FCOMMENT |
| byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray()); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(0, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(gzipHeader.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(fExtraLen.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtra)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(fExtra.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(zeroTerminatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(zeroTerminatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(headerCrc16.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(deflatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertEquals(gzipTrailer.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| } |
| |
| @Test |
| public void getAndResetDeflatedBytesConsumedExcludesGzipMetadata() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals( |
| gzippedData.length - GZIP_HEADER_MIN_SIZE - GZIP_TRAILER_SIZE, |
| gzipInflatingBuffer.getAndResetDeflatedBytesConsumed()); |
| } |
| |
| @Test |
| public void wrongHeaderMagicShouldFail() throws Exception { |
| gzipHeader[1] = (byte) ~(GZIP_MAGIC >> 8); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| try { |
| byte[] b = new byte[1]; |
| gzipInflatingBuffer.inflateBytes(b, 0, 1); |
| fail("Expected ZipException"); |
| } catch (ZipException expectedException) { |
| assertEquals("Not in GZIP format", expectedException.getMessage()); |
| } |
| } |
| |
| @Test |
| public void wrongHeaderCompressionMethodShouldFail() throws Exception { |
| gzipHeader[2] = 7; // Should be 8 |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| try { |
| byte[] b = new byte[1]; |
| gzipInflatingBuffer.inflateBytes(b, 0, 1); |
| fail("Expected ZipException"); |
| } catch (ZipException expectedException) { |
| assertEquals("Unsupported compression method", expectedException.getMessage()); |
| } |
| } |
| |
| @Test |
| public void allHeaderFlagsWork() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = |
| (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT); |
| int len = 1025; |
| byte[] fExtraLen = {(byte) len, (byte) (len >> 8)}; |
| byte[] fExtra = new byte[len]; |
| byte[] zeroTerminatedBytes = new byte[len]; |
| for (int i = 0; i < len - 1; i++) { |
| zeroTerminatedBytes[i] = 1; |
| } |
| ByteArrayOutputStream newHeader = new ByteArrayOutputStream(); |
| newHeader.write(gzipHeader); |
| newHeader.write(fExtraLen); |
| newHeader.write(fExtra); |
| newHeader.write(zeroTerminatedBytes); // FNAME |
| newHeader.write(zeroTerminatedBytes); // FCOMMENT |
| byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray()); |
| newHeader.write(headerCrc16); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(newHeader.toByteArray())); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerFTextFlagIsIgnored() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerFhcrcFlagWorks() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FHCRC); |
| |
| byte[] headerCrc16 = getHeaderCrc16Bytes(gzipHeader); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals( |
| gzippedData.length + headerCrc16.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerInvalidFhcrcFlagFails() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FHCRC); |
| |
| byte[] headerCrc16 = getHeaderCrc16Bytes(gzipHeader); |
| headerCrc16[0] = (byte) ~headerCrc16[0]; |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16)); |
| try { |
| byte[] b = new byte[1]; |
| gzipInflatingBuffer.inflateBytes(b, 0, 1); |
| fail("Expected ZipException"); |
| } catch (ZipException expectedException) { |
| assertEquals("Corrupt GZIP header", expectedException.getMessage()); |
| } |
| } |
| |
| @Test |
| public void headerFExtraFlagWorks() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA); |
| |
| int len = 1025; |
| byte[] fExtraLen = {(byte) len, (byte) (len >> 8)}; |
| byte[] fExtra = new byte[len]; |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtra)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals( |
| gzippedData.length + fExtraLen.length + fExtra.length, |
| gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerFExtraFlagWithZeroLenWorks() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA); |
| byte[] fExtraLen = new byte[2]; |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals( |
| gzippedData.length + fExtraLen.length, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerFExtraFlagWithMissingExtraLenFails() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected DataFormatException"); |
| } catch (DataFormatException expectedException) { |
| assertTrue( |
| "wrong exception message", |
| expectedException.getMessage().startsWith("Inflater data format exception:")); |
| } |
| } |
| |
| @Test |
| public void headerFExtraFlagWithMissingExtraBytesFails() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA); |
| |
| int len = 5; |
| byte[] fExtraLen = {(byte) len, (byte) (len >> 8)}; |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected DataFormatException"); |
| } catch (DataFormatException expectedException) { |
| assertTrue( |
| "wrong exception message", |
| expectedException.getMessage().startsWith("Inflater data format exception:")); |
| } |
| } |
| |
| @Test |
| public void headerFNameFlagWorks() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FNAME); |
| int len = 1025; |
| byte[] zeroTerminatedBytes = new byte[len]; |
| for (int i = 0; i < len - 1; i++) { |
| zeroTerminatedBytes[i] = 1; |
| } |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(gzippedData.length + len, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerFNameFlagWithMissingBytesFail() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FNAME); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected DataFormatException"); |
| } catch (DataFormatException expectedException) { |
| assertTrue( |
| "wrong exception message", |
| expectedException.getMessage().startsWith("Inflater data format exception:")); |
| } |
| } |
| |
| @Test |
| public void headerFCommentFlagWorks() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FCOMMENT); |
| int len = 1025; |
| byte[] zeroTerminatedBytes = new byte[len]; |
| for (int i = 0; i < len - 1; i++) { |
| zeroTerminatedBytes[i] = 1; |
| } |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| byte[] b = new byte[originalData.length]; |
| assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length)); |
| assertEquals(gzippedData.length + len, gzipInflatingBuffer.getAndResetBytesConsumed()); |
| assertTrue("inflated data does not match", Arrays.equals(originalData, b)); |
| } |
| |
| @Test |
| public void headerFCommentFlagWithMissingBytesFail() throws Exception { |
| gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FCOMMENT); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected DataFormatException"); |
| } catch (DataFormatException expectedException) { |
| assertTrue( |
| "wrong exception message", |
| expectedException.getMessage().startsWith("Inflater data format exception:")); |
| } |
| } |
| |
| @Test |
| public void wrongTrailerCrcShouldFail() throws Exception { |
| gzipTrailer[0] = (byte) ~gzipTrailer[0]; |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected ZipException"); |
| } catch (ZipException expectedException) { |
| assertEquals("Corrupt GZIP trailer", expectedException.getMessage()); |
| } |
| } |
| |
| @Test |
| public void wrongTrailerISizeShouldFail() throws Exception { |
| gzipTrailer[GZIP_TRAILER_SIZE - 1] = (byte) ~gzipTrailer[GZIP_TRAILER_SIZE - 1]; |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer)); |
| |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected ZipException"); |
| } catch (ZipException expectedException) { |
| assertEquals("Corrupt GZIP trailer", expectedException.getMessage()); |
| } |
| } |
| |
| @Test |
| public void invalidDeflateBlockShouldFail() throws Exception { |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader)); |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[10])); |
| |
| try { |
| byte[] b = new byte[originalData.length]; |
| gzipInflatingBuffer.inflateBytes(b, 0, originalData.length); |
| fail("Expected DataFormatException"); |
| } catch (DataFormatException expectedException) { |
| assertTrue( |
| "wrong exception message", |
| expectedException.getMessage().startsWith("Inflater data format exception:")); |
| } |
| } |
| |
| private void addInTwoChunksAndVerifyIsStalled(byte[] input) throws Exception { |
| byte[] b = new byte[1]; |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(input, 0, input.length - 1)); |
| assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled()); |
| |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| |
| gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(input, input.length - 1, 1)); |
| assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled()); |
| |
| assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1)); |
| assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled()); |
| } |
| |
| private byte[] getHeaderCrc16Bytes(byte[] headerBytes) { |
| CRC32 crc = new CRC32(); |
| crc.update(headerBytes); |
| byte[] headerCrc16 = {(byte) crc.getValue(), (byte) (crc.getValue() >> 8)}; |
| return headerCrc16; |
| } |
| } |