Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions src/main/java/com/ning/compress/lzf/impl/UnsafeChunkDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,53 +89,70 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o

main_loop:
do {
if (inPos >= inEnd) {
throw new LZFException("Corrupt data: truncated block");
}
int ctrl = in[inPos++] & 255;
while (ctrl < LZFChunk.MAX_LITERAL) { // literal run(s)
final int literalLength = ctrl + 1;
if (inPos > inEnd - literalLength || outPos > outEnd - literalLength) {
throw new LZFException("Corrupt data: truncated block");
}
if (outPos > outputEnd32 || inPos > inputEnd32) {
System.arraycopy(in, inPos, out, outPos, ctrl+1);
System.arraycopy(in, inPos, out, outPos, literalLength);
} else {
copyUpTo32(in, inPos, out, outPos, ctrl);
}
++ctrl;
inPos += ctrl;
outPos += ctrl;
inPos += literalLength;
outPos += literalLength;
if (outPos >= outEnd) {
break main_loop;
}
// The literal run may end exactly at inEnd, but another byte is required for the next control token.
if (inPos >= inEnd) {
throw new LZFException("Corrupt data: truncated block");
}
ctrl = in[inPos++] & 255;
}
// back reference
int len = ctrl >> 5;
ctrl = -((ctrl & 0x1f) << 8) - 1;
// short back reference? 2 bytes; run lengths of 2 - 8 bytes
// short back reference? 2 bytes; run lengths of 3 - 8 bytes
if (len < 7) {
if (inPos >= inEnd) {
throw new LZFException("Corrupt data: truncated block");
}
ctrl -= in[inPos++] & 255;
final int copyLength = len + 2;
if (outPos > outEnd - copyLength || outPos + ctrl < outPosStart) {
throw new LZFException("Invalid back reference");
}
if (ctrl < -7 && outPos < outputEnd8) { // non-overlapping? can use efficient bulk copy
if (outPos + ctrl < outPosStart) {
throw new LZFException("Invalid back reference");
}
final long rawOffset = BYTE_ARRAY_OFFSET + outPos;
unsafe.putLong(out, rawOffset, unsafe.getLong(out, rawOffset + ctrl));
// moveLong(out, outPos, outEnd, ctrl);
outPos += len+2;
outPos += copyLength;
continue;
}
// otherwise, byte-by-byte
outPos = copyOverlappingShort(out, outPos, ctrl, len);
continue;
}
// long back reference: 3 bytes, length of up to 264 bytes
if (inPos > inEnd - 2) {
throw new LZFException("Corrupt data: truncated block");
}
len = (in[inPos++] & 255) + 9;
ctrl -= in[inPos++] & 255;
if (outPos > outEnd - len || outPos + ctrl < outPosStart) {
throw new LZFException("Invalid back reference");
}
// First: ovelapping case can't use default handling, off line.
if ((ctrl > -9) || (outPos > outputEnd32)) {
outPos = copyOverlappingLong(out, outPos, ctrl, len-9);
continue;
}
// but non-overlapping is simple
if (outPos + ctrl < outPosStart) {
throw new LZFException("Invalid back reference");
}
if (len <= 32) {
copyUpTo32(out, outPos+ctrl, outPos, len-1);
outPos += len;
Expand Down
95 changes: 27 additions & 68 deletions src/main/java/com/ning/compress/lzf/impl/VanillaChunkDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,82 +50,34 @@ public void decodeChunk(byte[] in, int inPos, byte[] out, int outPos, int outEnd
public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int outPos, int outEnd)
throws LZFException
{
do {
final int outStart = outPos;
while (outPos < outEnd) {
if (inPos >= inEnd) {
throw new LZFException("Corrupt data: truncated block");
}
int ctrl = in[inPos++] & 255;
if (ctrl < LZFChunk.MAX_LITERAL) { // literal run
switch (ctrl) {
case 31:
out[outPos++] = in[inPos++];
case 30:
out[outPos++] = in[inPos++];
case 29:
out[outPos++] = in[inPos++];
case 28:
out[outPos++] = in[inPos++];
case 27:
out[outPos++] = in[inPos++];
case 26:
out[outPos++] = in[inPos++];
case 25:
out[outPos++] = in[inPos++];
case 24:
out[outPos++] = in[inPos++];
case 23:
out[outPos++] = in[inPos++];
case 22:
out[outPos++] = in[inPos++];
case 21:
out[outPos++] = in[inPos++];
case 20:
out[outPos++] = in[inPos++];
case 19:
out[outPos++] = in[inPos++];
case 18:
out[outPos++] = in[inPos++];
case 17:
out[outPos++] = in[inPos++];
case 16:
out[outPos++] = in[inPos++];
case 15:
out[outPos++] = in[inPos++];
case 14:
out[outPos++] = in[inPos++];
case 13:
out[outPos++] = in[inPos++];
case 12:
out[outPos++] = in[inPos++];
case 11:
out[outPos++] = in[inPos++];
case 10:
out[outPos++] = in[inPos++];
case 9:
out[outPos++] = in[inPos++];
case 8:
out[outPos++] = in[inPos++];
case 7:
out[outPos++] = in[inPos++];
case 6:
out[outPos++] = in[inPos++];
case 5:
out[outPos++] = in[inPos++];
case 4:
out[outPos++] = in[inPos++];
case 3:
out[outPos++] = in[inPos++];
case 2:
out[outPos++] = in[inPos++];
case 1:
out[outPos++] = in[inPos++];
case 0:
out[outPos++] = in[inPos++];
int literalLen = ctrl + 1;
if (inPos > inEnd - literalLen || outPos > outEnd - literalLen) {
throw new LZFException("Corrupt data: truncated block");
}
System.arraycopy(in, inPos, out, outPos, literalLen);
inPos += literalLen;
outPos += literalLen;
Comment on lines 59 to +66

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ai has benchmarked this to be faster and it's more maintainable

continue;
}
// back reference
int len = ctrl >> 5;
ctrl = -((ctrl & 0x1f) << 8) - 1;
if (len < 7) { // 2 bytes; length of 3 - 8 bytes
if (inPos >= inEnd) {
throw new LZFException("Corrupt data: truncated block");
}
ctrl -= in[inPos++] & 255;
final int copyLength = len + 2;
if (outPos > outEnd - copyLength || outPos + ctrl < outStart) {
throw new LZFException("Invalid back reference");
}
out[outPos] = out[outPos++ + ctrl];
out[outPos] = out[outPos++ + ctrl];
switch (len) {
Expand All @@ -146,9 +98,16 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o
}

// long version (3 bytes, length of up to 264 bytes)
if (inPos > inEnd - 2) {
throw new LZFException("Corrupt data: truncated block");
}
len = in[inPos++] & 255;
ctrl -= in[inPos++] & 255;

final int copyLength = len + 9;
if (outPos > outEnd - copyLength || outPos + ctrl < outStart) {
throw new LZFException("Invalid back reference");
}

// First: if there is no overlap, can just use arraycopy:
if ((ctrl + len) < -9) {
len += 9;
Expand Down Expand Up @@ -192,7 +151,7 @@ public final void decodeChunk(byte[] in, int inPos, int inEnd, byte[] out, int o
case 1:
out[outPos] = out[outPos++ + ctrl];
}
} while (outPos < outEnd);
}

// sanity check to guard against corrupt data:
if (inPos != inEnd) {
Expand Down
21 changes: 21 additions & 0 deletions src/test/java/com/ning/compress/lzf/TestLZFDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ public void testUnsafeValidation() {
assertThrows(ArrayIndexOutOfBoundsException.class, () -> decoder.decodeChunk(array, goodStart, goodEnd, array, goodStart, array.length + 1));
}

@Test
public void testMalformedShortBackReference() {
byte[] inputWithTrailingByte = new byte[] {
LZFChunk.BYTE_Z, LZFChunk.BYTE_V, LZFChunk.BLOCK_TYPE_COMPRESSED,
0, 3, 0, (byte) 0x8f, 0, 4, 0x50, 0x53
};
byte[] truncatedInput = new byte[] {
LZFChunk.BYTE_Z, LZFChunk.BYTE_V, LZFChunk.BLOCK_TYPE_COMPRESSED,
0, 3, 0, (byte) 0x8f, 0, 4, 0x50
};

assertMalformedShortBackReference(inputWithTrailingByte, ChunkDecoderFactory.safeInstance());
assertMalformedShortBackReference(inputWithTrailingByte, ChunkDecoderFactory.optimalInstance());
assertMalformedShortBackReference(truncatedInput, ChunkDecoderFactory.safeInstance());
assertMalformedShortBackReference(truncatedInput, ChunkDecoderFactory.optimalInstance());
}

private void assertMalformedShortBackReference(byte[] input, ChunkDecoder decoder) {
assertThrows(LZFException.class, () -> decoder.decodeChunk(input, 7, 10, new byte[143], 0, 143));
}

/*
///////////////////////////////////////////////////////////////////////
// Second-level test methods
Expand Down