Skip to content

Commit

Permalink
Support using LZ4SafeDecompressor in LZ4DecompressorWithLength
Browse files Browse the repository at this point in the history
  • Loading branch information
odaira committed Jun 4, 2021
1 parent 5b4ca25 commit e4780ab
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 44 deletions.
147 changes: 129 additions & 18 deletions src/java/net/jpountz/lz4/LZ4DecompressorWithLength.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@

public class LZ4DecompressorWithLength {

private final LZ4FastDecompressor decompressor;
private final LZ4FastDecompressor fastDecompressor;
private final LZ4SafeDecompressor safeDecompressor;

/**
* Returns the decompressed length of compressed data in <code>src</code>.
Expand Down Expand Up @@ -75,11 +76,24 @@ public static int getDecompressedLength(ByteBuffer src, int srcOff) {

/**
* Creates a new decompressor to decompress data compressed by {@link LZ4CompressorWithLength}.
* Note that it is deprecated to use a JNI-binding instance of {@link LZ4FastDecompressor}.
* Please see {@link LZ4Factory#nativeInstance()} for details.
*
* @param decompressor decompressor to use
* @param fastDecompressor fast decompressor to use
*/
public LZ4DecompressorWithLength(LZ4FastDecompressor decompressor) {
this.decompressor = decompressor;
public LZ4DecompressorWithLength(LZ4FastDecompressor fastDecompressor) {
this.fastDecompressor = fastDecompressor;
this.safeDecompressor = null;
}

/**
* Creates a new decompressor to decompress data compressed by {@link LZ4CompressorWithLength}.
*
* @param safeDecompressor safe decompressor to use
*/
public LZ4DecompressorWithLength(LZ4SafeDecompressor safeDecompressor) {
this.fastDecompressor = null;
this.safeDecompressor = safeDecompressor;
}

/**
Expand All @@ -95,18 +109,48 @@ public int decompress(byte[] src, byte[] dest) {
}

/**
* Decompresses <code>src[srcOff:]</code> into <code>dest[destOff:]</code>
* and returns the number of bytes read from <code>src</code>.
* When {@link LZ4FastDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:]</code> into <code>dest[destOff:]</code>
* and returns the number of bytes read from <code>src</code>, and
* when {@link LZ4SafeDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:src.length]</code> into <code>dest[destOff:]</code>
* and returns the number of decompressed bytes written into <code>dest</code>.
*
* @param src the compressed data
* @param srcOff the start offset in src
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
*/
public int decompress(byte[] src, int srcOff, byte[] dest, int destOff) {
if (safeDecompressor != null) {
return decompress(src, srcOff, src.length - srcOff, dest, destOff);
}
final int destLen = getDecompressedLength(src, srcOff);
return decompressor.decompress(src, srcOff + 4, dest, destOff, destLen) + 4;
return fastDecompressor.decompress(src, srcOff + 4, dest, destOff, destLen) + 4;
}

/**
* When {@link LZ4FastDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:]</code> into <code>dest[destOff:]</code>
* and returns the number of bytes read from <code>src</code>, and
* when {@link LZ4SafeDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:srcOff+srcLen]</code> into <code>dest[destOff:]</code>
* and returns the number of decompressed bytes written into <code>dest</code>.
*
* @param src the compressed data
* @param srcOff the start offset in src
* @param srcLen the exact size of the compressed data (ignored when {@link LZ4FastDecompressor} is used)
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
*/
public int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff) {
if (safeDecompressor == null) {
return decompress(src, srcOff, dest, destOff);
}
final int destLen = getDecompressedLength(src, srcOff);
return safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, destOff, destLen);
}

/**
Expand All @@ -122,7 +166,9 @@ public byte[] decompress(byte[] src) {

/**
* Convenience method which returns <code>src[srcOff:]</code>
* decompressed.
* decompressed when {@link LZ4FastDecompressor} was specified to the constructor,
* or <code>src[srcOff:src.length]</code> decompressed when
* {@link LZ4SafeDecompressor} was specified to the constructor.
* <p><b><span style="color:red">Warning</span></b>: this method has an
* important overhead due to the fact that it needs to allocate a buffer to
* decompress into.
Expand All @@ -132,35 +178,100 @@ public byte[] decompress(byte[] src) {
* @return the decompressed data
*/
public byte[] decompress(byte[] src, int srcOff) {
if (safeDecompressor != null) {
return decompress(src, srcOff, src.length - srcOff);
}
final int destLen = getDecompressedLength(src, srcOff);
return fastDecompressor.decompress(src, srcOff + 4, destLen);
}

/**
* Convenience method which returns <code>src[srcOff:]</code>
* decompressed when {@link LZ4FastDecompressor} was specified to the constructor,
* or <code>src[srcOff:srcOff+srcLen]</code> decompressed when
* {@link LZ4SafeDecompressor} was specified to the constructor.
* <p><b><span style="color:red">Warning</span></b>: this method has an
* important overhead due to the fact that it needs to allocate a buffer to
* decompress into.
*
* @param src the compressed data
* @param srcOff the start offset in src
* @param srcLen the exact size of the compressed data (ignored when {@link LZ4FastDecompressor} is used)
* @return the decompressed data
*/
public byte[] decompress(byte[] src, int srcOff, int srcLen) {
if (safeDecompressor == null) {
return decompress(src, srcOff);
}
final int destLen = getDecompressedLength(src, srcOff);
return decompressor.decompress(src, srcOff + 4, destLen);
return safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, destLen);
}

/**
* Decompresses <code>src</code> into <code>dest</code>. This method moves the positions of the buffers.
* Decompresses <code>src</code> into <code>dest</code>.
* When {@link LZ4SafeDecompressor} was specified to the constructor,
* <code>src</code>'s {@link ByteBuffer#remaining()} must be exactly the size
* of the compressed data. This method moves the positions of the buffers.
*
* @param src the compressed data
* @param dest the destination buffer to store the decompressed data
*/
public void decompress(ByteBuffer src, ByteBuffer dest) {
final int destLen = getDecompressedLength(src, src.position());
final int read = decompressor.decompress(src, src.position() + 4, dest, dest.position(), destLen);
src.position(src.position() + 4 + read);
dest.position(dest.position() + destLen);
if (safeDecompressor == null) {
final int read = fastDecompressor.decompress(src, src.position() + 4, dest, dest.position(), destLen);
src.position(src.position() + 4 + read);
dest.position(dest.position() + destLen);
} else {
final int written = safeDecompressor.decompress(src, src.position() + 4, src.remaining() - 4, dest, dest.position(), destLen);
src.position(src.limit());
dest.position(dest.position() + written);
}
}

/** Decompresses <code>src[srcOff:]</code> into <code>dest[destOff:]</code>
* and returns the number of bytes read from <code>src</code>.
/** When {@link LZ4FastDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:]</code> into <code>dest[destOff:]</code>
* and returns the number of bytes read from <code>src</code>, and
* when {@link LZ4SafeDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:src.remaining()]</code> into <code>dest[destOff:]</code>
* and returns the number of decompressed bytes written into <code>dest</code>.
* The positions and limits of the {@link ByteBuffer}s remain unchanged.
*
* @param src the compressed data
* @param srcOff the start offset in src
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
*/
public int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff) {
if (safeDecompressor != null) {
return decompress(src, srcOff, src.remaining() - srcOff, dest, destOff);
}
final int destLen = getDecompressedLength(src, srcOff);
return fastDecompressor.decompress(src, srcOff + 4, dest, destOff, destLen) + 4;
}

/**
* When {@link LZ4FastDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:]</code> into <code>dest[destOff:]</code>
* and returns the number of bytes read from <code>src</code>, and
* when {@link LZ4SafeDecompressor} was specified to the constructor,
* decompresses <code>src[srcOff:srcOff+srcLen]</code> into <code>dest[destOff:]</code>
* and returns the number of decompressed bytes written into <code>dest</code>.
* The positions and limits of the {@link ByteBuffer}s remain unchanged.
*
* @param src the compressed data
* @param srcOff the start offset in src
* @param srcLen the exact size of the compressed data (ignored when {@link LZ4FastDecompressor} is used)
* @param dest the destination buffer to store the decompressed data
* @param destOff the start offset in dest
* @return the number of bytes read to restore the original input (when {@link LZ4FastDecompressor} is used), or the number of decompressed bytes (when {@link LZ4SafeDecompressor} is used)
*/
public int decompress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff) {
if (safeDecompressor == null) {
return decompress(src, srcOff, dest, destOff);
}
final int destLen = getDecompressedLength(src, srcOff);
return decompressor.decompress(src, srcOff + 4, dest, destOff, destLen) + 4;
return safeDecompressor.decompress(src, srcOff + 4, srcLen - 4, dest, destOff, destLen);
}
}
4 changes: 2 additions & 2 deletions src/test/net/jpountz/lz4/AbstractLZ4Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public int decompress(LZ4FastDecompressor decompressor,
@Override
public int decompress(LZ4SafeDecompressor decompressor,
byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) {
return -1;
return new LZ4DecompressorWithLength(decompressor).decompress(src, srcOff, srcLen, dest, destOff);
}
};

Expand Down Expand Up @@ -190,7 +190,7 @@ public int decompress(LZ4FastDecompressor decompressor, ByteBuffer src,
@Override
public int decompress(LZ4SafeDecompressor decompressor, ByteBuffer src,
int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) {
return -1;
return new LZ4DecompressorWithLength(decompressor).decompress(src, srcOff, srcLen, dest, destOff);
}
};
}
Expand Down
51 changes: 27 additions & 24 deletions src/test/net/jpountz/lz4/LZ4Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ public <T> void testRoundTrip(
}

if (tester != Tester.BYTE_ARRAY_WITH_LENGTH && tester != Tester.BYTE_BUFFER_WITH_LENGTH) {
// LZ4DecompressorWithLength will succeed in decompression
// because it ignores destLen.

if (len > 0) {
// decompression dest is too small
try {
Expand All @@ -216,34 +219,34 @@ public <T> void testRoundTrip(
} catch (LZ4Exception e) {
// OK
}
}

// try decompression when only the size of the compressed buffer is known
if (len > 0) {
tester.fill(restored, randomByte());
assertEquals(len, tester.decompress(decompressor2, compressed, 0, compressedLen, restored, 0, len));
assertArrayEquals(Arrays.copyOfRange(data, off, off + len), tester.copyOf(restored, 0, len));
tester.fill(restored, randomByte());
} else {
assertEquals(0, tester.decompress(decompressor2, compressed, 0, compressedLen, tester.allocate(1), 0, 1));
}
// try decompression when only the size of the compressed buffer is known
if (len > 0) {
tester.fill(restored, randomByte());
assertEquals(len, tester.decompress(decompressor2, compressed, 0, compressedLen, restored, 0, len));
assertArrayEquals(Arrays.copyOfRange(data, off, off + len), tester.copyOf(restored, 0, len));
tester.fill(restored, randomByte());
} else {
assertEquals(0, tester.decompress(decompressor2, compressed, 0, compressedLen, tester.allocate(1), 0, 1));
}

// over-estimated compressed length
try {
final int decompressedLen = tester.decompress(decompressor2, compressed, 0, compressedLen + 1, tester.allocate(len + 100), 0, len + 100);
fail("decompressedLen=" + decompressedLen);
} catch (LZ4Exception e) {
// OK
}
// over-estimated compressed length
try {
final int decompressedLen = tester.decompress(decompressor2, compressed, 0, compressedLen + 1, tester.allocate(len + 100), 0, len + 100);
fail("decompressedLen=" + decompressedLen);
} catch (LZ4Exception e) {
// OK
}

// under-estimated compressed length
try {
final int decompressedLen = tester.decompress(decompressor2, compressed, 0, compressedLen - 1, tester.allocate(len + 100), 0, len + 100);
if (!(decompressor2 instanceof LZ4JNISafeDecompressor)) {
fail("decompressedLen=" + decompressedLen);
}
} catch (LZ4Exception e) {
// OK
// under-estimated compressed length
try {
final int decompressedLen = tester.decompress(decompressor2, compressed, 0, compressedLen - 1, tester.allocate(len + 100), 0, len + 100);
if (!(decompressor2 instanceof LZ4JNISafeDecompressor)) {
fail("decompressedLen=" + decompressedLen);
}
} catch (LZ4Exception e) {
// OK
}
}

Expand Down

0 comments on commit e4780ab

Please sign in to comment.