From bf614d3c51c9774df0f64285db314545f05bb5ef Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Fri, 7 Sep 2018 15:44:19 -0700 Subject: first sketch for a byte-accurate partial decoder --- lib/lz4.c | 126 ++++++++++++++++++++++++++++++++++-------------------- lib/lz4.h | 4 +- tests/fullbench.c | 32 +++++++++----- tests/fuzzer.c | 22 +++++----- 4 files changed, 113 insertions(+), 71 deletions(-) diff --git a/lib/lz4.c b/lib/lz4.c index 35df7f5..6febb90 100644 --- a/lib/lz4.c +++ b/lib/lz4.c @@ -483,9 +483,6 @@ typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; -typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; -typedef enum { full = 0, partial = 1 } earlyEnd_directive; - /*-************************************ * Local Utils @@ -684,9 +681,9 @@ LZ4_FORCE_INLINE int LZ4_compress_generic( /* the dictCtx currentOffset is indexed on the start of the dictionary, * while a dictionary in the current context precedes the currentOffset */ - const BYTE* dictBase = dictDirective == usingDictCtx ? - dictionary + dictSize - dictCtx->currentOffset : - dictionary + dictSize - startIndex; + const BYTE* dictBase = (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; BYTE* op = (BYTE*) dest; BYTE* const olimit = op + maxOutputSize; @@ -1385,25 +1382,32 @@ int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) -/*-***************************** -* Decompression functions -*******************************/ +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + /*! LZ4_decompress_generic() : * This generic decompression function covers all use cases. * It shall be instantiated several times, using different sets of directives. * Note that it is important for performance that this function really get inlined, * in order to remove useless branches during compilation optimization. */ -LZ4_FORCE_INLINE int +LZ4_FORCE_INLINE +int LZ4_decompress_generic( const char* const src, char* const dst, int srcSize, int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ - int endOnInput, /* endOnOutputSize, endOnInputSize */ - int partialDecoding, /* full, partial */ - int targetOutputSize, /* only used if partialDecoding==partial */ + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ int dict, /* noDict, withPrefix64k, usingExtDict */ const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ const BYTE* const dictStart, /* only if dict==usingExtDict */ @@ -1416,7 +1420,6 @@ LZ4_decompress_generic( BYTE* op = (BYTE*) dst; BYTE* const oend = op + outputSize; BYTE* cpy; - BYTE* oexit = op + targetOutputSize; const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; const unsigned inc32table[8] = {0, 1, 2, 1, 0, 4, 4, 4}; @@ -1432,9 +1435,9 @@ LZ4_decompress_generic( DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i)", srcSize); /* Special cases */ - if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => just decode everything */ + assert(src != NULL); if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ - if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0 ? 1 : -1); if ((endOnInput) && unlikely(srcSize==0)) return -1; /* Main Loop : decode sequences */ @@ -1443,7 +1446,7 @@ LZ4_decompress_generic( size_t offset; unsigned const token = *ip++; - size_t length = token >> ML_BITS; /* literal length */ + size_t length = token >> ML_BITS; /* literal length */ assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ @@ -1468,6 +1471,7 @@ LZ4_decompress_generic( length = token & ML_MASK; /* match length */ offset = LZ4_readLE16(ip); ip += 2; match = op - offset; + assert(match <= op); /* check overflow */ /* Do not deal with overlapping matches. */ if ( (length != ML_MASK) @@ -1501,11 +1505,11 @@ LZ4_decompress_generic( /* copy literals */ cpy = op+length; - if ( ((endOnInput) && ((cpy>(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) - || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) { if (partialDecoding) { - if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ + if (cpy > oend) { cpy = oend; length = oend-op; } /* Partial decoding : stop in the middle of literal segment */ if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ } else { if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ @@ -1514,10 +1518,15 @@ LZ4_decompress_generic( memcpy(op, ip, length); ip += length; op += length; - break; /* Necessarily EOF, due to parsing restrictions */ + if (!partialDecoding || (cpy == oend)) { + /* Necessarily EOF, due to parsing restrictions */ + break; + } + + } else { + LZ4_wildCopy(op, ip, cpy); + ip += length; op = cpy; } - LZ4_wildCopy(op, ip, cpy); - ip += length; op = cpy; /* get offset */ offset = LZ4_readLE16(ip); ip+=2; @@ -1541,21 +1550,24 @@ _copy_match: } length += MINMATCH; - /* check external dictionary */ + /* match starting within external dictionary */ if ((dict==usingExtDict) && (match < lowPrefix)) { - if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } if (length <= (size_t)(lowPrefix-match)) { - /* match can be copied as a single segment from external dictionary */ + /* match fits entirely within external dictionary : just copy */ memmove(op, dictEnd - (lowPrefix-match), length); op += length; } else { - /* match encompass external dictionary and current block */ - size_t const copySize = (size_t)(lowPrefix-match); + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); size_t const restSize = length - copySize; memcpy(op, dictEnd - copySize, copySize); op += copySize; - if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ BYTE* const endOfMatch = op + restSize; const BYTE* copyFrom = lowPrefix; while (op < endOfMatch) *op++ = *copyFrom++; @@ -1568,6 +1580,22 @@ _copy_match: /* copy match within block */ cpy = op + length; + + /* specific : partial decode : does not respect end parsing restrictions */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-12)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) *op++ = *match++; + } else { + memcpy(op, match, mlen); + } + op = copyEnd; + continue; + } + if (unlikely(offset<8)) { op[0] = match[0]; op[1] = match[1]; @@ -1576,23 +1604,26 @@ _copy_match: match += inc32table[offset]; memcpy(op+4, match, 4); match -= dec64table[offset]; - } else { memcpy(op, match, 8); match+=8; } + } else { + memcpy(op, match, 8); + match += 8; + } op += 8; - if (unlikely(cpy>oend-12)) { - BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); + if (unlikely(cpy > oend-12)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ if (op < oCopyLimit) { LZ4_wildCopy(op, match, oCopyLimit); match += oCopyLimit - op; op = oCopyLimit; } - while (op16) LZ4_wildCopy(op+8, match+8, cpy); + if (length > 16) LZ4_wildCopy(op+8, match+8, cpy); } - op = cpy; /* correction */ + op = cpy; /* wildcopy correction */ } /* end of decoding */ @@ -1613,23 +1644,24 @@ LZ4_FORCE_O2_GCC_PPC64LE int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) { return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, - endOnInputSize, full, 0, noDict, + endOnInputSize, decode_full_block, noDict, (BYTE*)dest, NULL, 0); } LZ4_FORCE_O2_GCC_PPC64LE -int LZ4_decompress_safe_partial(const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize) +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) { - return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, - endOnInputSize, partial, targetOutputSize, - noDict, (BYTE*)dest, NULL, 0); + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + endOnInputSize, partial_decode, + noDict, (BYTE*)dst, NULL, 0); } LZ4_FORCE_O2_GCC_PPC64LE int LZ4_decompress_fast(const char* source, char* dest, int originalSize) { return LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, withPrefix64k, + endOnOutputSize, decode_full_block, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 0); } @@ -1639,7 +1671,7 @@ LZ4_FORCE_O2_GCC_PPC64LE /* Exported, an obsolete API function. */ int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) { return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, withPrefix64k, + endOnInputSize, decode_full_block, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 0); } @@ -1656,7 +1688,7 @@ static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, i size_t prefixSize) { return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, noDict, + endOnInputSize, decode_full_block, noDict, (BYTE*)dest-prefixSize, NULL, 0); } @@ -1666,7 +1698,7 @@ int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, const void* dictStart, size_t dictSize) { return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, usingExtDict, + endOnInputSize, decode_full_block, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); } @@ -1675,7 +1707,7 @@ static int LZ4_decompress_fast_extDict(const char* source, char* dest, int origi const void* dictStart, size_t dictSize) { return LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, usingExtDict, + endOnOutputSize, decode_full_block, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); } @@ -1688,7 +1720,7 @@ int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compresse size_t prefixSize, const void* dictStart, size_t dictSize) { return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, - endOnInputSize, full, 0, usingExtDict, + endOnInputSize, decode_full_block, usingExtDict, (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); } @@ -1697,7 +1729,7 @@ int LZ4_decompress_fast_doubleDict(const char* source, char* dest, int originalS size_t prefixSize, const void* dictStart, size_t dictSize) { return LZ4_decompress_generic(source, dest, 0, originalSize, - endOnOutputSize, full, 0, usingExtDict, + endOnOutputSize, decode_full_block, usingExtDict, (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); } diff --git a/lib/lz4.h b/lib/lz4.h index 9d3890a..ce4d033 100644 --- a/lib/lz4.h +++ b/lib/lz4.h @@ -272,12 +272,12 @@ LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, in * @return : size of compressed block * or 0 if there is an error (typically, cannot fit into 'dst'). * - * Note 1 : Each invocation to LZ4_compress_fast_continue() will generate a new block. + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. * Each block has precise boundaries. * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. * Each block must be decompressed separately, calling LZ4_decompress_*() with associated metadata. * - * Note 2 : The previous 64KB of source data is assumed to remain present, unmodified, at same address in memory! + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory! * * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. * Make sure that buffers are separated, by at least one byte. diff --git a/tests/fullbench.c b/tests/fullbench.c index 2818ea2..fd1202d 100644 --- a/tests/fullbench.c +++ b/tests/fullbench.c @@ -317,7 +317,9 @@ static int local_LZ4_decompress_safe_forceExtDict(const char* in, char* out, int static int local_LZ4_decompress_safe_partial(const char* in, char* out, int inSize, int outSize) { - return LZ4_decompress_safe_partial(in, out, inSize, outSize - 5, outSize); + int result = LZ4_decompress_safe_partial(in, out, inSize, outSize - 5, outSize); + if (result < 0) return result; + return outSize; } @@ -462,9 +464,9 @@ int fullSpeedBench(const char** fileNamesTable, int nbFiles) case 12: compressionFunction = local_LZ4_compress_HC_extStateHC; compressorName = "LZ4_compress_HC_extStateHC"; break; case 14: compressionFunction = local_LZ4_compress_HC_continue; initFunction = local_LZ4_resetStreamHC; compressorName = "LZ4_compress_HC_continue"; break; #ifndef LZ4_DLL_IMPORT - case 20: compressionFunction = local_LZ4_compress_forceDict; initFunction = local_LZ4_resetDictT; compressorName = "LZ4_compress_forceDict"; break; + case 20: compressionFunction = local_LZ4_compress_forceDict; initFunction = local_LZ4_resetDictT; compressorName = "LZ4_compress_forceDict"; break; #endif - case 30: compressionFunction = local_LZ4F_compressFrame; compressorName = "LZ4F_compressFrame"; + case 30: compressionFunction = local_LZ4F_compressFrame; compressorName = "LZ4F_compressFrame"; chunkP[0].origSize = (int)benchedSize; nbChunks=1; break; case 40: compressionFunction = local_LZ4_saveDict; compressorName = "LZ4_saveDict"; @@ -542,6 +544,7 @@ int fullSpeedBench(const char** fileNamesTable, int nbFiles) const char* dName; int (*decompressionFunction)(const char*, char*, int, int); double bestTime = 100000000.; + int checkResult = 1; if ((g_decompressionAlgo != ALL_DECOMPRESSORS) && (g_decompressionAlgo != dAlgNb)) continue; @@ -553,11 +556,11 @@ int fullSpeedBench(const char** fileNamesTable, int nbFiles) case 3: decompressionFunction = local_LZ4_decompress_fast_usingExtDict; dName = "LZ4_decompress_fast_using(Ext)Dict"; break; case 4: decompressionFunction = LZ4_decompress_safe; dName = "LZ4_decompress_safe"; break; case 6: decompressionFunction = local_LZ4_decompress_safe_usingDict; dName = "LZ4_decompress_safe_usingDict"; break; - case 7: decompressionFunction = local_LZ4_decompress_safe_partial; dName = "LZ4_decompress_safe_partial"; break; + case 7: decompressionFunction = local_LZ4_decompress_safe_partial; dName = "LZ4_decompress_safe_partial"; checkResult = 0; break; #ifndef LZ4_DLL_IMPORT - case 8: decompressionFunction = local_LZ4_decompress_safe_forceExtDict; dName = "LZ4_decompress_safe_forceExtDict"; break; + case 8: decompressionFunction = local_LZ4_decompress_safe_forceExtDict; dName = "LZ4_decompress_safe_forceExtDict"; break; #endif - case 9: decompressionFunction = local_LZ4F_decompress; dName = "LZ4F_decompress"; + case 9: decompressionFunction = local_LZ4F_decompress; dName = "LZ4F_decompress"; errorCode = LZ4F_compressFrame(compressed_buff, compressedBuffSize, orig_buff, benchedSize, NULL); if (LZ4F_isError(errorCode)) { DISPLAY("Error while preparing compressed frame\n"); @@ -589,9 +592,13 @@ int fullSpeedBench(const char** fileNamesTable, int nbFiles) clockTime = clock(); while(BMK_GetClockSpan(clockTime) < TIMELOOP) { for (chunkNb=0; chunkNb %7.1f MB/s\n", dAlgNb, dName, (int)benchedSize, (double)benchedSize / bestTime / 1000000); } diff --git a/tests/fuzzer.c b/tests/fuzzer.c index 6c79515..bdb7841 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -504,7 +504,7 @@ static int FUZ_test(U32 seed, U32 nbCycles, const U32 startCycle, const double c /* Test decoding with empty input */ FUZ_DISPLAYTEST("LZ4_decompress_safe() with empty input"); - LZ4_decompress_safe(NULL, decodedBuffer, 0, blockSize); + LZ4_decompress_safe(compressedBuffer, decodedBuffer, 0, blockSize); /* Test decoding with a one byte input */ FUZ_DISPLAYTEST("LZ4_decompress_safe() with one byte input"); @@ -545,7 +545,6 @@ static int FUZ_test(U32 seed, U32 nbCycles, const U32 startCycle, const double c ret = LZ4_decompress_safe(compressedBuffer, decodedBuffer, compressedSize, blockSize+1); FUZ_CHECKTEST(ret<0, "LZ4_decompress_safe failed despite amply sufficient space"); FUZ_CHECKTEST(ret!=blockSize, "LZ4_decompress_safe did not regenerate original data"); - //FUZ_CHECKTEST(decodedBuffer[blockSize], "LZ4_decompress_safe wrote more than (unknown) target size"); // well, is that an issue ? FUZ_CHECKTEST(decodedBuffer[blockSize+1], "LZ4_decompress_safe overrun specified output buffer size"); { U32 const crcCheck = XXH32(decodedBuffer, blockSize, 0); FUZ_CHECKTEST(crcCheck!=crcOrig, "LZ4_decompress_safe corrupted decoded data"); @@ -579,15 +578,16 @@ static int FUZ_test(U32 seed, U32 nbCycles, const U32 startCycle, const double c FUZ_CHECKTEST(ret>=0, "LZ4_decompress_safe should have failed, due to input size being too large"); FUZ_CHECKTEST(decodedBuffer[blockSize], "LZ4_decompress_safe overrun specified output buffer size"); - // Test partial decoding with target output size being max/2 => must work - FUZ_DISPLAYTEST(); - ret = LZ4_decompress_safe_partial(compressedBuffer, decodedBuffer, compressedSize, blockSize/2, blockSize); - FUZ_CHECKTEST(ret<0, "LZ4_decompress_safe_partial failed despite sufficient space"); - - // Test partial decoding with target output size being just below max => must work - FUZ_DISPLAYTEST(); - ret = LZ4_decompress_safe_partial(compressedBuffer, decodedBuffer, compressedSize, blockSize-3, blockSize); - FUZ_CHECKTEST(ret<0, "LZ4_decompress_safe_partial failed despite sufficient space"); + /* Test partial decoding => must work */ + FUZ_DISPLAYTEST("test LZ4_decompress_safe_partial"); + { size_t const missingBytes = FUZ_rand(&randState) % blockSize; + int const targetSize = (int)(blockSize - missingBytes); + char const sentinel = compressedBuffer[targetSize] = block[targetSize] ^ 0x5A; + int const decResult = LZ4_decompress_safe_partial(compressedBuffer, decodedBuffer, compressedSize, targetSize, blockSize); + FUZ_CHECKTEST(decResult<0, "LZ4_decompress_safe_partial failed despite valid input data"); + FUZ_CHECKTEST(decResult != targetSize, "LZ4_decompress_safe_partial did not regenerated required amount of data (%i < %i <= %i)", decResult, targetSize, blockSize); + FUZ_CHECKTEST(compressedBuffer[targetSize] != sentinel, "LZ4_decompress_safe_partial overwrite beyond requested size (though %i <= %i <= %i)", decResult, targetSize, blockSize); + } /* Test Compression with limited output size */ -- cgit v0.12 From eaed9ea4a15934e59f74902f0ed692a07151579d Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Fri, 7 Sep 2018 16:21:31 -0700 Subject: updated function interface documentation --- doc/lz4_manual.html | 105 +++++++++++++++++++++++++++++++++------------------- lib/lz4.h | 24 +++++++++--- 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/doc/lz4_manual.html b/doc/lz4_manual.html index c7c5763..6ebf8d2 100644 --- a/doc/lz4_manual.html +++ b/doc/lz4_manual.html @@ -110,45 +110,67 @@ int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int src

Same compression function, just using an externally allocated memory space to store compression state. Use LZ4_sizeofState() to know how much memory must be allocated, and allocate it on 8-bytes boundaries (using malloc() typically). - Then, provide it as 'void* state' to compression function. + Then, provide this buffer as 'void* state' to compression function.


int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize);
-

Reverse the logic : compresses as much data as possible from 'src' buffer - into already allocated buffer 'dst' of size 'targetDestSize'. - This function either compresses the entire 'src' content into 'dst' if it's large enough, - or fill 'dst' buffer completely with as much data as possible from 'src'. - *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. - New value is necessarily <= old value. - return : Nb bytes written into 'dst' (necessarily <= targetDestSize) - or 0 if compression fails +

Reverse the logic : compresses as much data as possible from 'src' buffer + into already allocated buffer 'dst', of size >= 'targetDestSize'. + This function either compresses the entire 'src' content into 'dst' if it's large enough, + or fill 'dst' buffer completely with as much data as possible from 'src'. + note: acceleration parameter is fixed to "default". + + *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + New value is necessarily <= input value. + @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + or 0 if compression fails.


int LZ4_decompress_fast (const char* src, char* dst, int originalSize);
-

This function is a bit faster than LZ4_decompress_safe(), -but it may misbehave on malformed input because it doesn't perform full validation of compressed data. - originalSize : is the uncompressed size to regenerate - Destination buffer must be already allocated, and its size must be >= 'originalSize' bytes. - return : number of bytes read from source buffer (== compressed size). - If the source stream is detected malformed, the function stops decoding and return a negative result. - note : This function is only usable if the originalSize of uncompressed data is known in advance. - The caller should also check that all the compressed input has been consumed properly, - i.e. that the return value matches the size of the buffer with compressed input. - The function never writes past the output buffer. However, since it doesn't know its 'src' size, - it may read past the intended input. Also, because match offsets are not validated during decoding, - reads from 'src' may underflow. Use this function in trusted environment **only**. +

This function used to be a bit faster than LZ4_decompress_safe(), + though situation has changed in recent versions, + and now `LZ4_decompress_safe()` can be as fast and sometimes faster than `LZ4_decompress_fast()`. + Moreover, LZ4_decompress_fast() is not protected vs malformed input, as it doesn't perform full validation of compressed data. + As a consequence, this function is no longer recommended, and may be deprecated in future versions. + It's only remaining specificity is that it can decompress data without knowing its compressed size. + + originalSize : is the uncompressed size to regenerate. + `dst` must be already allocated, its size must be >= 'originalSize' bytes. + @return : number of bytes read from source buffer (== compressed size). + If the source stream is detected malformed, the function stops decoding and returns a negative result. + note : This function requires uncompressed originalSize to be known in advance. + The function never writes past the output buffer. + However, since it doesn't know its 'src' size, it may read past the intended input. + Also, because match offsets are not validated during decoding, + reads from 'src' may underflow. + Use this function in trusted environment **only**. +


int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity);
-

This function decompress a compressed block of size 'srcSize' at position 'src' - into destination buffer 'dst' of size 'dstCapacity'. - The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that. - However, it's not accurate, and may write more than 'targetOutputSize' (but always <= dstCapacity). - @return : the number of bytes decoded in the destination buffer (necessarily <= dstCapacity) - Note : this number can also be < targetOutputSize, if compressed block contains less data. - Therefore, always control how many bytes were decoded. - If source stream is detected malformed, function returns a negative result. - This function is protected against malicious data packets. +

Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + into destination buffer 'dst' of size 'dstCapacity'. + Up to 'targetOutputSize' bytes will be decoded. + The function stops decoding on reaching this objective, + which can boost performance when only the beginning of a block is required. + + @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity) + If source stream is detected malformed, function returns a negative result. + + Note : @return can be < targetOutputSize, if compressed block contains less data. + + Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity, + and expects targetOutputSize <= dstCapacity. + It effectively stops decoding on reaching targetOutputSize, + so dstCapacity is kind of redundant. + This is because in a previous version of this function, + decoding operation would not "break" a sequence in the middle. + As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize, + it could write more bytes, though only up to dstCapacity. + Some "margin" used to be required for this operation to work properly. + This is no longer necessary. + The function nonetheless keeps its signature, in an effort to not break API. +


Streaming Compression Functions


@@ -179,16 +201,23 @@ int           LZ4_freeStream (LZ4_stream_t* streamPtr);
   'dst' buffer must be already allocated.
   If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster.
 
-  Important : The previous 64KB of source data is assumed to remain present and unmodified in memory!
-
-  Special 1 : When input is a double-buffer, they can have any size, including < 64 KB.
-              Make sure that buffers are separated by at least one byte.
-              This way, each block only depends on previous block.
-  Special 2 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
-
  @return : size of compressed block
            or 0 if there is an error (typically, cannot fit into 'dst').
-  After an error, the stream status is invalid, it can only be reset or freed.
+
+  Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block.
+           Each block has precise boundaries.
+           It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together.
+           Each block must be decompressed separately, calling LZ4_decompress_*() with associated metadata.
+
+  Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory!
+
+  Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB.
+           Make sure that buffers are separated, by at least one byte.
+           This construction ensures that each block only depends on previous block.
+
+  Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB.
+
+  Note 5 : After an error, the stream status is invalid, it can only be reset or freed.
  
 


diff --git a/lib/lz4.h b/lib/lz4.h index ce4d033..c8ee206 100644 --- a/lib/lz4.h +++ b/lib/lz4.h @@ -226,14 +226,28 @@ LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePt LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); /*! LZ4_decompress_safe_partial() : - * This function decompresses a compressed block of size 'srcSize' at position 'src' + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', * into destination buffer 'dst' of size 'dstCapacity'. - * The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that. - * However, it's not accurate, and may write more than 'targetOutputSize' (but always <= dstCapacity). + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective, + * which can boost performance when only the beginning of a block is required. + * * @return : the number of bytes decoded in `dst` (necessarily <= dstCapacity) - * Note : this number can also be < targetOutputSize, if compressed block contains less data. * If source stream is detected malformed, function returns a negative result. - * This function is protected against malicious data packets. + * + * Note : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : this function features 2 parameters, targetOutputSize and dstCapacity, + * and expects targetOutputSize <= dstCapacity. + * It effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in a previous version of this function, + * decoding operation would not "break" a sequence in the middle. + * As a consequence, there was no guarantee that decoding would stop at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * This is no longer necessary. + * The function nonetheless keeps its signature, in an effort to not break API. */ LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); -- cgit v0.12 From e22bb8007432d42cda4c83daeb9e97ea15b7ef67 Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Fri, 7 Sep 2018 18:22:01 -0700 Subject: fixed fuzzer test and removed one blind copy, since there is no more guarantee that at least 4 bytes are still available in output buffer --- lib/lz4.c | 10 ++++++---- tests/fuzzer.c | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/lz4.c b/lib/lz4.c index 6febb90..dbda4f1 100644 --- a/lib/lz4.c +++ b/lib/lz4.c @@ -1398,8 +1398,7 @@ typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; * Note that it is important for performance that this function really get inlined, * in order to remove useless branches during compilation optimization. */ -LZ4_FORCE_INLINE -int +LZ4_FORCE_INLINE int LZ4_decompress_generic( const char* const src, char* const dst, @@ -1432,7 +1431,7 @@ LZ4_decompress_generic( const BYTE* const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/; const BYTE* const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/; - DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i)", srcSize); + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); /* Special cases */ assert(src != NULL); @@ -1537,7 +1536,7 @@ LZ4_decompress_generic( _copy_match: if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ - LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ + // LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ /* note : no longer valid with partialDecoding, since there is no guarantee that at least 4 bytes are available */ if (length == ML_MASK) { unsigned s; @@ -1584,15 +1583,18 @@ _copy_match: /* specific : partial decode : does not respect end parsing restrictions */ assert(op<=oend); if (partialDecoding && (cpy > oend-12)) { + DEBUGLOG(2, "match copy close to the end"); size_t const mlen = MIN(length, (size_t)(oend-op)); const BYTE* const matchEnd = match + mlen; BYTE* const copyEnd = op + mlen; if (matchEnd > op) { /* overlap copy */ while (op < copyEnd) *op++ = *match++; } else { + DEBUGLOG(2, "let's memcopy %zu bytes (non overlapping)", mlen); memcpy(op, match, mlen); } op = copyEnd; + if (op==oend) break; continue; } diff --git a/tests/fuzzer.c b/tests/fuzzer.c index bdb7841..d6a5f51 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -582,11 +582,12 @@ static int FUZ_test(U32 seed, U32 nbCycles, const U32 startCycle, const double c FUZ_DISPLAYTEST("test LZ4_decompress_safe_partial"); { size_t const missingBytes = FUZ_rand(&randState) % blockSize; int const targetSize = (int)(blockSize - missingBytes); - char const sentinel = compressedBuffer[targetSize] = block[targetSize] ^ 0x5A; + char const sentinel = decodedBuffer[targetSize] = block[targetSize] ^ 0x5A; + assert(decodedBuffer[targetSize] == sentinel); int const decResult = LZ4_decompress_safe_partial(compressedBuffer, decodedBuffer, compressedSize, targetSize, blockSize); - FUZ_CHECKTEST(decResult<0, "LZ4_decompress_safe_partial failed despite valid input data"); + FUZ_CHECKTEST(decResult<0, "LZ4_decompress_safe_partial failed despite valid input data (error:%i)", decResult); FUZ_CHECKTEST(decResult != targetSize, "LZ4_decompress_safe_partial did not regenerated required amount of data (%i < %i <= %i)", decResult, targetSize, blockSize); - FUZ_CHECKTEST(compressedBuffer[targetSize] != sentinel, "LZ4_decompress_safe_partial overwrite beyond requested size (though %i <= %i <= %i)", decResult, targetSize, blockSize); + FUZ_CHECKTEST(decodedBuffer[targetSize] != sentinel, "LZ4_decompress_safe_partial overwrite beyond requested size (though %i <= %i <= %i)", decResult, targetSize, blockSize); } /* Test Compression with limited output size */ -- cgit v0.12 From 32272f9866eb6d46e4457190cc7562ade2aa2ca8 Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Mon, 10 Sep 2018 15:51:53 -0700 Subject: removed temporary debug traces --- lib/lz4.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/lz4.c b/lib/lz4.c index dbda4f1..c68a7da 100644 --- a/lib/lz4.c +++ b/lib/lz4.c @@ -1583,14 +1583,12 @@ _copy_match: /* specific : partial decode : does not respect end parsing restrictions */ assert(op<=oend); if (partialDecoding && (cpy > oend-12)) { - DEBUGLOG(2, "match copy close to the end"); size_t const mlen = MIN(length, (size_t)(oend-op)); const BYTE* const matchEnd = match + mlen; BYTE* const copyEnd = op + mlen; if (matchEnd > op) { /* overlap copy */ while (op < copyEnd) *op++ = *match++; } else { - DEBUGLOG(2, "let's memcopy %zu bytes (non overlapping)", mlen); memcpy(op, match, mlen); } op = copyEnd; -- cgit v0.12 From 63fc6fbf7ee9e6a587598af085f22742cd9e6798 Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Mon, 10 Sep 2018 16:22:16 -0700 Subject: restored nullifying output to counter possible (offset==0) --- lib/lz4.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/lz4.c b/lib/lz4.c index c68a7da..0545bbc 100644 --- a/lib/lz4.c +++ b/lib/lz4.c @@ -1536,7 +1536,11 @@ LZ4_decompress_generic( _copy_match: if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ - // LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ /* note : no longer valid with partialDecoding, since there is no guarantee that at least 4 bytes are available */ + if (!partialDecoding) { + assert(oend > op); + assert(oend - op >= 4); + LZ4_write32(op, 0); /* silence an msan warning when offset==0; costs <1%; */ + } /* note : when partialDecoding, there is no guarantee that at least 4 bytes remain available in output buffer */ if (length == ML_MASK) { unsigned s; -- cgit v0.12 From b87a8e9e623d6e2404ae9f948a4e0ee8f1415bee Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Mon, 10 Sep 2018 16:48:41 -0700 Subject: fixed minor warning in fuzzer.c added a few more comments and assert() --- lib/lz4.c | 14 ++++++++------ lib/lz4.h | 8 ++++---- tests/fuzzer.c | 1 - 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/lz4.c b/lib/lz4.c index 0545bbc..133501d 100644 --- a/lib/lz4.c +++ b/lib/lz4.c @@ -1,6 +1,6 @@ /* LZ4 - Fast LZ compression algorithm - Copyright (C) 2011-2017, Yann Collet. + Copyright (C) 2011-present, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -1405,9 +1405,9 @@ LZ4_decompress_generic( int srcSize, int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ - endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ - earlyEnd_directive partialDecoding, /* full, partial */ - int dict, /* noDict, withPrefix64k, usingExtDict */ + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ const BYTE* const dictStart, /* only if dict==usingExtDict */ const size_t dictSize /* note : = 0 if noDict */ @@ -1434,6 +1434,7 @@ LZ4_decompress_generic( DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); /* Special cases */ + assert(lowPrefix <= op); assert(src != NULL); if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0 ? 1 : -1); @@ -1504,6 +1505,7 @@ LZ4_decompress_generic( /* copy literals */ cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) ) || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) { @@ -1523,7 +1525,7 @@ LZ4_decompress_generic( } } else { - LZ4_wildCopy(op, ip, cpy); + LZ4_wildCopy(op, ip, cpy); /* may overwrite up to WILDCOPYLENGTH beyond cpy */ ip += length; op = cpy; } @@ -1584,7 +1586,7 @@ _copy_match: /* copy match within block */ cpy = op + length; - /* specific : partial decode : does not respect end parsing restrictions */ + /* partialDecoding : may not respect endBlock parsing restrictions */ assert(op<=oend); if (partialDecoding && (cpy > oend-12)) { size_t const mlen = MIN(length, (size_t)(oend-op)); diff --git a/lib/lz4.h b/lib/lz4.h index c8ee206..059ef7c 100644 --- a/lib/lz4.h +++ b/lib/lz4.h @@ -1,7 +1,7 @@ /* * LZ4 - Fast LZ compression algorithm * Header File - * Copyright (C) 2011-2017, Yann Collet. + * Copyright (C) 2011-present, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -46,7 +46,7 @@ extern "C" { /** Introduction - LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core, + LZ4 is lossless compression algorithm, providing compression speed at 500 MB/s per core, scalable with multi-cores CPU. It features an extremely fast decoder, with speed in multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. @@ -62,8 +62,8 @@ extern "C" { An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md), take care of encoding standard metadata alongside LZ4-compressed blocks. - If your application requires interoperability, it's recommended to use it. - A library is provided to take care of it, see lz4frame.h. + Frame format is required for interoperability. + It is delivered through a companion API, declared in lz4frame.h. */ /*^*************************************************************** diff --git a/tests/fuzzer.c b/tests/fuzzer.c index d6a5f51..b29e82e 100644 --- a/tests/fuzzer.c +++ b/tests/fuzzer.c @@ -583,7 +583,6 @@ static int FUZ_test(U32 seed, U32 nbCycles, const U32 startCycle, const double c { size_t const missingBytes = FUZ_rand(&randState) % blockSize; int const targetSize = (int)(blockSize - missingBytes); char const sentinel = decodedBuffer[targetSize] = block[targetSize] ^ 0x5A; - assert(decodedBuffer[targetSize] == sentinel); int const decResult = LZ4_decompress_safe_partial(compressedBuffer, decodedBuffer, compressedSize, targetSize, blockSize); FUZ_CHECKTEST(decResult<0, "LZ4_decompress_safe_partial failed despite valid input data (error:%i)", decResult); FUZ_CHECKTEST(decResult != targetSize, "LZ4_decompress_safe_partial did not regenerated required amount of data (%i < %i <= %i)", decResult, targetSize, blockSize); -- cgit v0.12 From 6d32240b2e9cb921f9b34b790d787d0ee1ea51cb Mon Sep 17 00:00:00 2001 From: Yann Collet Date: Mon, 10 Sep 2018 17:36:40 -0700 Subject: clarify constant MFLIMIT and separate it from MATCH_SAFEGUARD_DISTANCE. While both constants have same value, they do not seve same purpose, hence should not be confused. --- lib/lz4.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/lz4.c b/lib/lz4.c index 133501d..4046102 100644 --- a/lib/lz4.c +++ b/lib/lz4.c @@ -297,8 +297,9 @@ void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) #define MINMATCH 4 #define WILDCOPYLENGTH 8 -#define LASTLITERALS 5 -#define MFLIMIT (WILDCOPYLENGTH+MINMATCH) +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ static const int LZ4_minLength = (MFLIMIT+1); #define KB *(1 <<10) @@ -1588,7 +1589,7 @@ _copy_match: /* partialDecoding : may not respect endBlock parsing restrictions */ assert(op<=oend); - if (partialDecoding && (cpy > oend-12)) { + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { size_t const mlen = MIN(length, (size_t)(oend-op)); const BYTE* const matchEnd = match + mlen; BYTE* const copyEnd = op + mlen; @@ -1616,7 +1617,7 @@ _copy_match: } op += 8; - if (unlikely(cpy > oend-12)) { + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ if (op < oCopyLimit) { -- cgit v0.12