From f0d7f6288162f33b8d86af64c5f451bf5a4e9f48 Mon Sep 17 00:00:00 2001 From: "Taro L. Saito" Date: Thu, 23 Oct 2014 12:42:12 +0900 Subject: [PATCH] #89: Add SnappyIOException to distinguish empty input, incompatible format version, etc. --- .../org/xerial/snappy/SnappyErrorCode.java | 6 ++++- .../org/xerial/snappy/SnappyIOException.java | 23 ++++++++++++++++ .../org/xerial/snappy/SnappyInputStream.java | 27 +++++++++---------- .../xerial/snappy/SnappyInputStreamTest.java | 6 ++--- 4 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/xerial/snappy/SnappyIOException.java diff --git a/src/main/java/org/xerial/snappy/SnappyErrorCode.java b/src/main/java/org/xerial/snappy/SnappyErrorCode.java index e3e520a..f1d8b6b 100755 --- a/src/main/java/org/xerial/snappy/SnappyErrorCode.java +++ b/src/main/java/org/xerial/snappy/SnappyErrorCode.java @@ -38,7 +38,11 @@ public enum SnappyErrorCode { PARSING_ERROR(2), NOT_A_DIRECT_BUFFER(3), OUT_OF_MEMORY(4), - FAILED_TO_UNCOMPRESS(5); + FAILED_TO_UNCOMPRESS(5), + EMPTY_INPUT(6), + INCOMPATIBLE_VERSION(7), + INVALID_CHUNK_SIZE(8) + ; public final int id; diff --git a/src/main/java/org/xerial/snappy/SnappyIOException.java b/src/main/java/org/xerial/snappy/SnappyIOException.java new file mode 100644 index 0000000..ee4c60c --- /dev/null +++ b/src/main/java/org/xerial/snappy/SnappyIOException.java @@ -0,0 +1,23 @@ +package org.xerial.snappy; + +import java.io.IOException; + +/** + * Enhanced IOException with SnappyErrorCode + */ +public class SnappyIOException extends IOException { + private final SnappyErrorCode errorCode; + + public SnappyIOException(SnappyErrorCode errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + @Override + public String getMessage() { + return String.format("[%s] %s", errorCode.name(), super.getMessage()); + } + + public SnappyErrorCode getErrorCode() { return errorCode; } + +} diff --git a/src/main/java/org/xerial/snappy/SnappyInputStream.java b/src/main/java/org/xerial/snappy/SnappyInputStream.java index 0d39c67..a56dff1 100755 --- a/src/main/java/org/xerial/snappy/SnappyInputStream.java +++ b/src/main/java/org/xerial/snappy/SnappyInputStream.java @@ -83,6 +83,10 @@ public class SnappyInputStream extends InputStream } // Quick test of the header + if(readBytes == 0) { + // Snappy produces at least 1-byte result. So the empty input is not a valid input + throw new SnappyIOException(SnappyErrorCode.EMPTY_INPUT, "Cannot decompress empty stream"); + } if (readBytes < header.length || header[0] != SnappyCodec.MAGIC_HEADER[0]) { // do the default uncompression readFully(header, readBytes); @@ -93,8 +97,8 @@ public class SnappyInputStream extends InputStream if (codec.isValidMagicHeader()) { // The input data is compressed by SnappyOutputStream if (codec.version < SnappyCodec.MINIMUM_COMPATIBLE_VERSION) { - throw new IOException(String.format( - "compressed with imcompatible codec version %d. At least version %d is required", + throw new SnappyIOException(SnappyErrorCode.INCOMPATIBLE_VERSION, String.format( + "Compressed with an incompatible codec version %d. At least version %d is required", codec.version, SnappyCodec.MINIMUM_COMPATIBLE_VERSION)); } } @@ -351,20 +355,15 @@ public class SnappyInputStream extends InputStream if (readBytes < chunkSize) { throw new IOException("failed to read chunk"); } - try { - int uncompressedLength = Snappy.uncompressedLength(compressed, 0, chunkSize); - if (uncompressed == null || uncompressedLength > uncompressed.length) { - uncompressed = new byte[uncompressedLength]; - } - int actualUncompressedLength = Snappy.uncompress(compressed, 0, chunkSize, uncompressed, 0); - if (uncompressedLength != actualUncompressedLength) { - throw new IOException("invalid uncompressed byte size"); - } - uncompressedLimit = actualUncompressedLength; + int uncompressedLength = Snappy.uncompressedLength(compressed, 0, chunkSize); + if (uncompressed == null || uncompressedLength > uncompressed.length) { + uncompressed = new byte[uncompressedLength]; } - catch (IOException e) { - throw new IOException("failed to uncompress the chunk: " + e.getMessage()); + int actualUncompressedLength = Snappy.uncompress(compressed, 0, chunkSize, uncompressed, 0); + if (uncompressedLength != actualUncompressedLength) { + throw new SnappyIOException(SnappyErrorCode.INVALID_CHUNK_SIZE, String.format("expected %,d bytes, but decompressed chunk has %,d bytes", uncompressedLength, actualUncompressedLength)); } + uncompressedLimit = actualUncompressedLength; return true; } diff --git a/src/test/java/org/xerial/snappy/SnappyInputStreamTest.java b/src/test/java/org/xerial/snappy/SnappyInputStreamTest.java index 011eb31..7e66c58 100755 --- a/src/test/java/org/xerial/snappy/SnappyInputStreamTest.java +++ b/src/test/java/org/xerial/snappy/SnappyInputStreamTest.java @@ -135,11 +135,11 @@ public class SnappyInputStreamTest SnappyInputStream in = new SnappyInputStream(new ByteArrayInputStream(new byte[0])); byte[] uncompressed = readFully(in); assertEquals(0, uncompressed.length); - } - catch(Exception e) { fail("should not reach here"); } - + catch(SnappyIOException e) { + assertEquals(SnappyErrorCode.EMPTY_INPUT, e.getErrorCode()); + } } }