From 8f4f8ea1c6d82a7228bb27476d88ec8870d01084 Mon Sep 17 00:00:00 2001 From: "Taro L. Saito" Date: Sat, 3 Oct 2026 00:21:01 -0700 Subject: [PATCH] Validate buffer bounds and declared lengths before calling native code The JNI layer writes through raw pointers without any bounds check, so the Java API must validate every range it hands to native code. - compress/rawCompress: require maxCompressedLength(inputLength) bytes of output space and an in-range input (#732, #717) - uncompress/rawUncompress: require the declared uncompressed length to fit in the output buffer (#728, CVE-2026-90559, #717) - uncompress*Array: reject uncompressed lengths that are not a multiple of the element size, which under-allocated the result array (#729) - uncompressedLength: reject negative lengths and lengths no valid Snappy stream of the given size can produce (> 64/3 of the compressed size), preventing huge allocations from a few bytes of input (#625, #730) - SnappyFramedInputStream: skip consecutive skippable chunks iteratively instead of recursing once per 4-byte chunk (#731) Co-Authored-By: Claude Opus 5.5 --- src/main/java/org/xerial/snappy/Snappy.java | 145 +++++++- .../snappy/SnappyFramedInputStream.java | 24 +- .../xerial/snappy/SnappyBoundsCheckTest.java | 335 ++++++++++++++++++ 3 files changed, 490 insertions(+), 14 deletions(-) create mode 100644 src/test/java/org/xerial/snappy/SnappyBoundsCheckTest.java diff --git a/src/main/java/org/xerial/snappy/Snappy.java b/src/main/java/org/xerial/snappy/Snappy.java index 14d17964..6aeea3e6 100755 --- a/src/main/java/org/xerial/snappy/Snappy.java +++ b/src/main/java/org/xerial/snappy/Snappy.java @@ -133,6 +133,8 @@ public static int compress(byte[] input, int inputOffset, int inputLength, byte[ * @param compressed output of the compressed data. Uses range [pos()..]. * @return byte size of the compressed data. * @throws SnappyError when the input is not a direct buffer + * @throws IllegalArgumentException when the output buffer has less than {@link #maxCompressedLength(int)} bytes + * remaining */ public static int compress(ByteBuffer uncompressed, ByteBuffer compressed) throws IOException @@ -150,6 +152,7 @@ public static int compress(ByteBuffer uncompressed, ByteBuffer compressed) int uPos = uncompressed.position(); int uLen = uncompressed.remaining(); int cPos = compressed.position(); + checkOutputSpace(compressed.remaining(), 0, requiredCompressedLength(uLen)); int compressedSize = impl.rawCompress(uncompressed, uPos, uLen, compressed, cPos); @@ -354,6 +357,7 @@ public static boolean isValidCompressedBuffer(byte[] input, int offset, int leng if (input == null) { throw new NullPointerException("input is null"); } + checkArrayRange(input, offset, length, "input"); return impl.isValidCompressedBuffer(input, offset, length); } @@ -446,7 +450,11 @@ public static long rawUncompress(long inputAddr, long inputSize, long destAddr) public static byte[] rawCompress(Object data, int byteSize) throws IOException { - byte[] buf = new byte[Snappy.maxCompressedLength(byteSize)]; + if (data == null) { + throw new NullPointerException("input is null"); + } + checkArrayRange(data, 0, byteSize, "input"); + byte[] buf = new byte[requiredCompressedLength(byteSize)]; int compressedByteSize = impl.rawCompress(data, 0, byteSize, buf, 0); byte[] result = new byte[compressedByteSize]; System.arraycopy(buf, 0, result, 0, compressedByteSize); @@ -464,6 +472,8 @@ public static byte[] rawCompress(Object data, int byteSize) * @param outputOffset byte offset at the output array * @return byte size of the compressed data * @throws IOException + * @throws IllegalArgumentException if the input range is out of bounds, or the output array does not have + * {@link #maxCompressedLength(int)} bytes available after outputOffset */ public static int rawCompress(Object input, int inputOffset, int inputLength, byte[] output, int outputOffset) throws IOException @@ -471,6 +481,8 @@ public static int rawCompress(Object input, int inputOffset, int inputLength, by if (input == null || output == null) { throw new NullPointerException("input or output is null"); } + checkArrayRange(input, inputOffset, inputLength, "input"); + checkOutputSpace(output.length, outputOffset, requiredCompressedLength(inputLength)); int compressedSize = impl .rawCompress(input, inputOffset, inputLength, output, outputOffset); @@ -494,6 +506,8 @@ public static int rawCompress(Object input, int inputOffset, int inputLength, by * @param outputOffset byte offset in the output buffer * @return the byte size of the uncompressed data * @throws IOException when failed to uncompress the input data + * @throws IllegalArgumentException if the input range is out of bounds, or the output array does not have + * enough space after outputOffset for the uncompressed data */ public static int rawUncompress(byte[] input, int inputOffset, int inputLength, Object output, int outputOffset) throws IOException @@ -501,9 +515,118 @@ public static int rawUncompress(byte[] input, int inputOffset, int inputLength, if (input == null || output == null) { throw new NullPointerException("input or output is null"); } + // uncompressedLength validates the input range + int requiredSize = uncompressedLength(input, inputOffset, inputLength); + checkOutputSpace(byteLengthOf(output), outputOffset, requiredSize); return impl.rawUncompress(input, inputOffset, inputLength, output, outputOffset); } + /** + * Returns the byte size of the given primitive array + */ + private static long byteLengthOf(Object array) + { + if (array instanceof byte[]) { + return ((byte[]) array).length; + } + if (array instanceof boolean[]) { + return ((boolean[]) array).length; + } + if (array instanceof char[]) { + return 2L * ((char[]) array).length; + } + if (array instanceof short[]) { + return 2L * ((short[]) array).length; + } + if (array instanceof int[]) { + return 4L * ((int[]) array).length; + } + if (array instanceof float[]) { + return 4L * ((float[]) array).length; + } + if (array instanceof long[]) { + return 8L * ((long[]) array).length; + } + if (array instanceof double[]) { + return 8L * ((double[]) array).length; + } + throw new IllegalArgumentException("not a primitive array: " + array.getClass().getName()); + } + + /** + * Ensures the byte range [offset, offset+length) lies within the given primitive array, since the native code + * accesses the array through a raw pointer without any bounds check. + */ + private static void checkArrayRange(Object array, int offset, int length, String name) + { + long size = byteLengthOf(array); + if (offset < 0 || length < 0 || (long) offset + length > size) { + throw new IllegalArgumentException(String.format( + "%s range [offset:%,d, length:%,d] is out of bounds for %s of %,d bytes", + name, offset, length, name, size)); + } + } + + /** + * Ensures the output has at least requiredSize bytes available after outputOffset, since the native code writes + * the output through a raw pointer without any bounds check. + */ + private static void checkOutputSpace(long outputSize, long outputOffset, long requiredSize) + { + if (outputOffset < 0 || outputOffset > outputSize) { + throw new IllegalArgumentException(String.format( + "output offset %,d is out of bounds for output of %,d bytes", outputOffset, outputSize)); + } + if (outputSize - outputOffset < requiredSize) { + throw new IllegalArgumentException(String.format( + "not enough space for output: need %,d bytes, but only %,d remaining", + requiredSize, outputSize - outputOffset)); + } + } + + /** + * Returns the output buffer size required by the native compressor for the given input size. + */ + private static int requiredCompressedLength(int inputLength) + { + int maxCompressedLength = maxCompressedLength(inputLength); + // maxCompressedLength overflows int for inputs close to 2GB + if (maxCompressedLength < 0) { + throw new SnappyError(SnappyErrorCode.TOO_LARGE_INPUT, "input is too large to compress: " + inputLength); + } + return maxCompressedLength; + } + + /** + * Rejects an uncompressed length declared in a compressed stream header that no valid Snappy stream of the + * given compressed size can produce. The densest Snappy element is a 3-byte copy that expands to 64 bytes, so + * a valid stream never declares more than 64/3 times its compressed size. This prevents allocating huge (or + * negative-size) buffers from a few bytes of crafted input before decompression detects the corruption. + */ + private static long checkUncompressedLength(long uncompressedLength, long compressedLength) + throws IOException + { + if (uncompressedLength < 0 || uncompressedLength > compressedLength * 64 / 3) { + throw new SnappyIOException(SnappyErrorCode.PARSING_ERROR, String.format( + "invalid uncompressed length %,d declared in %,d bytes of compressed data", + uncompressedLength, compressedLength)); + } + return uncompressedLength; + } + + /** + * Rejects an uncompressed length that is not a multiple of the element size of the typed array to produce. + */ + private static void checkElementAlignment(int uncompressedLength, int elementSize) + throws IOException + { + if (uncompressedLength % elementSize != 0) { + throw new SnappyIOException(SnappyErrorCode.FAILED_TO_UNCOMPRESS, String.format( + "uncompressed length %,d is not a multiple of the element size %d", + uncompressedLength, elementSize)); + } + } + /** * High-level API for uncompressing the input byte array. * @@ -557,6 +680,8 @@ public static int uncompress(byte[] input, int inputOffset, int inputLength, byt * @return uncompressed data size * @throws IOException when failed to uncompress the given input * @throws SnappyError when the input is not a direct buffer + * @throws IllegalArgumentException when the output buffer does not have enough space remaining for the + * uncompressed data */ public static int uncompress(ByteBuffer compressed, ByteBuffer uncompressed) throws IOException @@ -572,6 +697,7 @@ public static int uncompress(ByteBuffer compressed, ByteBuffer uncompressed) int cPos = compressed.position(); int cLen = compressed.remaining(); int uPos = uncompressed.position(); + checkOutputSpace(uncompressed.remaining(), 0, uncompressedLength(compressed)); // pos limit // [ ......UUUUUU.........] @@ -608,6 +734,7 @@ public static char[] uncompressCharArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + checkElementAlignment(uncompressedLength, 2); char[] result = new char[uncompressedLength / 2]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -639,6 +766,7 @@ public static double[] uncompressDoubleArray(byte[] input, int offset, int lengt throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + checkElementAlignment(uncompressedLength, 8); double[] result = new double[uncompressedLength / 8]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -656,7 +784,7 @@ public static double[] uncompressDoubleArray(byte[] input, int offset, int lengt public static int uncompressedLength(byte[] input) throws IOException { - return impl.uncompressedLength(input, 0, input.length); + return uncompressedLength(input, 0, input.length); } /** @@ -676,8 +804,9 @@ public static int uncompressedLength(byte[] input, int offset, int length) if (input == null) { throw new NullPointerException("input is null"); } + checkArrayRange(input, offset, length, "input"); - return impl.uncompressedLength(input, offset, length); + return (int) checkUncompressedLength(impl.uncompressedLength(input, offset, length), length); } /** @@ -697,7 +826,9 @@ public static int uncompressedLength(ByteBuffer compressed) throw new SnappyError(SnappyErrorCode.NOT_A_DIRECT_BUFFER, "input is not a direct buffer"); } - return impl.uncompressedLength(compressed, compressed.position(), compressed.remaining()); + return (int) checkUncompressedLength( + impl.uncompressedLength(compressed, compressed.position(), compressed.remaining()), + compressed.remaining()); } /** @@ -712,7 +843,7 @@ public static int uncompressedLength(ByteBuffer compressed) public static long uncompressedLength(long inputAddr, long len) throws IOException { - return impl.uncompressedLength(inputAddr, len); + return checkUncompressedLength(impl.uncompressedLength(inputAddr, len), len); } /** @@ -741,6 +872,7 @@ public static float[] uncompressFloatArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + checkElementAlignment(uncompressedLength, 4); float[] result = new float[uncompressedLength / 4]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -772,6 +904,7 @@ public static int[] uncompressIntArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + checkElementAlignment(uncompressedLength, 4); int[] result = new int[uncompressedLength / 4]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -803,6 +936,7 @@ public static long[] uncompressLongArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + checkElementAlignment(uncompressedLength, 8); long[] result = new long[uncompressedLength / 8]; impl.rawUncompress(input, offset, length, result, 0); return result; @@ -834,6 +968,7 @@ public static short[] uncompressShortArray(byte[] input, int offset, int length) throws IOException { int uncompressedLength = Snappy.uncompressedLength(input, offset, length); + checkElementAlignment(uncompressedLength, 2); short[] result = new short[uncompressedLength / 2]; impl.rawUncompress(input, offset, length, result, 0); return result; diff --git a/src/main/java/org/xerial/snappy/SnappyFramedInputStream.java b/src/main/java/org/xerial/snappy/SnappyFramedInputStream.java index 8e975735..f4ef064d 100644 --- a/src/main/java/org/xerial/snappy/SnappyFramedInputStream.java +++ b/src/main/java/org/xerial/snappy/SnappyFramedInputStream.java @@ -517,18 +517,22 @@ private boolean ensureBuffer() return false; } - if (!readBlockHeader()) { - eof = true; - return false; - } - - // get action based on header - final FrameMetaData frameMetaData = getFrameMetaData(frameHeader); + // Skip chunks in a loop rather than recursively: a zero-length skippable chunk is only 4 bytes, so + // recursing per chunk lets a small input exhaust the stack + FrameMetaData frameMetaData; + while (true) { + if (!readBlockHeader()) { + eof = true; + return false; + } - if (FrameAction.SKIP == frameMetaData.frameAction) { + // get action based on header + frameMetaData = getFrameMetaData(frameHeader); + if (FrameAction.SKIP != frameMetaData.frameAction) { + break; + } SnappyFramed.skip(rbc, frameMetaData.length, ByteBuffer.wrap(buffer)); - return ensureBuffer(); } if (frameMetaData.length > input.capacity()) { @@ -550,6 +554,8 @@ private boolean ensureBuffer() input.position(frameData.offset); + // uncompressedLength rejects lengths that cannot be produced from the frame size, so the buffers + // allocated below stay proportional to the frame actually read final int uncompressedLength = Snappy.uncompressedLength(input); if (uncompressedLength > uncompressedDirect.capacity()) { diff --git a/src/test/java/org/xerial/snappy/SnappyBoundsCheckTest.java b/src/test/java/org/xerial/snappy/SnappyBoundsCheckTest.java new file mode 100644 index 00000000..ea0c017b --- /dev/null +++ b/src/test/java/org/xerial/snappy/SnappyBoundsCheckTest.java @@ -0,0 +1,335 @@ +package org.xerial.snappy; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +import org.junit.Test; + +/** + * Regression tests for buffer bounds checks before calling native code, and for limits on sizes declared in + * untrusted compressed data. + */ +public class SnappyBoundsCheckTest +{ + private static byte[] randomBytes(int size) + { + byte[] data = new byte[size]; + new Random(42).nextBytes(data); + return data; + } + + private static ByteBuffer directBufferOf(byte[] data) + { + ByteBuffer buf = ByteBuffer.allocateDirect(data.length); + buf.put(data); + buf.flip(); + return buf; + } + + private static byte[] varint(long v) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + while ((v & ~0x7FL) != 0) { + out.write((int) ((v & 0x7F) | 0x80)); + v >>>= 7; + } + out.write((int) v); + return out.toByteArray(); + } + + // https://github.com/xerial/snappy-java/issues/732 + @Test(expected = IllegalArgumentException.class) + public void compressByteBufferToUndersizedOutput() + throws Exception + { + ByteBuffer src = directBufferOf(randomBytes(1024 * 1024)); + Snappy.compress(src, ByteBuffer.allocateDirect(64)); + } + + @Test + public void compressByteBufferToMaxCompressedLengthOutput() + throws Exception + { + byte[] data = randomBytes(1024 * 1024); + ByteBuffer compressed = ByteBuffer.allocateDirect(Snappy.maxCompressedLength(data.length)); + Snappy.compress(directBufferOf(data), compressed); + ByteBuffer uncompressed = ByteBuffer.allocateDirect(data.length); + assertEquals(data.length, Snappy.uncompress(compressed, uncompressed)); + byte[] result = new byte[data.length]; + uncompressed.get(result); + assertArrayEquals(data, result); + } + + @Test(expected = IllegalArgumentException.class) + public void compressByteArrayToUndersizedOutput() + throws Exception + { + byte[] data = randomBytes(1024 * 1024); + Snappy.compress(data, 0, data.length, new byte[64], 0); + } + + @Test(expected = IllegalArgumentException.class) + public void compressByteArrayWithOutOfRangeInput() + throws Exception + { + byte[] data = randomBytes(100); + Snappy.compress(data, 50, 100, new byte[Snappy.maxCompressedLength(100)], 0); + } + + @Test(expected = IllegalArgumentException.class) + public void compressByteArrayWithNegativeOutputOffset() + throws Exception + { + byte[] data = randomBytes(100); + Snappy.compress(data, 0, data.length, new byte[1024], -1); + } + + @Test(expected = IllegalArgumentException.class) + public void rawCompressWithTooLargeByteSize() + throws Exception + { + Snappy.rawCompress(new int[10], 41); + } + + // https://github.com/xerial/snappy-java/issues/728 (CVE-2026-90559) + @Test(expected = IllegalArgumentException.class) + public void uncompressByteBufferToUndersizedOutput() + throws Exception + { + byte[] data = new byte[1024 * 1024]; + ByteBuffer compressed = ByteBuffer.allocateDirect(Snappy.maxCompressedLength(data.length)); + Snappy.compress(directBufferOf(data), compressed); + Snappy.uncompress(compressed, ByteBuffer.allocateDirect(64)); + } + + @Test(expected = IllegalArgumentException.class) + public void uncompressByteArrayToUndersizedOutput() + throws Exception + { + byte[] compressed = Snappy.compress(new byte[1024 * 1024]); + Snappy.uncompress(compressed, 0, compressed.length, new byte[64], 0); + } + + @Test(expected = IllegalArgumentException.class) + public void uncompressByteArrayWithOutOfRangeOutputOffset() + throws Exception + { + byte[] compressed = Snappy.compress(new byte[100]); + Snappy.uncompress(compressed, 0, compressed.length, new byte[200], 150); + } + + @Test(expected = IllegalArgumentException.class) + public void uncompressByteArrayWithOutOfRangeInput() + throws Exception + { + byte[] compressed = Snappy.compress(new byte[100]); + Snappy.uncompress(compressed, 1, compressed.length, new byte[200], 0); + } + + @Test(expected = IllegalArgumentException.class) + public void rawUncompressToUndersizedTypedArray() + throws Exception + { + byte[] compressed = Snappy.compress(new int[100]); + Snappy.rawUncompress(compressed, 0, compressed.length, new int[99], 0); + } + + @Test + public void uncompressByteArrayAtOffset() + throws Exception + { + byte[] data = randomBytes(1000); + byte[] compressed = Snappy.compress(data); + byte[] output = new byte[1100]; + assertEquals(data.length, Snappy.uncompress(compressed, 0, compressed.length, output, 100)); + assertArrayEquals(data, Arrays.copyOfRange(output, 100, 1100)); + } + + // https://github.com/xerial/snappy-java/issues/729 + @Test + public void uncompressTypedArrayWithMisalignedLength() + throws Exception + { + // 15 bytes is not a multiple of any of the element sizes + byte[] compressed = Snappy.compress(randomBytes(15)); + try { + Snappy.uncompressLongArray(compressed); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + assertEquals(SnappyErrorCode.FAILED_TO_UNCOMPRESS, e.getErrorCode()); + } + try { + Snappy.uncompressDoubleArray(compressed); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + // expected + } + try { + Snappy.uncompressIntArray(compressed); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + // expected + } + try { + Snappy.uncompressFloatArray(compressed); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + // expected + } + try { + Snappy.uncompressShortArray(compressed); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + // expected + } + try { + Snappy.uncompressCharArray(compressed); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + // expected + } + } + + // https://github.com/xerial/snappy-java/issues/625 + @Test + public void uncompressWithHugeDeclaredLength() + throws Exception + { + byte[] data = new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07}; + try { + Snappy.uncompress(data); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + assertEquals(SnappyErrorCode.PARSING_ERROR, e.getErrorCode()); + } + } + + // https://github.com/xerial/snappy-java/issues/625 + @Test + public void uncompressWithNegativeDeclaredLength() + throws Exception + { + byte[] data = new byte[] {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x08}; + try { + Snappy.uncompress(data); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + assertEquals(SnappyErrorCode.PARSING_ERROR, e.getErrorCode()); + } + } + + @Test + public void uncompressedLengthAcceptsHighlyCompressibleData() + throws Exception + { + // all-zero input is close to the maximum compression ratio of Snappy + byte[] data = new byte[16 * 1024 * 1024]; + byte[] compressed = Snappy.compress(data); + assertEquals(data.length, Snappy.uncompressedLength(compressed)); + assertArrayEquals(data, Snappy.uncompress(compressed)); + assertEquals(0, Snappy.uncompress(Snappy.compress(new byte[0])).length); + } + + @Test(expected = IllegalArgumentException.class) + public void uncompressedLengthWithOutOfRangeInput() + throws Exception + { + Snappy.uncompressedLength(new byte[10], 5, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void isValidCompressedBufferWithOutOfRangeInput() + throws Exception + { + Snappy.isValidCompressedBuffer(new byte[10], -1, 5); + } + + private static void writeChunk(ByteArrayOutputStream out, int flag, byte[] body) + { + out.write(flag); + out.write(body.length & 0xFF); + out.write((body.length >>> 8) & 0xFF); + out.write((body.length >>> 16) & 0xFF); + out.write(body, 0, body.length); + } + + private static byte[] readAll(InputStream in) + throws IOException + { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int n; + while ((n = in.read(buf)) != -1) { + out.write(buf, 0, n); + } + return out.toByteArray(); + } + finally { + in.close(); + } + } + + // https://github.com/xerial/snappy-java/issues/731 + @Test + public void framedStreamWithManySkippableChunks() + throws Exception + { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + stream.write(SnappyFramed.HEADER_BYTES); + for (int i = 0; i < 200000; i++) { + writeChunk(stream, 0x80, new byte[0]); + } + byte[] data = "hello".getBytes("UTF-8"); + ByteArrayOutputStream body = new ByteArrayOutputStream(); + int crc = SnappyFramed.maskedCrc32c(SnappyFramed.getCRC32C(), data, 0, data.length); + for (int i = 0; i < 4; i++) { + body.write((crc >>> (8 * i)) & 0xFF); + } + body.write(data); + writeChunk(stream, 0x01, body.toByteArray()); + for (int i = 0; i < 200000; i++) { + writeChunk(stream, 0xfe, new byte[0]); + } + + byte[] result = readAll(new SnappyFramedInputStream(new ByteArrayInputStream(stream.toByteArray()))); + assertArrayEquals(data, result); + } + + // https://github.com/xerial/snappy-java/issues/730 + @Test + public void framedStreamWithHugeDeclaredUncompressedLength() + throws Exception + { + ByteArrayOutputStream body = new ByteArrayOutputStream(); + body.write(new byte[4]); // checksum, which must not be reached + body.write(varint(1L << 30)); + body.write(new byte[] {0x00, 0x61}); // a single literal + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + stream.write(SnappyFramed.HEADER_BYTES); + writeChunk(stream, 0x00, body.toByteArray()); + + try { + readAll(new SnappyFramedInputStream(new ByteArrayInputStream(stream.toByteArray()))); + fail("expected SnappyIOException"); + } + catch (SnappyIOException e) { + assertEquals(SnappyErrorCode.PARSING_ERROR, e.getErrorCode()); + } + } +}