mirror of
https://github.com/lz4/lz4.git
synced 2026-10-24 01:48:57 +03:00
Follow-up to "lz4frame: avoid signed overflow", per the #1824 review: saturate at LZ4_ACCELERATION_MAX rather than INT_MAX, since higher values make no difference. LZ4_ACCELERATION_DEFAULT and LZ4_ACCELERATION_MAX move from lz4.c to the LZ4_STATIC_LINKING_ONLY section of lz4.h, which lz4.c, lz4frame.c, bench.c and frametest.c already include, so their values derive from it. The LZ4_compress_fast() documentation already referred to both constants. bench.c computed the acceleration the same way (-cLevel + 1), so `lz4 -b --fast=2147483647` hit the same signed overflow: apply the same cap there. test-lz4-basic.sh: --fast=2147483647 must produce the same frame as --fast=65536 (fails on dev, where acceleration wraps to the default), and its benchmark runs (caught by the UBSan CI job before this fix). lz4.1: --fast values above 65536 make no difference.
3844 lines
179 KiB
C
3844 lines
179 KiB
C
/*
|
|
frameTest - test tool for lz4frame
|
|
Copyright (C) Yann Collet 2014-2020
|
|
|
|
GPL v2 License
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
You can contact the author at :
|
|
- LZ4 homepage : http://www.lz4.org
|
|
- LZ4 source repository : https://github.com/lz4/lz4
|
|
*/
|
|
|
|
/*-************************************
|
|
* Compiler specific
|
|
**************************************/
|
|
#ifdef _MSC_VER /* Visual Studio */
|
|
# pragma warning(disable : 26451) /* disable: Arithmetic overflow */
|
|
# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
|
|
#endif
|
|
|
|
|
|
/*-************************************
|
|
* Includes
|
|
**************************************/
|
|
#include "util.h" /* U32 */
|
|
#include <stdlib.h> /* malloc, free */
|
|
#include <stdio.h> /* fprintf */
|
|
#include <string.h> /* strcmp */
|
|
#include <time.h> /* clock_t, clock(), CLOCKS_PER_SEC */
|
|
#include <assert.h>
|
|
#include <limits.h> /* INT_MIN */
|
|
#include "lz4frame.h" /* included multiple times to test correctness/safety */
|
|
#include "lz4frame.h"
|
|
#include "lz4file.h"
|
|
#define LZ4F_STATIC_LINKING_ONLY
|
|
#include "lz4frame.h"
|
|
#include "lz4frame.h"
|
|
#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX, LZ4_ACCELERATION_MAX */
|
|
#include "lz4.h" /* LZ4_VERSION_STRING */
|
|
#define XXH_STATIC_LINKING_ONLY
|
|
#include "xxhash.h" /* XXH64 */
|
|
|
|
|
|
/* unoptimized version; solves endianness & alignment issues */
|
|
static void FUZ_writeLE32 (void* dstVoidPtr, U32 value32)
|
|
{
|
|
BYTE* const dstPtr = (BYTE*)dstVoidPtr;
|
|
dstPtr[0] = (BYTE) value32;
|
|
dstPtr[1] = (BYTE)(value32 >> 8);
|
|
dstPtr[2] = (BYTE)(value32 >> 16);
|
|
dstPtr[3] = (BYTE)(value32 >> 24);
|
|
}
|
|
|
|
|
|
/*-************************************
|
|
* Constants
|
|
**************************************/
|
|
#define KB *(1U<<10)
|
|
#define MB *(1U<<20)
|
|
#define GB *(1U<<30)
|
|
|
|
static const U32 nbTestsDefault = 256 KB;
|
|
#define FUZ_COMPRESSIBILITY_DEFAULT 50
|
|
static const U32 prime1 = 2654435761U;
|
|
static const U32 prime2 = 2246822519U;
|
|
|
|
static const U64 extCrcSeed = 1;
|
|
|
|
/*-************************************
|
|
* Macros
|
|
**************************************/
|
|
#define DISPLAY(...) fprintf(stderr, __VA_ARGS__)
|
|
#define DISPLAYLEVEL(l, ...) do { if (displayLevel>=(l)) DISPLAY(__VA_ARGS__); } while (0)
|
|
#define DISPLAYUPDATE(l, ...) do { if (displayLevel>=(l)) { \
|
|
if ((FUZ_GetClockSpan(g_clockTime) > refreshRate) || (displayLevel>=4)) \
|
|
{ g_clockTime = clock(); DISPLAY(__VA_ARGS__); \
|
|
if (displayLevel>=4) fflush(stdout); } } } while (0)
|
|
static const clock_t refreshRate = CLOCKS_PER_SEC / 6;
|
|
static clock_t g_clockTime = 0;
|
|
|
|
|
|
/*-***************************************
|
|
* Local Parameters
|
|
*****************************************/
|
|
static U32 no_prompt = 0;
|
|
static U32 displayLevel = 2;
|
|
static U32 use_pause = 0;
|
|
|
|
|
|
/*-*******************************************************
|
|
* Fuzzer functions
|
|
*********************************************************/
|
|
#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
|
|
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
|
|
|
|
typedef struct {
|
|
uintptr_t ptr;
|
|
size_t size;
|
|
} Test_alloc_alloc;
|
|
|
|
typedef struct {
|
|
Test_alloc_alloc* allocs;
|
|
size_t allocs_capacity;
|
|
|
|
size_t live_alloc_count;
|
|
size_t live_alloc_total_space;
|
|
} Test_alloc_state;
|
|
|
|
static Test_alloc_state g_testAllocState;
|
|
|
|
static void alloc_state_init(Test_alloc_state* state) {
|
|
state->allocs = NULL;
|
|
state->allocs_capacity = 0;
|
|
|
|
state->live_alloc_count = 0;
|
|
state->live_alloc_total_space = 0;
|
|
}
|
|
|
|
static void alloc_state_destroy(Test_alloc_state* state) {
|
|
free(state->allocs);
|
|
alloc_state_init(state);
|
|
}
|
|
|
|
static void alloc_state_record_alloc(Test_alloc_state* state, uintptr_t ptr, size_t size) {
|
|
size_t i;
|
|
Test_alloc_alloc* alloc = NULL;
|
|
if (ptr == (uintptr_t)NULL) {
|
|
assert(size == 0);
|
|
return;
|
|
}
|
|
for (i = 0; i < state->allocs_capacity; i++) {
|
|
if (state->allocs[i].ptr == (uintptr_t)NULL) {
|
|
alloc = &state->allocs[i];
|
|
break;
|
|
}
|
|
}
|
|
if (alloc == NULL) {
|
|
const size_t old_capacity = state->allocs_capacity;
|
|
const size_t new_capacity = (old_capacity + !old_capacity) * 2;
|
|
Test_alloc_alloc* new_allocs = (Test_alloc_alloc*)realloc(
|
|
state->allocs, new_capacity * sizeof(state->allocs[0]));
|
|
assert(state->live_alloc_count == old_capacity);
|
|
assert(new_allocs != NULL);
|
|
memset(&new_allocs[old_capacity], 0, (new_capacity - old_capacity) * sizeof(state->allocs[0]));
|
|
state->allocs = new_allocs;
|
|
state->allocs_capacity = new_capacity;
|
|
alloc = &state->allocs[old_capacity];
|
|
}
|
|
assert(alloc != NULL);
|
|
assert(alloc->ptr == (uintptr_t)NULL);
|
|
alloc->ptr = ptr;
|
|
alloc->size = size;
|
|
state->live_alloc_count++;
|
|
state->live_alloc_total_space += size;
|
|
}
|
|
|
|
static void alloc_state_record_free(Test_alloc_state* state, uintptr_t ptr) {
|
|
size_t i;
|
|
if (ptr == (uintptr_t)NULL) {
|
|
return;
|
|
}
|
|
for (i = 0; i < state->allocs_capacity; i++) {
|
|
Test_alloc_alloc* alloc = &state->allocs[i];
|
|
if (alloc->ptr == ptr) {
|
|
const size_t size = alloc->size;
|
|
assert(state->live_alloc_count >= 1);
|
|
assert(state->live_alloc_total_space >= size);
|
|
state->live_alloc_count--;
|
|
state->live_alloc_total_space -= size;
|
|
alloc->ptr = (uintptr_t)NULL;
|
|
alloc->size = 0;
|
|
return;
|
|
}
|
|
}
|
|
assert(0); /* didn't find matching entry */
|
|
}
|
|
|
|
static void* dummy_malloc(void* state, size_t s)
|
|
{
|
|
Test_alloc_state* const t = (Test_alloc_state*)state;
|
|
void* const p = malloc(s);
|
|
if (p==NULL) return NULL;
|
|
assert(t != NULL);
|
|
alloc_state_record_alloc(t, (uintptr_t)p, s);
|
|
DISPLAYLEVEL(6, "Allocating %llu bytes at address %p \n", (long long unsigned)s, p);
|
|
DISPLAYLEVEL(5, "nb allocated memory segments : %llu \n", (long long unsigned)t->live_alloc_count);
|
|
return p;
|
|
}
|
|
|
|
static void* dummy_calloc(void* state, size_t s)
|
|
{
|
|
Test_alloc_state* const t = (Test_alloc_state*)state;
|
|
void* const p = calloc(1, s);
|
|
if (p==NULL) return NULL;
|
|
assert(t != NULL);
|
|
alloc_state_record_alloc(t, (uintptr_t)p, s);
|
|
DISPLAYLEVEL(6, "Allocating and zeroing %llu bytes at address %p \n", (long long unsigned)s, p);
|
|
DISPLAYLEVEL(5, "nb allocated memory segments : %llu \n", (long long unsigned)t->live_alloc_count);
|
|
return p;
|
|
}
|
|
|
|
static void dummy_free(void* state, void* p)
|
|
{
|
|
Test_alloc_state* const t = (Test_alloc_state*)state;
|
|
if (p==NULL) {
|
|
DISPLAYLEVEL(5, "free() on NULL \n");
|
|
return;
|
|
}
|
|
DISPLAYLEVEL(6, "freeing memory at address %p \n", p);
|
|
assert(t != NULL);
|
|
alloc_state_record_free(t, (uintptr_t)p);
|
|
free(p);
|
|
DISPLAYLEVEL(5, "nb of allocated memory segments after this free : %llu \n", (long long unsigned)t->live_alloc_count);
|
|
}
|
|
|
|
static const LZ4F_CustomMem lz4f_cmem_test = {
|
|
dummy_malloc,
|
|
dummy_calloc,
|
|
dummy_free,
|
|
&g_testAllocState
|
|
};
|
|
|
|
|
|
static clock_t FUZ_GetClockSpan(clock_t clockStart)
|
|
{
|
|
return clock() - clockStart; /* works even if overflow; max span ~ 30 mn */
|
|
}
|
|
|
|
#define FUZ_rotl32(x,r) ((x << r) | (x >> (32 - r)))
|
|
unsigned int FUZ_rand(unsigned int* src)
|
|
{
|
|
U32 rand32 = *src;
|
|
rand32 *= prime1;
|
|
rand32 += prime2;
|
|
rand32 = FUZ_rotl32(rand32, 13);
|
|
*src = rand32;
|
|
return rand32 >> 5;
|
|
}
|
|
|
|
#define RAND_BITS(N) (FUZ_rand(randState) & ((1 << (N))-1))
|
|
#define FUZ_LITERAL (RAND_BITS(6) + '0')
|
|
#define FUZ_ABOUT(_R) ((FUZ_rand(randState) % (_R)) + (FUZ_rand(randState) % (_R)) + 1)
|
|
static void FUZ_fillCompressibleNoiseBuffer(void* buffer, size_t bufferSize, double proba, U32* randState)
|
|
{
|
|
BYTE* BBuffer = (BYTE*)buffer;
|
|
size_t pos = 0;
|
|
U32 P32 = (U32)(32768 * proba);
|
|
|
|
/* First Byte */
|
|
BBuffer[pos++] = FUZ_LITERAL;
|
|
|
|
while (pos < bufferSize) {
|
|
/* Select : Literal (noise) or copy (within 64K) */
|
|
if (RAND_BITS(15) < P32) {
|
|
/* Copy (within 64K) */
|
|
size_t const lengthRand = FUZ_ABOUT(8) + 4;
|
|
size_t const length = MIN(lengthRand, bufferSize - pos);
|
|
size_t const end = pos + length;
|
|
size_t const offsetRand = RAND_BITS(15) + 1;
|
|
size_t const offset = MIN(offsetRand, pos);
|
|
size_t match = pos - offset;
|
|
while (pos < end) BBuffer[pos++] = BBuffer[match++];
|
|
} else {
|
|
/* Literal (noise) */
|
|
size_t const lengthRand = FUZ_ABOUT(4);
|
|
size_t const length = MIN(lengthRand, bufferSize - pos);
|
|
size_t const end = pos + length;
|
|
while (pos < end) BBuffer[pos++] = FUZ_LITERAL;
|
|
} }
|
|
}
|
|
|
|
static unsigned FUZ_highbit(U32 v32)
|
|
{
|
|
unsigned nbBits = 0;
|
|
if (v32==0) return 0;
|
|
while (v32) {v32 >>= 1; nbBits ++;}
|
|
return nbBits;
|
|
}
|
|
|
|
|
|
/*-*******************************************************
|
|
* Tests
|
|
*********************************************************/
|
|
|
|
#define CONTROL(c) { \
|
|
if (!(c)) { \
|
|
DISPLAY("Error (line %i) => %s not respected \n", __LINE__, #c); \
|
|
return 1; \
|
|
} }
|
|
|
|
static int bug1227(void)
|
|
{
|
|
LZ4F_dctx* dctx;
|
|
CONTROL(!LZ4F_isError(LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION)));
|
|
|
|
/* first session */
|
|
{ const char s9Buffer[9] = { 0 };
|
|
char d9Buffer[sizeof(s9Buffer)];
|
|
size_t const c9SizeBound = LZ4F_compressFrameBound(sizeof(s9Buffer), NULL);
|
|
void* const c9Buffer = malloc(c9SizeBound);
|
|
/* First compress a valid frame */
|
|
LZ4F_preferences_t pref = LZ4F_INIT_PREFERENCES;
|
|
pref.frameInfo.contentSize = sizeof(s9Buffer);
|
|
CONTROL(c9Buffer != NULL);
|
|
{ size_t const c9Size = LZ4F_compressFrame(c9Buffer, c9SizeBound, s9Buffer, sizeof(s9Buffer), &pref);
|
|
CONTROL(!LZ4F_isError(c9Size));
|
|
assert(c9Size > 15);
|
|
/* decompress it, but do not complete the process - state not terminated correctly */
|
|
{ size_t dstSize = sizeof(d9Buffer);
|
|
size_t srcSize = 15;
|
|
size_t const d9Size = LZ4F_decompress(dctx, d9Buffer, &dstSize, c9Buffer, &srcSize, NULL);
|
|
CONTROL(!LZ4F_isError(d9Size));
|
|
CONTROL(srcSize < c9Size); /* not entirely consumed */
|
|
}
|
|
}
|
|
free(c9Buffer);
|
|
}
|
|
LZ4F_resetDecompressionContext(dctx); /* unfinished session -> reset should make it clean */
|
|
|
|
/* second session : generate a valid 0-size frame with no content size field (default) */
|
|
{ size_t const c0SizeBound = LZ4F_compressFrameBound(0, NULL);
|
|
void* const c0Buffer = malloc(c0SizeBound);
|
|
char d0Buffer[1];
|
|
CONTROL(c0Buffer != NULL);
|
|
{ size_t const c0Size = LZ4F_compressFrame(c0Buffer, c0SizeBound, NULL, 0, NULL);
|
|
CONTROL(!LZ4F_isError(c0Size));
|
|
/* now decompress this valid empty frame */
|
|
{ size_t dstSize = sizeof(d0Buffer);
|
|
size_t srcSize = c0Size;
|
|
size_t const d0Size = LZ4F_decompress(dctx, d0Buffer, &dstSize, c0Buffer, &srcSize, NULL);
|
|
CONTROL(!LZ4F_isError(d0Size));
|
|
CONTROL(dstSize == 0);
|
|
CONTROL(srcSize == c0Size);
|
|
} }
|
|
free(c0Buffer);
|
|
}
|
|
|
|
LZ4F_freeDecompressionContext(dctx);
|
|
return 0;
|
|
}
|
|
|
|
#define CHECK_V(v,f) v = f; if (LZ4F_isError(v)) { fprintf(stderr, "%s \n", LZ4F_getErrorName(v)); goto _output_error; }
|
|
#define CHECK(f) { LZ4F_errorCode_t const CHECK_V(err_ , f); }
|
|
|
|
static int testMixedUpdates(U32 seed)
|
|
{
|
|
size_t const blockSize = 64 KB;
|
|
size_t const bufferedSizes[] = { 1, 1 KB, (64 KB) - 1 };
|
|
size_t const inputSizes[] = { 0, 1, (64 KB) - 1, 64 KB, (64 KB) + 1, 128 KB };
|
|
size_t const frameCapacity = 4 * blockSize + 64;
|
|
BYTE* const src = (BYTE*)malloc(3 * blockSize);
|
|
BYTE* const frame = (BYTE*)malloc(frameCapacity);
|
|
BYTE* const decoded = (BYTE*)malloc(3 * blockSize);
|
|
BYTE* dst = NULL;
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_dctx* dctx = NULL;
|
|
unsigned checksums, uncompressedFirst, autoFlush;
|
|
size_t b, i, pos;
|
|
int result = 1;
|
|
|
|
if (src == NULL || frame == NULL || decoded == NULL) goto _output_error;
|
|
/* Incompressible input exercises the largest possible block output. */
|
|
for (pos = 0; pos < 3 * blockSize; ++pos) src[pos] = (BYTE)FUZ_rand(&seed);
|
|
CHECK(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION));
|
|
CHECK(LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION));
|
|
|
|
for (checksums = 0; checksums < 4; ++checksums)
|
|
for (uncompressedFirst = 0; uncompressedFirst < 2; ++uncompressedFirst)
|
|
for (autoFlush = 0; autoFlush < 2; ++autoFlush)
|
|
for (b = 0; b < sizeof(bufferedSizes) / sizeof(bufferedSizes[0]); ++b)
|
|
for (i = 0; i < sizeof(inputSizes) / sizeof(inputSizes[0]); ++i) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
size_t const firstSize = bufferedSizes[b];
|
|
size_t const secondSize = inputSizes[i];
|
|
size_t const srcSize = firstSize + secondSize;
|
|
size_t frameSize, cSize, bound;
|
|
unsigned update;
|
|
|
|
prefs.frameInfo.blockMode = LZ4F_blockIndependent;
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)(checksums & 1);
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)(checksums >> 1);
|
|
prefs.frameInfo.contentSize = srcSize;
|
|
prefs.autoFlush = autoFlush;
|
|
CHECK_V(frameSize, LZ4F_compressBegin(cctx, frame, frameCapacity, &prefs));
|
|
|
|
for (update = 0; update < 2; ++update) {
|
|
size_t const inputSize = update ? secondSize : firstSize;
|
|
const BYTE* const input = src + (update ? firstSize : 0);
|
|
unsigned const uncompressed = uncompressedFirst ^ update;
|
|
bound = LZ4F_compressBound(inputSize, &prefs);
|
|
dst = (BYTE*)malloc(bound + 1);
|
|
if (dst == NULL) goto _output_error;
|
|
dst[bound] = 0xA5;
|
|
cSize = uncompressed
|
|
? LZ4F_uncompressedUpdate(cctx, dst, bound, input, inputSize, NULL)
|
|
: LZ4F_compressUpdate(cctx, dst, bound, input, inputSize, NULL);
|
|
if (LZ4F_isError(cSize) || cSize > bound || dst[bound] != 0xA5) {
|
|
DISPLAY("Mixed update exceeds its bound or fails: first=%u, second=%u, "
|
|
"uncompressed=%u, checksums=%u, autoFlush=%u \n",
|
|
(unsigned)firstSize, (unsigned)secondSize,
|
|
uncompressed, checksums, autoFlush);
|
|
goto _output_error;
|
|
}
|
|
if (update == 0 && !autoFlush && cSize != 0) goto _output_error;
|
|
if (cSize > frameCapacity - frameSize) goto _output_error;
|
|
memcpy(frame + frameSize, dst, cSize);
|
|
frameSize += cSize;
|
|
free(dst);
|
|
dst = NULL;
|
|
}
|
|
|
|
bound = LZ4F_compressBound(0, &prefs);
|
|
if (bound >= frameCapacity - frameSize) goto _output_error;
|
|
frame[frameSize + bound] = 0xA5;
|
|
CHECK_V(cSize, LZ4F_compressEnd(cctx, frame + frameSize, bound, NULL));
|
|
if (cSize > bound || frame[frameSize + bound] != 0xA5) goto _output_error;
|
|
frameSize += cSize;
|
|
{ size_t consumed = frameSize;
|
|
size_t decodedSize = 3 * blockSize;
|
|
size_t dResult;
|
|
CHECK_V(dResult, LZ4F_decompress(dctx, decoded, &decodedSize,
|
|
frame, &consumed, NULL));
|
|
if (dResult != 0 || consumed != frameSize || decodedSize != srcSize)
|
|
goto _output_error;
|
|
if (memcmp(src, decoded, srcSize) != 0) goto _output_error;
|
|
}
|
|
|
|
/* A mode switch writes 2 blocks : the flushed one, then the new one.
|
|
* Capacity is only required for what is actually written.
|
|
* Insufficient capacity fails without writing beyond it, and invalidates the frame. */
|
|
if (!autoFlush && firstSize == blockSize - 1 && secondSize == blockSize) {
|
|
size_t const blockOverhead = 4 + 4 * prefs.frameInfo.blockChecksumFlag;
|
|
size_t const flushSize = firstSize + blockOverhead; /* incompressible => raw */
|
|
size_t const exactSize = flushSize + secondSize + blockOverhead;
|
|
size_t const capacities[] = { 0, flushSize - 1, flushSize, exactSize - 1, exactSize };
|
|
size_t s;
|
|
for (s = 0; s < sizeof(capacities) / sizeof(capacities[0]); ++s) {
|
|
size_t const capacity = capacities[s];
|
|
CHECK(LZ4F_compressBegin(cctx, frame, frameCapacity, &prefs));
|
|
bound = LZ4F_compressBound(firstSize, &prefs);
|
|
CHECK_V(cSize, uncompressedFirst
|
|
? LZ4F_uncompressedUpdate(cctx, frame, bound, src, firstSize, NULL)
|
|
: LZ4F_compressUpdate(cctx, frame, bound, src, firstSize, NULL));
|
|
if (cSize != 0) goto _output_error;
|
|
dst = (BYTE*)malloc(capacity + 1);
|
|
if (dst == NULL) goto _output_error;
|
|
dst[capacity] = 0xA5;
|
|
cSize = uncompressedFirst
|
|
? LZ4F_compressUpdate(cctx, dst, capacity, src + firstSize, secondSize, NULL)
|
|
: LZ4F_uncompressedUpdate(cctx, dst, capacity, src + firstSize, secondSize, NULL);
|
|
if (dst[capacity] != 0xA5) goto _output_error;
|
|
if (capacity >= exactSize) {
|
|
if (cSize != exactSize) goto _output_error;
|
|
} else {
|
|
if (LZ4F_getErrorCode(cSize) != LZ4F_ERROR_dstCapacity_tooSmall)
|
|
goto _output_error;
|
|
cSize = LZ4F_compressEnd(cctx, frame, frameCapacity, NULL);
|
|
if (LZ4F_getErrorCode(cSize) != LZ4F_ERROR_compressionState_uninitialized)
|
|
goto _output_error;
|
|
}
|
|
free(dst);
|
|
dst = NULL;
|
|
}
|
|
}
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(dst);
|
|
free(src);
|
|
free(frame);
|
|
free(decoded);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
LZ4F_freeDecompressionContext(dctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/*-*******************************************************
|
|
* Compression into dst capacities < compressBound (#1817)
|
|
* Bounds guarantee success, they are not requirements :
|
|
* compression must succeed if and only if its output fits,
|
|
* and then produce the same output as with a bound-sized buffer.
|
|
*********************************************************/
|
|
|
|
#define SD_CANARY 0xA5
|
|
#define SD_CANARY_SIZE 8
|
|
#define SD_ELTS_MAX 256
|
|
|
|
#define SD_CHECK(c, ...) do { if (!(c)) { \
|
|
DISPLAY("Error (frametest.c:%i) : %s => ", __LINE__, #c); \
|
|
DISPLAY(__VA_ARGS__); DISPLAY(" \n"); \
|
|
goto _output_error; \
|
|
} } while (0)
|
|
|
|
static U32 FUZ_readLE32(const void* srcVoidPtr)
|
|
{
|
|
const BYTE* const p = (const BYTE*)srcVoidPtr;
|
|
return (U32)p[0] + ((U32)p[1] << 8) + ((U32)p[2] << 16) + ((U32)p[3] << 24);
|
|
}
|
|
|
|
static int sd_isTooSmall(size_t r)
|
|
{
|
|
return LZ4F_getErrorCode(r) == LZ4F_ERROR_dstCapacity_tooSmall;
|
|
}
|
|
|
|
static int sd_isUninitialized(size_t r)
|
|
{
|
|
return LZ4F_getErrorCode(r) == LZ4F_ERROR_compressionState_uninitialized;
|
|
}
|
|
|
|
static void sd_setCanary(BYTE* p) { memset(p, SD_CANARY, SD_CANARY_SIZE); }
|
|
|
|
static int sd_canaryIntact(const BYTE* p)
|
|
{
|
|
size_t n;
|
|
for (n = 0; n < SD_CANARY_SIZE; n++) if (p[n] != SD_CANARY) return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* sd_frameElementEnds() :
|
|
* Lists end positions of each element of @frame :
|
|
* header, then each block, then endMark, then optional content checksum.
|
|
* @return : nb of elements, or 0 if @frame is not a well-formed frame */
|
|
static size_t sd_frameElementEnds(size_t* ends, size_t maxEnds, const BYTE* frame, size_t frameSize)
|
|
{
|
|
size_t nb = 0;
|
|
size_t pos;
|
|
unsigned blockChecksum, contentChecksum;
|
|
if (frameSize < LZ4F_HEADER_SIZE_MIN || maxEnds == 0) return 0;
|
|
pos = LZ4F_headerSize(frame, frameSize);
|
|
if (LZ4F_isError(pos) || pos > frameSize) return 0;
|
|
blockChecksum = (frame[4] >> 4) & 1;
|
|
contentChecksum = (frame[4] >> 2) & 1;
|
|
ends[nb++] = pos;
|
|
for (;;) {
|
|
U32 header;
|
|
if (pos + 4 > frameSize || nb == maxEnds) return 0;
|
|
header = FUZ_readLE32(frame + pos);
|
|
pos += 4;
|
|
if (header == 0) { /* endMark */
|
|
ends[nb++] = pos;
|
|
if (contentChecksum) {
|
|
if (nb == maxEnds) return 0;
|
|
pos += 4;
|
|
ends[nb++] = pos;
|
|
}
|
|
return (pos == frameSize) ? nb : 0;
|
|
}
|
|
pos += (header & 0x7FFFFFFFU) + 4 * blockChecksum;
|
|
ends[nb++] = pos;
|
|
}
|
|
}
|
|
|
|
/* sd_pickSmallCapacity() :
|
|
* @return a capacity < @size, for an output starting at @start within a frame.
|
|
* Favors capacities ending at, or just before, the end of a frame element. */
|
|
static size_t sd_pickSmallCapacity(const size_t* ends, size_t nbEnds, size_t start, size_t size, U32* rand)
|
|
{
|
|
assert(size > 0);
|
|
switch (FUZ_rand(rand) % 5) {
|
|
case 0: return size - 1;
|
|
case 1: return 0;
|
|
case 2: return FUZ_rand(rand) % size;
|
|
default:
|
|
{ size_t first = 0, last, n;
|
|
while (first < nbEnds && ends[first] <= start) first++;
|
|
last = first;
|
|
while (last < nbEnds && ends[last] <= start + size) last++;
|
|
if (first == last) return size - 1;
|
|
n = first + FUZ_rand(rand) % (last - first);
|
|
if ((FUZ_rand(rand) & 1) && (ends[n] < start + size)) return ends[n] - start;
|
|
return ends[n] - start - 1;
|
|
} }
|
|
}
|
|
|
|
static int sd_decodesTo(const BYTE* frame, size_t frameSize,
|
|
const BYTE* expected, size_t expectedSize,
|
|
const void* dict, size_t dictSize,
|
|
BYTE* decoded, size_t decodedCapacity)
|
|
{
|
|
LZ4F_dctx* dctx;
|
|
size_t dSize = decodedCapacity;
|
|
size_t cSize = frameSize;
|
|
size_t r;
|
|
if (LZ4F_isError(LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION))) return 0;
|
|
r = LZ4F_decompress_usingDict(dctx, decoded, &dSize, frame, &cSize, dict, dictSize, NULL);
|
|
LZ4F_freeDecompressionContext(dctx);
|
|
return (r == 0) && (cSize == frameSize) && (dSize == expectedSize)
|
|
&& (memcmp(decoded, expected, expectedSize) == 0);
|
|
}
|
|
|
|
|
|
/* LZ4F_compressBegin*() only needs the actual header size */
|
|
static int testSmallDstBegin(U32 seed)
|
|
{
|
|
size_t const dictSize = 16 KB;
|
|
size_t const srcSize = 100 KB;
|
|
BYTE* const src = (BYTE*)malloc(srcSize);
|
|
size_t const frameCapacity = LZ4F_compressFrameBound(srcSize, NULL) + LZ4F_HEADER_SIZE_MAX;
|
|
BYTE* const frame = (BYTE*)malloc(frameCapacity);
|
|
BYTE* const decoded = (BYTE*)malloc(srcSize);
|
|
BYTE dst[LZ4F_HEADER_SIZE_MAX + SD_CANARY_SIZE];
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_CDict* cdict = NULL;
|
|
unsigned options, beginMode;
|
|
int result = 1;
|
|
|
|
SD_CHECK(src != NULL && frame != NULL && decoded != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(src, srcSize, 0.5, &seed);
|
|
cdict = LZ4F_createCDict(src, dictSize);
|
|
SD_CHECK(cdict != NULL, "LZ4F_createCDict failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
|
|
for (options = 0; options < 4; options++)
|
|
for (beginMode = 0; beginMode < 3; beginMode++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
unsigned const hasContentSize = options & 1;
|
|
unsigned const hasDictID = options >> 1;
|
|
size_t const headerSize = 7 + 8 * hasContentSize + 4 * hasDictID;
|
|
size_t const capacities[] = { headerSize, headerSize - 1, 1, 0 };
|
|
size_t c;
|
|
prefs.frameInfo.contentSize = hasContentSize ? 123456 : 0;
|
|
prefs.frameInfo.dictID = hasDictID ? 0x1817 : 0;
|
|
for (c = 0; c < sizeof(capacities) / sizeof(capacities[0]); c++) {
|
|
size_t const capacity = capacities[c];
|
|
size_t r;
|
|
memset(dst, SD_CANARY, sizeof(dst));
|
|
switch (beginMode) {
|
|
case 0: r = LZ4F_compressBegin(cctx, dst, capacity, &prefs); break;
|
|
case 1: r = LZ4F_compressBegin_usingDict(cctx, dst, capacity, src, dictSize, &prefs); break;
|
|
default: r = LZ4F_compressBegin_usingCDict(cctx, dst, capacity, cdict, &prefs); break;
|
|
}
|
|
if (capacity >= headerSize) {
|
|
SD_CHECK(r == headerSize, "options=%u, beginMode=%u, capacity=%u : %s",
|
|
options, beginMode, (unsigned)capacity,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "wrong header size");
|
|
SD_CHECK(sd_canaryIntact(dst + capacity), "options=%u, beginMode=%u : write beyond capacity",
|
|
options, beginMode);
|
|
} else {
|
|
SD_CHECK(sd_isTooSmall(r), "options=%u, beginMode=%u, capacity=%u : %s",
|
|
options, beginMode, (unsigned)capacity,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
/* nothing written at all */
|
|
{ size_t n;
|
|
for (n = 0; n < sizeof(dst); n++)
|
|
SD_CHECK(dst[n] == SD_CANARY, "options=%u, beginMode=%u, capacity=%u : dst modified",
|
|
options, beginMode, (unsigned)capacity);
|
|
} } } }
|
|
|
|
/* A failed begin must not disturb a frame in progress */
|
|
{ LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
LZ4F_preferences_t otherPrefs = LZ4F_INIT_PREFERENCES;
|
|
size_t pos = 0;
|
|
size_t r;
|
|
prefs.frameInfo.blockMode = LZ4F_blockLinked;
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
prefs.frameInfo.contentSize = srcSize;
|
|
otherPrefs.frameInfo.dictID = 7;
|
|
otherPrefs.compressionLevel = 9;
|
|
r = LZ4F_compressBegin(cctx, frame, frameCapacity, &prefs);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
pos += r;
|
|
r = LZ4F_compressUpdate(cctx, frame + pos, frameCapacity - pos, src, srcSize / 2, NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
pos += r;
|
|
r = LZ4F_compressBegin(cctx, dst, 7 + 4 - 1, &otherPrefs);
|
|
/* note : also checks the former error name remains available */
|
|
SD_CHECK(LZ4F_getErrorCode(r) == LZ4F_ERROR_dstMaxSize_tooSmall,
|
|
"%s", LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
r = LZ4F_compressUpdate(cctx, frame + pos, frameCapacity - pos, src + srcSize / 2, srcSize - srcSize / 2, NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
pos += r;
|
|
r = LZ4F_compressEnd(cctx, frame + pos, frameCapacity - pos, NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
pos += r;
|
|
SD_CHECK(sd_decodesTo(frame, pos, src, srcSize, NULL, 0, decoded, srcSize), "frame corrupted");
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(frame);
|
|
free(decoded);
|
|
LZ4F_freeCDict(cdict);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* One-shot : LZ4F_compressFrame() and LZ4F_compressFrame_usingCDict() */
|
|
|
|
typedef struct {
|
|
int usingCDict; /* 0 => LZ4F_compressFrame() */
|
|
LZ4F_cctx* cctx; /* used by LZ4F_compressFrame_usingCDict() */
|
|
const LZ4F_CDict* cdict;
|
|
const LZ4F_preferences_t* prefs;
|
|
const BYTE* src;
|
|
size_t srcSize;
|
|
} sd_frameJob;
|
|
|
|
static size_t sd_compressFrame(const sd_frameJob* job, void* dst, size_t dstCapacity)
|
|
{
|
|
if (!job->usingCDict)
|
|
return LZ4F_compressFrame(dst, dstCapacity, job->src, job->srcSize, job->prefs);
|
|
return LZ4F_compressFrame_usingCDict(job->cctx, dst, dstCapacity,
|
|
job->src, job->srcSize, job->cdict, job->prefs);
|
|
}
|
|
|
|
/* @return 1 if compression into @capacity behaves as expected, 0 otherwise */
|
|
static int sd_frameCapacityOK(const sd_frameJob* job, BYTE* dst, size_t capacity,
|
|
const BYTE* ref, size_t refSize)
|
|
{
|
|
size_t r;
|
|
sd_setCanary(dst + capacity);
|
|
r = sd_compressFrame(job, dst, capacity);
|
|
if (!sd_canaryIntact(dst + capacity)) {
|
|
DISPLAY("capacity %u : write beyond capacity \n", (unsigned)capacity);
|
|
return 0;
|
|
}
|
|
if (capacity >= refSize) {
|
|
if (r != refSize) {
|
|
DISPLAY("capacity %u >= frameSize %u : %s \n", (unsigned)capacity, (unsigned)refSize,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "different frame size");
|
|
return 0;
|
|
}
|
|
if (memcmp(dst, ref, refSize)) {
|
|
DISPLAY("capacity %u >= frameSize %u : different frame content \n",
|
|
(unsigned)capacity, (unsigned)refSize);
|
|
return 0;
|
|
}
|
|
} else if (!sd_isTooSmall(r)) {
|
|
DISPLAY("capacity %u < frameSize %u : %s \n", (unsigned)capacity, (unsigned)refSize,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int testSmallDstFrame(U32 seed)
|
|
{
|
|
/* source layout : zeroes, then compressible noise, then incompressible noise */
|
|
size_t const zeroSize = 1 KB;
|
|
size_t const noiseSize = 136 KB;
|
|
size_t const randSize = 72 KB;
|
|
size_t const srcBufSize = zeroSize + noiseSize + randSize;
|
|
struct { size_t pos; size_t size; } const inputs[] = {
|
|
{ 0, 0 }, /* empty frame */
|
|
{ 0, 1 }, /* single byte */
|
|
{ 1 KB, 12 }, /* too short to be compressed */
|
|
{ 0, 300 }, /* small, highly compressible */
|
|
{ 1 KB, 5000 }, /* one compressed block */
|
|
{ 137 KB, 70 KB }, /* incompressible : raw blocks */
|
|
{ 1 KB, 136 KB }, /* several compressed blocks */
|
|
{ 105 KB, 100 KB } /* compressed, then raw blocks */
|
|
};
|
|
int const levels[] = { -1, 1, 2, 3, 10 }; /* fast, accelerated, mid, hash chain, optimal */
|
|
size_t const nbInputs = sizeof(inputs) / sizeof(inputs[0]);
|
|
size_t const nbLevels = sizeof(levels) / sizeof(levels[0]);
|
|
size_t const dictSize = 32 KB;
|
|
size_t buffSize;
|
|
BYTE* const src = (BYTE*)malloc(srcBufSize);
|
|
BYTE* ref = NULL;
|
|
BYTE* dst = NULL;
|
|
BYTE* decoded = NULL;
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_CDict* cdict = NULL;
|
|
size_t ends[SD_ELTS_MAX];
|
|
size_t in, lv;
|
|
unsigned blockMode;
|
|
int result = 1;
|
|
|
|
{ LZ4F_preferences_t worstPrefs = LZ4F_INIT_PREFERENCES;
|
|
worstPrefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
worstPrefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
worstPrefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
buffSize = LZ4F_compressFrameBound(srcBufSize, &worstPrefs) + SD_CANARY_SIZE;
|
|
}
|
|
ref = (BYTE*)malloc(buffSize);
|
|
dst = (BYTE*)malloc(buffSize);
|
|
decoded = (BYTE*)malloc(srcBufSize);
|
|
SD_CHECK(src != NULL && ref != NULL && dst != NULL && decoded != NULL, "allocation failure");
|
|
memset(src, 0, zeroSize);
|
|
FUZ_fillCompressibleNoiseBuffer(src + zeroSize, noiseSize, 0.5, &seed);
|
|
{ size_t n;
|
|
for (n = zeroSize + noiseSize; n < srcBufSize; n++) src[n] = (BYTE)FUZ_rand(&seed);
|
|
}
|
|
cdict = LZ4F_createCDict(src + zeroSize + 64 KB, dictSize);
|
|
SD_CHECK(cdict != NULL, "LZ4F_createCDict failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
|
|
for (in = 0; in < nbInputs; in++)
|
|
for (lv = 0; lv < nbLevels; lv++)
|
|
for (blockMode = 0; blockMode < 2; blockMode++) {
|
|
unsigned const configNb = (unsigned)((in * nbLevels + lv) * 2 + blockMode);
|
|
unsigned const flags = FUZ_rand(&seed);
|
|
unsigned const api = configNb % 3; /* 0: compressFrame; 1: usingCDict(NULL); 2: usingCDict(cdict) */
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
sd_frameJob job;
|
|
size_t bound, refSize, nbEnds, n;
|
|
|
|
assert(inputs[in].pos + inputs[in].size <= srcBufSize);
|
|
prefs.frameInfo.blockMode = (LZ4F_blockMode_t)blockMode;
|
|
prefs.frameInfo.blockSizeID = ((flags >> 6) & 7) == 1 ? LZ4F_max256KB : LZ4F_max64KB;
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)(flags & 1);
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)((flags >> 1) & 1);
|
|
prefs.frameInfo.contentSize = (flags >> 2) & 1; /* any value != 0 => auto-corrected */
|
|
prefs.frameInfo.dictID = ((flags >> 3) & 1) ? 0x1817 : 0;
|
|
prefs.favorDecSpeed = (flags >> 4) & 1;
|
|
prefs.compressionLevel = levels[lv];
|
|
job.usingCDict = (api != 0);
|
|
job.cctx = cctx;
|
|
job.cdict = (api == 2) ? cdict : NULL;
|
|
job.prefs = &prefs;
|
|
job.src = src + inputs[in].pos;
|
|
job.srcSize = inputs[in].size;
|
|
|
|
DISPLAYLEVEL(4, "\ninput %u, level %i, blockMode %u, flags %02X, api %u : ",
|
|
(unsigned)in, levels[lv], blockMode, flags & 0x1FF, api);
|
|
|
|
/* reference, using a bound-sized buffer */
|
|
bound = LZ4F_compressFrameBound(job.srcSize, &prefs);
|
|
SD_CHECK(bound + SD_CANARY_SIZE <= buffSize, "test buffer too small");
|
|
refSize = sd_compressFrame(&job, ref, bound);
|
|
SD_CHECK(!LZ4F_isError(refSize), "%s", LZ4F_getErrorName(refSize));
|
|
SD_CHECK(sd_decodesTo(ref, refSize, job.src, job.srcSize,
|
|
job.cdict ? src + zeroSize + 64 KB : NULL, job.cdict ? dictSize : 0,
|
|
decoded, srcBufSize), "reference frame is corrupted");
|
|
nbEnds = sd_frameElementEnds(ends, SD_ELTS_MAX, ref, refSize);
|
|
SD_CHECK(nbEnds > 0, "reference frame is malformed");
|
|
DISPLAYLEVEL(4, "%u bytes, %u elements ", (unsigned)refSize, (unsigned)nbEnds);
|
|
|
|
/* exact size, and anything larger, must succeed */
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, refSize, ref, refSize), "exact capacity");
|
|
if (bound > refSize)
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, refSize + 1 + FUZ_rand(&seed) % (bound - refSize), ref, refSize),
|
|
"capacity > frameSize");
|
|
|
|
/* anything smaller must fail */
|
|
if (refSize <= 300) {
|
|
for (n = 0; n < refSize; n++)
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, n, ref, refSize), "small capacity");
|
|
} else {
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, 0, ref, refSize), "zero capacity");
|
|
for (n = 0; n < nbEnds; n++) /* just before the end of each element */
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, ends[n] - 1, ref, refSize), "element %u", (unsigned)n);
|
|
n = FUZ_rand(&seed) % (nbEnds - 1); /* exactly at the end of an element (but the last one) */
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, ends[n], ref, refSize), "end of element %u", (unsigned)n);
|
|
SD_CHECK(sd_frameCapacityOK(&job, dst, FUZ_rand(&seed) % refSize, ref, refSize), "random capacity");
|
|
}
|
|
|
|
/* a context remains usable after a failure */
|
|
if (job.usingCDict) {
|
|
size_t r = LZ4F_compressFrame_usingCDict(cctx, dst, refSize / 2, job.src, job.srcSize, job.cdict, &prefs);
|
|
SD_CHECK(sd_isTooSmall(r), "%s", LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
r = LZ4F_compressFrame_usingCDict(cctx, dst, bound, job.src, job.srcSize, job.cdict, &prefs);
|
|
SD_CHECK(!LZ4F_isError(r), "after a failure : %s", LZ4F_getErrorName(r));
|
|
SD_CHECK(sd_decodesTo(dst, r, job.src, job.srcSize,
|
|
job.cdict ? src + zeroSize + 64 KB : NULL, job.cdict ? dictSize : 0,
|
|
decoded, srcBufSize), "frame after a failure is corrupted");
|
|
}
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(ref);
|
|
free(dst);
|
|
free(decoded);
|
|
LZ4F_freeCDict(cdict);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Streaming : LZ4F_compressBegin*(), LZ4F_compressUpdate(),
|
|
* LZ4F_uncompressedUpdate(), LZ4F_flush(), LZ4F_compressEnd() */
|
|
|
|
typedef enum { sd_begin, sd_update, sd_uncompressedUpdate, sd_flush, sd_end } sd_opType_e;
|
|
|
|
typedef struct {
|
|
sd_opType_e type;
|
|
size_t srcPos;
|
|
size_t srcSize;
|
|
} sd_op;
|
|
|
|
typedef struct {
|
|
LZ4F_cctx* cctx;
|
|
const LZ4F_preferences_t* prefs;
|
|
LZ4F_compressOptions_t cOpts;
|
|
const BYTE* src;
|
|
unsigned beginMode; /* 0: plain; 1: usingDict; 2: usingCDict */
|
|
const void* dict;
|
|
size_t dictSize;
|
|
const LZ4F_CDict* cdict;
|
|
} sd_stream;
|
|
|
|
static size_t sd_runOp(const sd_stream* s, const sd_op* op, void* dst, size_t dstCapacity)
|
|
{
|
|
switch (op->type) {
|
|
case sd_begin:
|
|
if (s->beginMode == 1)
|
|
return LZ4F_compressBegin_usingDict(s->cctx, dst, dstCapacity, s->dict, s->dictSize, s->prefs);
|
|
if (s->beginMode == 2)
|
|
return LZ4F_compressBegin_usingCDict(s->cctx, dst, dstCapacity, s->cdict, s->prefs);
|
|
return LZ4F_compressBegin(s->cctx, dst, dstCapacity, s->prefs);
|
|
case sd_update:
|
|
return LZ4F_compressUpdate(s->cctx, dst, dstCapacity, s->src + op->srcPos, op->srcSize, &s->cOpts);
|
|
case sd_uncompressedUpdate:
|
|
return LZ4F_uncompressedUpdate(s->cctx, dst, dstCapacity, s->src + op->srcPos, op->srcSize, &s->cOpts);
|
|
case sd_flush:
|
|
return LZ4F_flush(s->cctx, dst, dstCapacity, &s->cOpts);
|
|
case sd_end:
|
|
return LZ4F_compressEnd(s->cctx, dst, dstCapacity, &s->cOpts);
|
|
}
|
|
return (size_t)-1; /* error */
|
|
}
|
|
|
|
static size_t sd_opBound(const sd_stream* s, const sd_op* op)
|
|
{
|
|
switch (op->type) {
|
|
case sd_begin: return LZ4F_HEADER_SIZE_MAX;
|
|
case sd_update:
|
|
case sd_uncompressedUpdate: return LZ4F_compressBound(op->srcSize, s->prefs);
|
|
case sd_flush:
|
|
case sd_end: return LZ4F_compressBound(0, s->prefs);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char* sd_opName(sd_opType_e type)
|
|
{
|
|
switch (type) {
|
|
case sd_begin: return "begin";
|
|
case sd_update: return "compressUpdate";
|
|
case sd_uncompressedUpdate: return "uncompressedUpdate";
|
|
case sd_flush: return "flush";
|
|
case sd_end: return "compressEnd";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
/* sd_genScript() :
|
|
* begin, then a random sequence of updates and flushes, then end.
|
|
* Input is consumed sequentially from position 0.
|
|
* @return : nb of operations */
|
|
static size_t sd_genScript(sd_op* ops, size_t maxOps, size_t blockSize, int allowUncompressed,
|
|
size_t srcCapacity, size_t* totalSrcSize, U32* rand)
|
|
{
|
|
size_t const nbMiddle = FUZ_rand(rand) % (maxOps - 1);
|
|
size_t nbOps = 0, pos = 0, n;
|
|
assert(maxOps >= 2);
|
|
ops[nbOps].type = sd_begin; ops[nbOps].srcPos = 0; ops[nbOps].srcSize = 0; nbOps++;
|
|
for (n = 0; n < nbMiddle; n++) {
|
|
unsigned const t = FUZ_rand(rand) % 8;
|
|
size_t size = 0;
|
|
sd_op* const op = ops + nbOps++;
|
|
op->type = (t == 0) ? sd_flush : ((t <= 2) && allowUncompressed) ? sd_uncompressedUpdate : sd_update;
|
|
if (op->type != sd_flush) {
|
|
switch (FUZ_rand(rand) % 8) {
|
|
case 0: size = 0; break;
|
|
case 1: size = 1; break;
|
|
case 2: size = 2 + FUZ_rand(rand) % 100; break;
|
|
case 3: size = blockSize - 1; break;
|
|
case 4: size = blockSize; break;
|
|
case 5: size = blockSize + 1; break;
|
|
case 6: size = FUZ_rand(rand) % blockSize; break;
|
|
default: size = 2 * blockSize + FUZ_rand(rand) % blockSize; break;
|
|
}
|
|
size = MIN(size, srcCapacity - pos);
|
|
}
|
|
op->srcPos = pos;
|
|
op->srcSize = size;
|
|
pos += size;
|
|
}
|
|
ops[nbOps].type = sd_end; ops[nbOps].srcPos = pos; ops[nbOps].srcSize = 0; nbOps++;
|
|
*totalSrcSize = pos;
|
|
return nbOps;
|
|
}
|
|
|
|
/* sd_opExactOK() :
|
|
* runs @op into exactly @refSize bytes (or @capacity if larger),
|
|
* and checks it produces the same output as reference.
|
|
* @return 1 if OK, 0 otherwise */
|
|
static int sd_opExactOK(const sd_stream* s, const sd_op* op, BYTE* dst, size_t capacity,
|
|
const BYTE* ref, size_t refSize)
|
|
{
|
|
size_t r;
|
|
assert(capacity >= refSize);
|
|
sd_setCanary(dst + capacity);
|
|
r = sd_runOp(s, op, dst, capacity);
|
|
if (!sd_canaryIntact(dst + capacity)) {
|
|
DISPLAY("%s (srcSize %u) into capacity %u : write beyond capacity \n",
|
|
sd_opName(op->type), (unsigned)op->srcSize, (unsigned)capacity);
|
|
return 0;
|
|
}
|
|
if (r != refSize) {
|
|
DISPLAY("%s (srcSize %u) into capacity %u (expected output %u) : %s \n",
|
|
sd_opName(op->type), (unsigned)op->srcSize, (unsigned)capacity, (unsigned)refSize,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "different output size");
|
|
return 0;
|
|
}
|
|
if (memcmp(dst, ref, refSize)) {
|
|
DISPLAY("%s (srcSize %u) into capacity %u : different output content \n",
|
|
sd_opName(op->type), (unsigned)op->srcSize, (unsigned)capacity);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* sd_runScript() :
|
|
* runs all @ops, each one using its bound as capacity, writing the frame into @frame.
|
|
* @opStarts and @opSizes are optional.
|
|
* @return : frame size, or an error code */
|
|
static size_t sd_runScript(const sd_stream* s, const sd_op* ops, size_t nbOps,
|
|
BYTE* frame, size_t frameCapacity,
|
|
size_t* opStarts, size_t* opSizes)
|
|
{
|
|
size_t n, pos = 0;
|
|
for (n = 0; n < nbOps; n++) {
|
|
size_t const bound = sd_opBound(s, ops + n);
|
|
size_t r;
|
|
if (pos + bound + SD_CANARY_SIZE > frameCapacity) {
|
|
DISPLAY("test buffer too small \n");
|
|
return (size_t)-1;
|
|
}
|
|
sd_setCanary(frame + pos + bound);
|
|
r = sd_runOp(s, ops + n, frame + pos, bound);
|
|
if (LZ4F_isError(r)) {
|
|
DISPLAY("op %u (%s, srcSize %u) : %s \n",
|
|
(unsigned)n, sd_opName(ops[n].type), (unsigned)ops[n].srcSize, LZ4F_getErrorName(r));
|
|
return r;
|
|
}
|
|
if (r > bound || !sd_canaryIntact(frame + pos + bound)) {
|
|
DISPLAY("op %u (%s, srcSize %u) : exceeds its bound \n",
|
|
(unsigned)n, sd_opName(ops[n].type), (unsigned)ops[n].srcSize);
|
|
return (size_t)-1;
|
|
}
|
|
if (opStarts) opStarts[n] = pos;
|
|
if (opSizes) opSizes[n] = r;
|
|
pos += r;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
#define SD_OPS_MAX 16
|
|
|
|
static int testSmallDstStream(U32 seed, unsigned nbConfigs)
|
|
{
|
|
size_t const srcBufSize = 192 KB;
|
|
size_t const dictSize = 32 KB;
|
|
int const levels[] = { -2, 1, 2, 3, 10 }; /* fast, accelerated, mid, hash chain, optimal */
|
|
BYTE* const src = (BYTE*)malloc(srcBufSize);
|
|
BYTE* const dictBuf = (BYTE*)malloc(dictSize);
|
|
BYTE* frame = NULL;
|
|
BYTE* dst = NULL;
|
|
BYTE* decoded = NULL;
|
|
size_t frameCapacity;
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_CDict* cdict = NULL;
|
|
sd_op ops[SD_OPS_MAX];
|
|
size_t opSizes[SD_OPS_MAX];
|
|
size_t opStarts[SD_OPS_MAX];
|
|
size_t ends[SD_ELTS_MAX];
|
|
unsigned configNb;
|
|
int result = 1;
|
|
|
|
{ LZ4F_preferences_t worstPrefs = LZ4F_INIT_PREFERENCES;
|
|
worstPrefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
worstPrefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
worstPrefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
/* generous : each op can add a partial block */
|
|
frameCapacity = LZ4F_compressFrameBound(srcBufSize, &worstPrefs)
|
|
+ SD_OPS_MAX * LZ4F_compressBound(0, &worstPrefs) + SD_CANARY_SIZE;
|
|
}
|
|
frame = (BYTE*)malloc(frameCapacity);
|
|
dst = (BYTE*)malloc(frameCapacity);
|
|
decoded = (BYTE*)malloc(srcBufSize);
|
|
SD_CHECK(src != NULL && dictBuf != NULL && frame != NULL && dst != NULL && decoded != NULL,
|
|
"allocation failure");
|
|
/* alternate compressible and incompressible segments */
|
|
{ size_t pos;
|
|
for (pos = 0; pos < srcBufSize; pos += 64 KB) {
|
|
size_t const segSize = MIN(64 KB, srcBufSize - pos);
|
|
size_t const noiseSize = MIN(48 KB, segSize);
|
|
size_t n;
|
|
FUZ_fillCompressibleNoiseBuffer(src + pos, noiseSize, 0.5, &seed);
|
|
for (n = noiseSize; n < segSize; n++) src[pos + n] = (BYTE)FUZ_rand(&seed);
|
|
} }
|
|
FUZ_fillCompressibleNoiseBuffer(dictBuf, dictSize, 0.5, &seed);
|
|
cdict = LZ4F_createCDict(dictBuf, dictSize);
|
|
SD_CHECK(cdict != NULL, "LZ4F_createCDict failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
|
|
for (configNb = 0; configNb < nbConfigs; configNb++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
sd_stream s;
|
|
size_t blockSize, totalSrcSize, nbOps, refSize, nbEnds, n;
|
|
unsigned probe;
|
|
|
|
prefs.frameInfo.blockMode = (LZ4F_blockMode_t)(FUZ_rand(&seed) & 1);
|
|
prefs.frameInfo.blockSizeID = (FUZ_rand(&seed) % 8 == 0) ? LZ4F_max256KB : LZ4F_max64KB;
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)(FUZ_rand(&seed) & 1);
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)(FUZ_rand(&seed) & 1);
|
|
prefs.frameInfo.dictID = (FUZ_rand(&seed) & 1) ? 0x1817 : 0;
|
|
prefs.autoFlush = (FUZ_rand(&seed) % 4) == 0;
|
|
prefs.favorDecSpeed = FUZ_rand(&seed) & 1;
|
|
prefs.compressionLevel = levels[FUZ_rand(&seed) % (sizeof(levels) / sizeof(levels[0]))];
|
|
blockSize = LZ4F_getBlockSize(prefs.frameInfo.blockSizeID);
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
s.prefs = &prefs;
|
|
s.cOpts.stableSrc = FUZ_rand(&seed) & 1;
|
|
s.src = src;
|
|
s.beginMode = FUZ_rand(&seed) % 3;
|
|
s.dict = dictBuf;
|
|
s.dictSize = dictSize;
|
|
s.cdict = cdict;
|
|
|
|
nbOps = sd_genScript(ops, SD_OPS_MAX, blockSize,
|
|
prefs.frameInfo.blockMode == LZ4F_blockIndependent,
|
|
srcBufSize, &totalSrcSize, &seed);
|
|
if (FUZ_rand(&seed) & 1) prefs.frameInfo.contentSize = totalSrcSize;
|
|
|
|
DISPLAYLEVEL(4, "\nconfig %u : level %i, blockMode %u, blockSize %u, checksums %u-%u, "
|
|
"autoFlush %u, stableSrc %u, beginMode %u, %u ops, %u bytes ",
|
|
configNb, prefs.compressionLevel, prefs.frameInfo.blockMode, (unsigned)blockSize,
|
|
prefs.frameInfo.blockChecksumFlag, prefs.frameInfo.contentChecksumFlag,
|
|
prefs.autoFlush, s.cOpts.stableSrc, s.beginMode,
|
|
(unsigned)nbOps, (unsigned)totalSrcSize);
|
|
|
|
/* reference pass, each operation using its bound */
|
|
s.cctx = cctx;
|
|
refSize = sd_runScript(&s, ops, nbOps, frame, frameCapacity, opStarts, opSizes);
|
|
SD_CHECK(!LZ4F_isError(refSize), "config %u : reference pass", configNb);
|
|
SD_CHECK(sd_decodesTo(frame, refSize, src, totalSrcSize,
|
|
s.beginMode ? dictBuf : NULL, s.beginMode ? dictSize : 0,
|
|
decoded, srcBufSize), "config %u : reference frame is corrupted", configNb);
|
|
nbEnds = sd_frameElementEnds(ends, SD_ELTS_MAX, frame, refSize);
|
|
SD_CHECK(nbEnds > 0, "config %u : reference frame is malformed", configNb);
|
|
|
|
/* failure probes : replay up to op k, then give op k less than its output size */
|
|
for (probe = 0; probe < 4; probe++) {
|
|
size_t k = FUZ_rand(&seed) % nbOps;
|
|
size_t capacity, r;
|
|
while (opSizes[k] == 0) k = (k + 1) % nbOps; /* begin and end always produce output */
|
|
capacity = sd_pickSmallCapacity(ends, nbEnds, opStarts[k], opSizes[k], &seed);
|
|
for (n = 0; n < k; n++)
|
|
SD_CHECK(sd_opExactOK(&s, ops + n, dst, opSizes[n], frame + opStarts[n], opSizes[n]),
|
|
"config %u, probe %u : replay op %u", configNb, probe, (unsigned)n);
|
|
sd_setCanary(dst + capacity);
|
|
r = sd_runOp(&s, ops + k, dst, capacity);
|
|
SD_CHECK(sd_canaryIntact(dst + capacity), "config %u : op %u (%s) writes beyond capacity",
|
|
configNb, (unsigned)k, sd_opName(ops[k].type));
|
|
SD_CHECK(sd_isTooSmall(r), "config %u, op %u (%s, srcSize %u) into capacity %u < %u : %s",
|
|
configNb, (unsigned)k, sd_opName(ops[k].type), (unsigned)ops[k].srcSize,
|
|
(unsigned)capacity, (unsigned)opSizes[k],
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
/* the frame can't be continued anymore */
|
|
{ sd_op next;
|
|
next.type = (sd_opType_e)(sd_update + (probe % 4));
|
|
next.srcPos = 0;
|
|
next.srcSize = 1;
|
|
r = sd_runOp(&s, &next, dst, frameCapacity);
|
|
SD_CHECK(sd_isUninitialized(r), "config %u : %s after failed op %u (%s) : %s",
|
|
configNb, sd_opName(next.type), (unsigned)k, sd_opName(ops[k].type),
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
}
|
|
/* the context remains usable for a new frame */
|
|
if (probe == 0) {
|
|
r = sd_runScript(&s, ops, nbOps, dst, frameCapacity, NULL, NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "config %u : new frame after failed op %u", configNb, (unsigned)k);
|
|
SD_CHECK(sd_decodesTo(dst, r, src, totalSrcSize,
|
|
s.beginMode ? dictBuf : NULL, s.beginMode ? dictSize : 0,
|
|
decoded, srcBufSize),
|
|
"config %u : new frame after failed op %u is corrupted", configNb, (unsigned)k);
|
|
} }
|
|
|
|
/* exact pass : each operation into exactly its output size, sometimes more */
|
|
for (n = 0; n < nbOps; n++) {
|
|
size_t const bound = sd_opBound(&s, ops + n);
|
|
size_t capacity = opSizes[n];
|
|
if ((FUZ_rand(&seed) % 4 == 0) && (bound > opSizes[n]))
|
|
capacity += FUZ_rand(&seed) % (bound - opSizes[n]);
|
|
SD_CHECK(sd_opExactOK(&s, ops + n, dst, capacity, frame + opStarts[n], opSizes[n]),
|
|
"config %u : exact pass, op %u", configNb, (unsigned)n);
|
|
}
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(dictBuf);
|
|
free(frame);
|
|
free(dst);
|
|
free(decoded);
|
|
LZ4F_freeCDict(cdict);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* LZ4F_flush() and LZ4F_compressEnd() require a frame in progress */
|
|
static int testFlushOutsideFrame(void)
|
|
{
|
|
BYTE src[100];
|
|
BYTE dst[LZ4F_HEADER_SIZE_MAX + 200];
|
|
LZ4F_cctx* cctx = NULL;
|
|
size_t r;
|
|
int result = 1;
|
|
|
|
memset(src, 'a', sizeof(src));
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
r = LZ4F_flush(cctx, dst, sizeof(dst), NULL);
|
|
SD_CHECK(sd_isUninitialized(r), "flush on fresh context : %s",
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
r = LZ4F_compressEnd(cctx, dst, sizeof(dst), NULL);
|
|
SD_CHECK(sd_isUninitialized(r), "compressEnd on fresh context : %s",
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
|
|
r = LZ4F_compressBegin(cctx, dst, sizeof(dst), NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
r = LZ4F_compressUpdate(cctx, dst, sizeof(dst), src, sizeof(src), NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
r = LZ4F_compressEnd(cctx, dst, sizeof(dst), NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
|
|
r = LZ4F_flush(cctx, dst, sizeof(dst), NULL);
|
|
SD_CHECK(sd_isUninitialized(r), "flush after end : %s",
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
r = LZ4F_compressEnd(cctx, dst, sizeof(dst), NULL);
|
|
SD_CHECK(sd_isUninitialized(r), "compressEnd after end : %s",
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
result = 0;
|
|
|
|
_output_error:
|
|
LZ4F_freeCompressionContext(cctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* favorDecSpeed applies to all blocks of a frame, including after an incompressible block :
|
|
* with independent blocks, a block doesn't depend on the previous ones */
|
|
static int testFavorDecSpeedAfterRawBlock(U32 seed)
|
|
{
|
|
size_t const blockSize = 64 KB;
|
|
size_t const headerSize = 7; /* no content size, no dictID */
|
|
BYTE* const src = (BYTE*)malloc(2 * blockSize); /* incompressible block, then compressible block */
|
|
size_t const dstCapacity = LZ4F_compressFrameBound(2 * blockSize, NULL);
|
|
BYTE* const frame = (BYTE*)malloc(dstCapacity);
|
|
BYTE* const alone = (BYTE*)malloc(dstCapacity);
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
size_t frameSize, aloneSize, cBlockSize, n;
|
|
int result = 1;
|
|
|
|
SD_CHECK(src != NULL && frame != NULL && alone != NULL, "allocation failure");
|
|
for (n = 0; n < blockSize; n++) src[n] = (BYTE)FUZ_rand(&seed);
|
|
FUZ_fillCompressibleNoiseBuffer(src + blockSize, blockSize, 0.5, &seed);
|
|
prefs.frameInfo.blockMode = LZ4F_blockIndependent;
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.compressionLevel = 10; /* optimal parser : favorDecSpeed only applies there */
|
|
|
|
/* this test is only meaningful if favorDecSpeed changes the compressible block */
|
|
frameSize = LZ4F_compressFrame(frame, dstCapacity, src + blockSize, blockSize, &prefs);
|
|
SD_CHECK(!LZ4F_isError(frameSize), "%s", LZ4F_getErrorName(frameSize));
|
|
prefs.favorDecSpeed = 1;
|
|
aloneSize = LZ4F_compressFrame(alone, dstCapacity, src + blockSize, blockSize, &prefs);
|
|
SD_CHECK(!LZ4F_isError(aloneSize), "%s", LZ4F_getErrorName(aloneSize));
|
|
SD_CHECK(frameSize != aloneSize || memcmp(frame, alone, aloneSize),
|
|
"favorDecSpeed doesn't change the output of this test input");
|
|
|
|
/* the same block, after an incompressible one, must be identical */
|
|
frameSize = LZ4F_compressFrame(frame, dstCapacity, src, 2 * blockSize, &prefs);
|
|
SD_CHECK(!LZ4F_isError(frameSize), "%s", LZ4F_getErrorName(frameSize));
|
|
SD_CHECK(FUZ_readLE32(frame + headerSize) == (0x80000000U | (U32)blockSize), "first block should be stored raw");
|
|
cBlockSize = 4 + (FUZ_readLE32(alone + headerSize) & 0x7FFFFFFFU); /* block header included */
|
|
SD_CHECK(frameSize == headerSize + (4 + blockSize) + cBlockSize + 4
|
|
&& !memcmp(frame + headerSize + 4 + blockSize, alone + headerSize, cBlockSize),
|
|
"block compressed after an incompressible block differs (favorDecSpeed lost ?)");
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(frame);
|
|
free(alone);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* compress a whole frame, started with the LZ4F_compressBegin*() variant selected by @beginMode */
|
|
static size_t compressFrameWithDict(LZ4F_cctx* cctx, BYTE* dst, size_t dstCapacity,
|
|
const BYTE* src, size_t srcSize,
|
|
unsigned beginMode, const BYTE* dict, size_t dictSize, const LZ4F_CDict* cdict,
|
|
const LZ4F_preferences_t* prefs)
|
|
{
|
|
size_t pos, r;
|
|
switch (beginMode) {
|
|
case 0: r = LZ4F_compressBegin(cctx, dst, dstCapacity, prefs); break;
|
|
case 1: r = LZ4F_compressBegin_usingDict(cctx, dst, dstCapacity, dict, dictSize, prefs); break;
|
|
default: r = LZ4F_compressBegin_usingCDict(cctx, dst, dstCapacity, cdict, prefs); break;
|
|
}
|
|
if (LZ4F_isError(r)) return r;
|
|
pos = r;
|
|
r = LZ4F_compressUpdate(cctx, dst + pos, dstCapacity - pos, src, srcSize, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
pos += r;
|
|
r = LZ4F_compressEnd(cctx, dst + pos, dstCapacity - pos, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
return pos + r;
|
|
}
|
|
|
|
/* favorDecSpeed applies to frames started with a dictionary :
|
|
* neither loading a dictionary (LZ4F_compressBegin_usingDict())
|
|
* nor attaching a CDict (LZ4F_compressBegin_usingCDict()) may reset it */
|
|
static int testFavorDecSpeedWithDict(U32 seed)
|
|
{
|
|
size_t const dictSize = 64 KB;
|
|
size_t const srcSize = 64 KB;
|
|
BYTE* const dict = (BYTE*)malloc(dictSize);
|
|
BYTE* const src = (BYTE*)malloc(srcSize);
|
|
size_t const dstCapacity = LZ4F_compressFrameBound(srcSize, NULL) + LZ4F_HEADER_SIZE_MAX;
|
|
BYTE* const ref = (BYTE*)malloc(dstCapacity);
|
|
BYTE* const fds = (BYTE*)malloc(dstCapacity);
|
|
BYTE* const decoded = (BYTE*)malloc(srcSize);
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_CDict* cdict = NULL;
|
|
unsigned blockMode, beginMode;
|
|
int result = 1;
|
|
|
|
SD_CHECK(dict != NULL && src != NULL && ref != NULL && fds != NULL && decoded != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(dict, dictSize, 0.5, &seed);
|
|
FUZ_fillCompressibleNoiseBuffer(src, srcSize, 0.5, &seed);
|
|
cdict = LZ4F_createCDict(dict, dictSize);
|
|
SD_CHECK(cdict != NULL, "LZ4F_createCDict failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
|
|
/* beginMode 0 (no dictionary) checks this test input is sensitive to favorDecSpeed */
|
|
for (blockMode = 0; blockMode < 2; blockMode++)
|
|
for (beginMode = 0; beginMode < 3; beginMode++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
size_t refSize, fdsSize;
|
|
prefs.frameInfo.blockMode = blockMode ? LZ4F_blockIndependent : LZ4F_blockLinked;
|
|
prefs.compressionLevel = 10; /* optimal parser : favorDecSpeed only applies there */
|
|
refSize = compressFrameWithDict(cctx, ref, dstCapacity, src, srcSize, beginMode, dict, dictSize, cdict, &prefs);
|
|
SD_CHECK(!LZ4F_isError(refSize), "blockMode=%u, beginMode=%u : %s", blockMode, beginMode, LZ4F_getErrorName(refSize));
|
|
prefs.favorDecSpeed = 1;
|
|
fdsSize = compressFrameWithDict(cctx, fds, dstCapacity, src, srcSize, beginMode, dict, dictSize, cdict, &prefs);
|
|
SD_CHECK(!LZ4F_isError(fdsSize), "blockMode=%u, beginMode=%u : %s", blockMode, beginMode, LZ4F_getErrorName(fdsSize));
|
|
SD_CHECK(refSize != fdsSize || memcmp(ref, fds, fdsSize),
|
|
"blockMode=%u, beginMode=%u : favorDecSpeed doesn't change the output (lost ?)", blockMode, beginMode);
|
|
SD_CHECK(sd_decodesTo(fds, fdsSize, src, srcSize, beginMode ? dict : NULL, beginMode ? dictSize : 0, decoded, srcSize),
|
|
"blockMode=%u, beginMode=%u : frame corrupted", blockMode, beginMode);
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(dict);
|
|
free(src);
|
|
free(ref);
|
|
free(fds);
|
|
free(decoded);
|
|
LZ4F_freeCDict(cdict);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Reproducibility : frames don't depend on the context's history */
|
|
|
|
/* compresses @src with LZ4F_compressFrame_usingCDict() if @chunkSize == 0,
|
|
* otherwise streams it by chunks of @chunkSize bytes.
|
|
* @return : frame size, or an error code */
|
|
static size_t rf_compressFrame(LZ4F_cctx* cctx, BYTE* dst, size_t dstCapacity,
|
|
const BYTE* src, size_t srcSize, size_t chunkSize,
|
|
const LZ4F_CDict* cdict, const LZ4F_preferences_t* prefs)
|
|
{
|
|
size_t pos, n, r;
|
|
if (chunkSize == 0)
|
|
return LZ4F_compressFrame_usingCDict(cctx, dst, dstCapacity, src, srcSize, cdict, prefs);
|
|
pos = LZ4F_compressBegin_usingCDict(cctx, dst, dstCapacity, cdict, prefs);
|
|
if (LZ4F_isError(pos)) return pos;
|
|
for (n = 0; n < srcSize; n += chunkSize) {
|
|
r = LZ4F_compressUpdate(cctx, dst + pos, dstCapacity - pos, src + n, MIN(chunkSize, srcSize - n), NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
pos += r;
|
|
}
|
|
r = LZ4F_compressEnd(cctx, dst + pos, dstCapacity - pos, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
return pos + r;
|
|
}
|
|
|
|
static int testReproducibleFrames(U32 seed)
|
|
{
|
|
size_t const srcSize = 100 KB; /* > 64 KB blocks : LZ4F_compressFrame*() keeps blocks linked */
|
|
size_t const otherSize = 64 KB; /* history frames, and dictionary */
|
|
size_t const dictSize = 16 KB;
|
|
int const levels[] = { -3, 1, 2, 9, 10 }; /* accelerated, fast, mid, hash chain, optimal */
|
|
size_t const chunkSizes[] = { 0, 3000, 70 KB };
|
|
size_t const nbLevels = sizeof(levels) / sizeof(levels[0]);
|
|
size_t const nbChunkSizes = sizeof(chunkSizes) / sizeof(chunkSizes[0]);
|
|
BYTE* const src = (BYTE*)malloc(srcSize);
|
|
BYTE* const other = (BYTE*)malloc(otherSize);
|
|
BYTE* ref = NULL;
|
|
BYTE* dst = NULL;
|
|
BYTE* decoded = NULL;
|
|
size_t dstCapacity;
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_CDict* cdict = NULL;
|
|
size_t lv, ch, variant;
|
|
int result = 1;
|
|
|
|
{ LZ4F_preferences_t worstPrefs = LZ4F_INIT_PREFERENCES;
|
|
worstPrefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
worstPrefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
worstPrefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
dstCapacity = LZ4F_compressFrameBound(srcSize, &worstPrefs);
|
|
}
|
|
ref = (BYTE*)malloc(dstCapacity);
|
|
dst = (BYTE*)malloc(dstCapacity);
|
|
decoded = (BYTE*)malloc(srcSize);
|
|
SD_CHECK(src != NULL && other != NULL && ref != NULL && dst != NULL && decoded != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(src, srcSize, 0.5, &seed);
|
|
FUZ_fillCompressibleNoiseBuffer(other, otherSize, 0.5, &seed);
|
|
/* the first 4 bytes reappear, with a different 5th byte :
|
|
* a match only found when starting from a cleared table */
|
|
memcpy(src, "ABCDx", 5);
|
|
memcpy(src + 16, "ABCDy", 5);
|
|
cdict = LZ4F_createCDict(other, dictSize);
|
|
SD_CHECK(cdict != NULL, "LZ4F_createCDict failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
|
|
for (lv = 0; lv < nbLevels; lv++)
|
|
for (ch = 0; ch < nbChunkSizes; ch++)
|
|
for (variant = 0; variant < 8; variant++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
const LZ4F_CDict* const cd = (variant & 1) ? cdict : NULL;
|
|
size_t refSize, history;
|
|
prefs.compressionLevel = levels[lv];
|
|
prefs.frameInfo.blockMode = (LZ4F_blockMode_t)((variant >> 1) & 1);
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.autoFlush = (variant >> 2) & 1;
|
|
DISPLAYLEVEL(4, "\nlevel %i, chunks %u, blockMode %u, autoFlush %u, cdict %u : ", prefs.compressionLevel,
|
|
(unsigned)chunkSizes[ch], prefs.frameInfo.blockMode, prefs.autoFlush, cd != NULL);
|
|
|
|
/* reference : a new context */
|
|
{ LZ4F_cctx* newCctx = NULL;
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&newCctx, LZ4F_VERSION)), "cctx creation failure");
|
|
refSize = rf_compressFrame(newCctx, ref, dstCapacity, src, srcSize, chunkSizes[ch], cd, &prefs);
|
|
LZ4F_freeCompressionContext(newCctx);
|
|
}
|
|
SD_CHECK(!LZ4F_isError(refSize), "reference : %s", LZ4F_getErrorName(refSize));
|
|
SD_CHECK(sd_decodesTo(ref, refSize, src, srcSize, cd ? other : NULL, cd ? dictSize : 0, decoded, srcSize),
|
|
"reference frame is corrupted");
|
|
|
|
/* the same frame, on a context with various histories */
|
|
for (history = 0; history < 4; history++) {
|
|
LZ4F_preferences_t hPrefs = LZ4F_INIT_PREFERENCES;
|
|
size_t r;
|
|
switch (history) {
|
|
case 0: break; /* whatever the previous frame was */
|
|
case 1: hPrefs.compressionLevel = 1; break; /* fast, linked blocks */
|
|
case 2: hPrefs.compressionLevel = 9; break; /* switch to HC */
|
|
default: hPrefs.frameInfo.blockMode = LZ4F_blockIndependent; break; /* small table type */
|
|
}
|
|
if (history) {
|
|
r = rf_compressFrame(cctx, dst, dstCapacity, other, otherSize, 1000, NULL, &hPrefs);
|
|
SD_CHECK(!LZ4F_isError(r), "history frame %u : %s", (unsigned)history, LZ4F_getErrorName(r));
|
|
}
|
|
r = rf_compressFrame(cctx, dst, dstCapacity, src, srcSize, chunkSizes[ch], cd, &prefs);
|
|
SD_CHECK(!LZ4F_isError(r), "after history %u : %s", (unsigned)history, LZ4F_getErrorName(r));
|
|
SD_CHECK(r == refSize && !memcmp(dst, ref, refSize),
|
|
"level %i, chunks %u, blockMode %u, autoFlush %u, cdict %u : "
|
|
"after history %u, frame differs from a new context's (%u vs %u bytes)",
|
|
prefs.compressionLevel, (unsigned)chunkSizes[ch], prefs.frameInfo.blockMode, prefs.autoFlush,
|
|
cd != NULL, (unsigned)history, (unsigned)r, (unsigned)refSize);
|
|
}
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(other);
|
|
free(ref);
|
|
free(dst);
|
|
free(decoded);
|
|
LZ4F_freeCDict(cdict);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* skipChecksums disables verification of all checksums :
|
|
* block checksums, of compressed and uncompressed blocks, and content checksum */
|
|
static int testSkipChecksums(U32 seed)
|
|
{
|
|
size_t const blockSize = 64 KB;
|
|
size_t const srcSize = 3 * blockSize; /* 2nd block is stored uncompressed */
|
|
BYTE* const src = (BYTE*)malloc(srcSize);
|
|
BYTE* const decoded = (BYTE*)malloc(srcSize);
|
|
BYTE* frame = NULL;
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
LZ4F_dctx* dctx = NULL;
|
|
size_t ends[SD_ELTS_MAX];
|
|
size_t frameCapacity, frameSize, n;
|
|
unsigned corrupted;
|
|
int result = 1;
|
|
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
frameCapacity = LZ4F_compressFrameBound(srcSize, &prefs);
|
|
frame = (BYTE*)malloc(frameCapacity);
|
|
SD_CHECK(src != NULL && decoded != NULL && frame != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(src, srcSize, 0.5, &seed);
|
|
for (n = blockSize; n < 2 * blockSize; n++) src[n] = (BYTE)FUZ_rand(&seed);
|
|
frameSize = LZ4F_compressFrame(frame, frameCapacity, src, srcSize, &prefs);
|
|
SD_CHECK(!LZ4F_isError(frameSize), "%s", LZ4F_getErrorName(frameSize));
|
|
SD_CHECK(sd_frameElementEnds(ends, SD_ELTS_MAX, frame, frameSize) == 6, "malformed frame"); /* header, 3 blocks, endMark, checksum */
|
|
SD_CHECK(!(FUZ_readLE32(frame + ends[0]) & 0x80000000U) && (FUZ_readLE32(frame + ends[1]) & 0x80000000U),
|
|
"1st block should be compressed, and 2nd block uncompressed");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION)), "dctx creation failure");
|
|
|
|
for (corrupted = 0; corrupted < 3; corrupted++) { /* checksum of : compressed block, uncompressed block, content */
|
|
size_t const crcPos = (corrupted == 2) ? frameSize - 1 : ends[corrupted + 1] - 1;
|
|
LZ4F_errorCodes const expected = (corrupted == 2) ? LZ4F_ERROR_contentChecksum_invalid : LZ4F_ERROR_blockChecksum_invalid;
|
|
LZ4F_decompressOptions_t dOpt;
|
|
size_t srcLen = frameSize, dstLen = srcSize, r;
|
|
frame[crcPos] ^= 1;
|
|
LZ4F_resetDecompressionContext(dctx);
|
|
r = LZ4F_decompress(dctx, decoded, &dstLen, frame, &srcLen, NULL);
|
|
SD_CHECK(LZ4F_getErrorCode(r) == expected, "checksum %u : %s", corrupted,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "corruption not detected");
|
|
memset(&dOpt, 0, sizeof(dOpt));
|
|
dOpt.skipChecksums = 1;
|
|
srcLen = frameSize;
|
|
dstLen = srcSize;
|
|
LZ4F_resetDecompressionContext(dctx);
|
|
r = LZ4F_decompress(dctx, decoded, &dstLen, frame, &srcLen, &dOpt);
|
|
SD_CHECK(r == 0 && srcLen == frameSize && dstLen == srcSize && !memcmp(decoded, src, srcSize),
|
|
"checksum %u : %s", corrupted, LZ4F_isError(r) ? LZ4F_getErrorName(r) : "not skipped");
|
|
frame[crcPos] ^= 1;
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(decoded);
|
|
free(frame);
|
|
LZ4F_freeDecompressionContext(dctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* Random access within a frame (#1304) :
|
|
* a first pass, directed by hints, records the start of each block, within the frame and within decoded data.
|
|
* Decoding can later resume from any block start.
|
|
* testRandomAccess() checks the recipe used by Wireshark, which works since v1.10.0, using a reset state :
|
|
* the frame header is decoded again with LZ4F_getFrameInfo(),
|
|
* the last 64 KB decoded before the block are provided with LZ4F_decompress_usingDict() (linked blocks only),
|
|
* and checksums are skipped, since they can't be verified from the middle of a frame.
|
|
* Note : frames declaring their content size can't be resumed this way.
|
|
* testResumeFrame() checks LZ4F_resumeFrame(), which supports all frames. */
|
|
|
|
/* ra_expectedHint() :
|
|
* @ends : frame elements, as listed by sd_frameElementEnds(). ends[@nbBlocks] is the start of endMark.
|
|
* @return : hint expected after consuming @pos bytes of the frame,
|
|
* up to the end of next block header, or up to the end of frame once endMark is consumed */
|
|
static size_t ra_expectedHint(const size_t* ends, size_t nbBlocks, size_t frameSize, size_t pos)
|
|
{
|
|
size_t k;
|
|
for (k = 0; k <= nbBlocks; k++)
|
|
if (ends[k] + 4 > pos) return ends[k] + 4 - pos;
|
|
return frameSize - pos;
|
|
}
|
|
|
|
/* ra_indexFrame() :
|
|
* decodes @frame into @decoded, providing exactly the hinted amount of input,
|
|
* which stops decoding right after each block header, and checks hints along the way.
|
|
* Records into @dStarts the position of each block start within decoded data,
|
|
* and into @frameInfo the frame parameters.
|
|
* @dict : the dictionary used to compress the frame, or NULL.
|
|
* @return : 0 on success */
|
|
static int ra_indexFrame(LZ4F_dctx* dctx, LZ4F_frameInfo_t* frameInfo,
|
|
const BYTE* frame, size_t frameSize, const size_t* ends, size_t nbBlocks,
|
|
const void* dict, size_t dictSize,
|
|
BYTE* decoded, size_t decodedSize, size_t* dStarts)
|
|
{
|
|
size_t const hSize = ends[0];
|
|
size_t pos = hSize, dPos = 0, k, r;
|
|
LZ4F_resetDecompressionContext(dctx);
|
|
r = LZ4F_getFrameInfo(dctx, frameInfo, frame, &pos);
|
|
SD_CHECK(r == 4 && pos == hSize, "%s", LZ4F_isError(r) ? LZ4F_getErrorName(r) : "wrong header");
|
|
for (k = 0; r != 0; k++) {
|
|
LZ4F_frameInfo_t fi;
|
|
size_t srcLen = r, dstLen = decodedSize - dPos, noInput = 0;
|
|
r = LZ4F_decompress_usingDict(dctx, decoded + dPos, &dstLen, frame + pos, &srcLen, dict, dictSize, NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "%s", LZ4F_getErrorName(r));
|
|
pos += srcLen;
|
|
dPos += dstLen;
|
|
SD_CHECK(r == ra_expectedHint(ends, nbBlocks, frameSize, pos), "pos %u : wrong hint %u", (unsigned)pos, (unsigned)r);
|
|
if (r == 0) break;
|
|
SD_CHECK(k <= nbBlocks && pos == ends[k] + 4, "pos %u : not after a block header", (unsigned)pos);
|
|
dStarts[k] = dPos;
|
|
/* LZ4F_getFrameInfo() mid-frame provides the same hint */
|
|
SD_CHECK(LZ4F_getFrameInfo(dctx, &fi, NULL, &noInput) == r, "pos %u : LZ4F_getFrameInfo() provides a different hint",
|
|
(unsigned)pos);
|
|
}
|
|
SD_CHECK(pos == frameSize && dPos == decodedSize, "frame not fully decoded");
|
|
return 0;
|
|
|
|
_output_error:
|
|
return 1;
|
|
}
|
|
|
|
static int testRandomAccess(U32 seed)
|
|
{
|
|
size_t const blockSize = 64 KB;
|
|
size_t const srcSize = 5 * blockSize + 1000; /* last block is partial */
|
|
BYTE* const src = (BYTE*)malloc(srcSize);
|
|
BYTE* const decoded = (BYTE*)malloc(srcSize);
|
|
BYTE* frame = NULL;
|
|
size_t frameCapacity;
|
|
LZ4F_dctx* dctx = NULL;
|
|
unsigned variant, pass;
|
|
size_t n;
|
|
int result = 1;
|
|
|
|
{ LZ4F_preferences_t worstPrefs = LZ4F_INIT_PREFERENCES;
|
|
worstPrefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
worstPrefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
worstPrefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
frameCapacity = LZ4F_compressFrameBound(srcSize, &worstPrefs);
|
|
}
|
|
frame = (BYTE*)malloc(frameCapacity);
|
|
SD_CHECK(src != NULL && decoded != NULL && frame != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(src, srcSize, 0.5, &seed);
|
|
for (n = 2 * blockSize; n < 3 * blockSize; n++) src[n] = (BYTE)FUZ_rand(&seed); /* 3rd block is stored uncompressed */
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION)), "dctx creation failure");
|
|
|
|
for (variant = 0; variant < 8; variant++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
LZ4F_frameInfo_t fi;
|
|
size_t ends[SD_ELTS_MAX], dStarts[SD_ELTS_MAX];
|
|
size_t frameSize, nbElts, nbBlocks, hSize, pos, dPos, k, r;
|
|
prefs.frameInfo.blockMode = (LZ4F_blockMode_t)(variant & 1);
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)((variant >> 1) & 1);
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)((variant >> 2) & 1);
|
|
frameSize = LZ4F_compressFrame(frame, frameCapacity, src, srcSize, &prefs);
|
|
SD_CHECK(!LZ4F_isError(frameSize), "variant %u : %s", variant, LZ4F_getErrorName(frameSize));
|
|
nbElts = sd_frameElementEnds(ends, SD_ELTS_MAX, frame, frameSize);
|
|
SD_CHECK(nbElts > 0, "variant %u : malformed frame", variant);
|
|
nbBlocks = nbElts - 2 - prefs.frameInfo.contentChecksumFlag; /* minus header, endMark, and content checksum */
|
|
hSize = ends[0];
|
|
|
|
/* first pass : records block starts */
|
|
SD_CHECK(!ra_indexFrame(dctx, &fi, frame, frameSize, ends, nbBlocks, NULL, 0, decoded, srcSize, dStarts),
|
|
"variant %u : first pass failed", variant);
|
|
SD_CHECK(!memcmp(decoded, src, srcSize), "variant %u : first pass corrupted", variant);
|
|
|
|
/* hints remain exact whatever the amount of input and output */
|
|
for (pass = 0; pass < 16; pass++) {
|
|
LZ4F_resetDecompressionContext(dctx);
|
|
pos = hSize;
|
|
r = LZ4F_getFrameInfo(dctx, &fi, frame, &pos);
|
|
SD_CHECK(!LZ4F_isError(r), "variant %u : %s", variant, LZ4F_getErrorName(r));
|
|
dPos = 0;
|
|
while (r != 0) {
|
|
size_t const cut = FUZ_rand(&seed) % 12; /* stops within block checksums and headers */
|
|
size_t srcLen = (FUZ_rand(&seed) & 1) ? 1 + FUZ_rand(&seed) % r : (cut < r ? r - cut : 1);
|
|
size_t dstLen = (FUZ_rand(&seed) & 1) ? srcSize - dPos : FUZ_rand(&seed) % (srcSize - dPos + 1);
|
|
r = LZ4F_decompress(dctx, decoded + dPos, &dstLen, frame + pos, &srcLen, NULL);
|
|
SD_CHECK(!LZ4F_isError(r), "variant %u : %s", variant, LZ4F_getErrorName(r));
|
|
pos += srcLen;
|
|
dPos += dstLen;
|
|
SD_CHECK(r == ra_expectedHint(ends, nbBlocks, frameSize, pos),
|
|
"variant %u, pos %u : wrong hint %u", variant, (unsigned)pos, (unsigned)r);
|
|
}
|
|
SD_CHECK(pos == frameSize && dPos == srcSize && !memcmp(decoded, src, srcSize), "variant %u : decoding corrupted", variant);
|
|
}
|
|
|
|
/* resume from each block start */
|
|
for (k = 0; k < nbBlocks; k++) {
|
|
size_t const dictSize = (prefs.frameInfo.blockMode == LZ4F_blockLinked) ? MIN(dStarts[k], 64 KB) : 0;
|
|
size_t hs = hSize, noSrc = 0, noDst = 0;
|
|
size_t srcLen = frameSize - ends[k], dstLen = srcSize - dStarts[k];
|
|
LZ4F_decompressOptions_t dOpt;
|
|
memset(&dOpt, 0, sizeof(dOpt));
|
|
dOpt.skipChecksums = 1;
|
|
LZ4F_resetDecompressionContext(dctx);
|
|
r = LZ4F_getFrameInfo(dctx, &fi, frame, &hs);
|
|
SD_CHECK(!LZ4F_isError(r) && hs == hSize, "variant %u, block %u : %s", variant, (unsigned)k,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "wrong header");
|
|
r = LZ4F_decompress_usingDict(dctx, NULL, &noDst, NULL, &noSrc, src + dStarts[k] - dictSize, dictSize, &dOpt);
|
|
SD_CHECK(!LZ4F_isError(r), "variant %u, block %u : %s", variant, (unsigned)k, LZ4F_getErrorName(r));
|
|
/* skipChecksums remains active up to the end of frame */
|
|
r = LZ4F_decompress(dctx, decoded, &dstLen, frame + ends[k], &srcLen, NULL);
|
|
SD_CHECK(r == 0, "variant %u, block %u : %s", variant, (unsigned)k,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "frame not completed");
|
|
SD_CHECK(srcLen == frameSize - ends[k] && dstLen == srcSize - dStarts[k]
|
|
&& !memcmp(decoded, src + dStarts[k], dstLen),
|
|
"variant %u, block %u : resumed decoding is corrupted", variant, (unsigned)k);
|
|
}
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(decoded);
|
|
free(frame);
|
|
LZ4F_freeDecompressionContext(dctx);
|
|
return result;
|
|
}
|
|
|
|
/* ra_compressFrame() :
|
|
* compresses @src into a frame, flushing after @firstSize bytes, so that the 2nd block starts there.
|
|
* @return : frame size, or an error code */
|
|
static size_t ra_compressFrame(LZ4F_cctx* cctx, BYTE* dst, size_t dstCapacity,
|
|
const BYTE* src, size_t srcSize, size_t firstSize,
|
|
const LZ4F_CDict* cdict, const LZ4F_preferences_t* prefs)
|
|
{
|
|
size_t pos, r;
|
|
pos = LZ4F_compressBegin_usingCDict(cctx, dst, dstCapacity, cdict, prefs);
|
|
if (LZ4F_isError(pos)) return pos;
|
|
r = LZ4F_compressUpdate(cctx, dst + pos, dstCapacity - pos, src, firstSize, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
pos += r;
|
|
r = LZ4F_flush(cctx, dst + pos, dstCapacity - pos, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
pos += r;
|
|
r = LZ4F_compressUpdate(cctx, dst + pos, dstCapacity - pos, src + firstSize, srcSize - firstSize, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
pos += r;
|
|
r = LZ4F_compressEnd(cctx, dst + pos, dstCapacity - pos, NULL);
|
|
if (LZ4F_isError(r)) return r;
|
|
return pos + r;
|
|
}
|
|
|
|
/* ra_blockDict() :
|
|
* determines the data a block starting at position @pos within @src may refer to, as expected by LZ4F_resumeFrame() :
|
|
* for linked blocks, the last (up to) 64 KB of @dict followed by @src[0, @pos[, copied into @hist ;
|
|
* for independent blocks, @dict.
|
|
* @dict : the dictionary used to compress the frame, or NULL.
|
|
* @return : its size, and sets *dictPtr to its start */
|
|
static size_t ra_blockDict(const void** dictPtr, BYTE* hist, LZ4F_blockMode_t blockMode,
|
|
const BYTE* dict, size_t dictSize, const BYTE* src, size_t pos)
|
|
{
|
|
if (blockMode == LZ4F_blockIndependent) {
|
|
*dictPtr = dict;
|
|
return dictSize;
|
|
}
|
|
{ size_t const fromSrc = MIN(pos, 64 KB);
|
|
size_t const fromDict = MIN(dictSize, 64 KB - fromSrc);
|
|
if (fromDict) memcpy(hist, dict + dictSize - fromDict, fromDict);
|
|
if (fromSrc) memcpy(hist + fromDict, src + pos - fromSrc, fromSrc);
|
|
*dictPtr = hist;
|
|
return fromDict + fromSrc;
|
|
}
|
|
}
|
|
|
|
static int testResumeFrame(U32 seed)
|
|
{
|
|
size_t const blockSize = 64 KB;
|
|
size_t const firstSize = 10 KB; /* 2nd block starts there : its history is partly within the dictionary */
|
|
size_t const srcSize = firstSize + 4 * blockSize + 1000; /* last block is partial */
|
|
size_t const rawBlock = 3; /* stored uncompressed */
|
|
size_t const dictSize = 32 KB;
|
|
BYTE* const src = (BYTE*)malloc(srcSize);
|
|
BYTE* const dict = (BYTE*)malloc(dictSize);
|
|
BYTE* const decoded = (BYTE*)malloc(srcSize);
|
|
BYTE* const hist = (BYTE*)malloc(64 KB);
|
|
BYTE* frame = NULL;
|
|
size_t frameCapacity, bStart, n;
|
|
LZ4F_cctx* cctx = NULL;
|
|
LZ4F_CDict* cdict = NULL;
|
|
LZ4F_dctx* dctx = NULL;
|
|
unsigned variant;
|
|
int result = 1;
|
|
|
|
{ LZ4F_preferences_t worstPrefs = LZ4F_INIT_PREFERENCES;
|
|
worstPrefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
worstPrefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
worstPrefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
worstPrefs.frameInfo.contentSize = srcSize;
|
|
frameCapacity = 2 * LZ4F_compressFrameBound(srcSize, &worstPrefs); /* margin for the flushed block */
|
|
}
|
|
frame = (BYTE*)malloc(frameCapacity);
|
|
SD_CHECK(src != NULL && dict != NULL && decoded != NULL && hist != NULL && frame != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(dict, dictSize, 0.5, &seed);
|
|
FUZ_fillCompressibleNoiseBuffer(src, srcSize, 0.5, &seed);
|
|
for (n = firstSize + (rawBlock-1) * blockSize; n < firstSize + rawBlock * blockSize; n++) src[n] = (BYTE)FUZ_rand(&seed);
|
|
/* blocks start with content from the dictionary, so that they depend on it */
|
|
for (bStart = 0; bStart < srcSize; bStart = bStart ? bStart + blockSize : firstSize) {
|
|
if (bStart == firstSize + (rawBlock-1) * blockSize) continue;
|
|
memcpy(src + bStart, dict + dictSize - 2 KB, MIN(1 KB, srcSize - bStart));
|
|
}
|
|
cdict = LZ4F_createCDict(dict, dictSize);
|
|
SD_CHECK(cdict != NULL, "LZ4F_createCDict failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createCompressionContext(&cctx, LZ4F_VERSION)), "cctx creation failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION)), "dctx creation failure");
|
|
|
|
for (variant = 0; variant < 32; variant++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
unsigned const withDict = (variant >> 4) & 1;
|
|
const BYTE* const fDict = withDict ? dict : NULL;
|
|
size_t const fDictSize = withDict ? dictSize : 0;
|
|
LZ4F_frameInfo_t fi;
|
|
size_t ends[SD_ELTS_MAX], dStarts[SD_ELTS_MAX];
|
|
size_t frameSize, nbElts, nbBlocks, k, r;
|
|
prefs.frameInfo.blockMode = (LZ4F_blockMode_t)(variant & 1);
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)((variant >> 1) & 1);
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)((variant >> 2) & 1);
|
|
prefs.frameInfo.contentSize = ((variant >> 3) & 1) ? srcSize : 0;
|
|
frameSize = ra_compressFrame(cctx, frame, frameCapacity, src, srcSize, firstSize, withDict ? cdict : NULL, &prefs);
|
|
SD_CHECK(!LZ4F_isError(frameSize), "variant %u : %s", variant, LZ4F_getErrorName(frameSize));
|
|
nbElts = sd_frameElementEnds(ends, SD_ELTS_MAX, frame, frameSize);
|
|
SD_CHECK(nbElts > 0, "variant %u : malformed frame", variant);
|
|
nbBlocks = nbElts - 2 - prefs.frameInfo.contentChecksumFlag; /* minus header, endMark, and content checksum */
|
|
SD_CHECK(nbBlocks > rawBlock && (FUZ_readLE32(frame + ends[rawBlock]) & 0x80000000U),
|
|
"variant %u : block %u should be stored uncompressed", variant, (unsigned)rawBlock);
|
|
|
|
/* first pass : records block starts */
|
|
SD_CHECK(!ra_indexFrame(dctx, &fi, frame, frameSize, ends, nbBlocks, fDict, fDictSize, decoded, srcSize, dStarts),
|
|
"variant %u : first pass failed", variant);
|
|
SD_CHECK(!memcmp(decoded, src, srcSize), "variant %u : first pass corrupted", variant);
|
|
SD_CHECK(dStarts[1] == firstSize, "variant %u : 2nd block should start at %u", variant, (unsigned)firstSize);
|
|
|
|
/* resume from each block start */
|
|
for (k = 0; k < nbBlocks; k++) {
|
|
const void* bDict;
|
|
size_t const bDictSize = ra_blockDict(&bDict, hist, prefs.frameInfo.blockMode, fDict, fDictSize, src, dStarts[k]);
|
|
size_t srcLen = frameSize - ends[k], dstLen = srcSize - dStarts[k];
|
|
r = LZ4F_resumeFrame(dctx, &fi, dStarts[k], bDict, bDictSize);
|
|
SD_CHECK(!LZ4F_isError(r), "variant %u, block %u : %s", variant, (unsigned)k, LZ4F_getErrorName(r));
|
|
r = LZ4F_decompress(dctx, decoded, &dstLen, frame + ends[k], &srcLen, NULL);
|
|
SD_CHECK(r == 0, "variant %u, block %u : %s", variant, (unsigned)k,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "frame not completed");
|
|
SD_CHECK(srcLen == frameSize - ends[k] && dstLen == srcSize - dStarts[k]
|
|
&& !memcmp(decoded, src + dStarts[k], dstLen),
|
|
"variant %u, block %u : resumed decoding is corrupted", variant, (unsigned)k);
|
|
}
|
|
|
|
/* skipping the content checksum doesn't extend to next frame */
|
|
if (prefs.frameInfo.contentChecksumFlag) {
|
|
size_t srcLen = frameSize, dstLen = srcSize;
|
|
frame[frameSize - 1] ^= 1;
|
|
r = LZ4F_decompress_usingDict(dctx, decoded, &dstLen, frame, &srcLen, fDict, fDictSize, NULL);
|
|
frame[frameSize - 1] ^= 1;
|
|
SD_CHECK(LZ4F_getErrorCode(r) == LZ4F_ERROR_contentChecksum_invalid, "variant %u : %s", variant,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "corrupted content checksum not detected");
|
|
}
|
|
|
|
/* block checksums remain verified (including uncompressed blocks), and content size too */
|
|
{ const void* bDict;
|
|
size_t const bDictSize = ra_blockDict(&bDict, hist, prefs.frameInfo.blockMode, fDict, fDictSize, src, dStarts[rawBlock]);
|
|
size_t srcLen = frameSize - ends[rawBlock], dstLen = srcSize;
|
|
if (prefs.frameInfo.blockChecksumFlag) {
|
|
r = LZ4F_resumeFrame(dctx, &fi, dStarts[rawBlock], bDict, bDictSize);
|
|
SD_CHECK(!LZ4F_isError(r), "variant %u : %s", variant, LZ4F_getErrorName(r));
|
|
frame[ends[rawBlock+1] - 1] ^= 1;
|
|
r = LZ4F_decompress(dctx, decoded, &dstLen, frame + ends[rawBlock], &srcLen, NULL);
|
|
frame[ends[rawBlock+1] - 1] ^= 1;
|
|
SD_CHECK(LZ4F_getErrorCode(r) == LZ4F_ERROR_blockChecksum_invalid, "variant %u : %s", variant,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "corrupted block checksum not detected");
|
|
}
|
|
if (prefs.frameInfo.contentSize) {
|
|
srcLen = frameSize - ends[rawBlock];
|
|
dstLen = srcSize;
|
|
r = LZ4F_resumeFrame(dctx, &fi, dStarts[rawBlock] + 1, bDict, bDictSize);
|
|
SD_CHECK(!LZ4F_isError(r), "variant %u : %s", variant, LZ4F_getErrorName(r));
|
|
r = LZ4F_decompress(dctx, decoded, &dstLen, frame + ends[rawBlock], &srcLen, NULL);
|
|
SD_CHECK(LZ4F_getErrorCode(r) == LZ4F_ERROR_frameSize_wrong, "variant %u : %s", variant,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "wrong decodedPos not detected");
|
|
r = LZ4F_resumeFrame(dctx, &fi, srcSize + 1, NULL, 0);
|
|
SD_CHECK(LZ4F_getErrorCode(r) == LZ4F_ERROR_frameSize_wrong, "variant %u : %s", variant,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "decodedPos beyond content size accepted");
|
|
} }
|
|
}
|
|
|
|
/* invalid parameters */
|
|
{ LZ4F_frameInfo_t fi;
|
|
memset(&fi, 0, sizeof(fi));
|
|
fi.blockSizeID = LZ4F_max64KB;
|
|
SD_CHECK(!LZ4F_isError(LZ4F_resumeFrame(dctx, &fi, 0, NULL, 0)), "valid frameInfo rejected");
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_resumeFrame(dctx, NULL, 0, NULL, 0)) == LZ4F_ERROR_parameter_null, "NULL frameInfo accepted");
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_resumeFrame(dctx, &fi, 0, NULL, 1)) == LZ4F_ERROR_parameter_null, "NULL dict accepted");
|
|
fi.frameType = LZ4F_skippableFrame;
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_resumeFrame(dctx, &fi, 0, NULL, 0)) == LZ4F_ERROR_frameType_unknown, "skippable frame accepted");
|
|
fi.frameType = LZ4F_frame;
|
|
fi.blockSizeID = LZ4F_default;
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_resumeFrame(dctx, &fi, 0, NULL, 0)) == LZ4F_ERROR_maxBlockSize_invalid, "undefined block size accepted");
|
|
fi.blockSizeID = LZ4F_max64KB;
|
|
fi.blockMode = (LZ4F_blockMode_t)2;
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_resumeFrame(dctx, &fi, 0, NULL, 0)) == LZ4F_ERROR_blockMode_invalid, "invalid block mode accepted");
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(src);
|
|
free(dict);
|
|
free(decoded);
|
|
free(hist);
|
|
free(frame);
|
|
LZ4F_freeCDict(cdict);
|
|
LZ4F_freeCompressionContext(cctx);
|
|
LZ4F_freeDecompressionContext(dctx);
|
|
return result;
|
|
}
|
|
|
|
|
|
/* LZ4F_seek() : random reads within a file, compared to its decoded content.
|
|
* The file starts after unrelated data, and contains frames using linked or independent blocks,
|
|
* checksums, content size, a skippable frame, and an empty frame. */
|
|
|
|
/* fs_writeFrame() : compresses @src into a frame, appended to @f.
|
|
* @return : 0 on success */
|
|
static int fs_writeFrame(FILE* f, const BYTE* src, size_t srcSize, const LZ4F_preferences_t* prefs)
|
|
{
|
|
size_t const capacity = LZ4F_compressFrameBound(srcSize, prefs);
|
|
BYTE* const frame = (BYTE*)malloc(capacity);
|
|
size_t frameSize;
|
|
int result = 1;
|
|
if (frame == NULL) return 1;
|
|
frameSize = LZ4F_compressFrame(frame, capacity, src, srcSize, prefs);
|
|
if (!LZ4F_isError(frameSize) && fwrite(frame, 1, frameSize, f) == frameSize) result = 0;
|
|
free(frame);
|
|
return result;
|
|
}
|
|
|
|
static int testFileSeek(U32 seed)
|
|
{
|
|
/* a skippable frame follows the 1st one.
|
|
* the last frame uses linked blocks and is larger than LZ4F_RESUME_SPAN (1 MB) :
|
|
* a resume point is recorded at its end, which is the end of file */
|
|
size_t const sizes[] = { 1200 KB, 1000, 0, 2500 KB, 1200 KB };
|
|
size_t const nbFrames = sizeof(sizes) / sizeof(sizes[0]);
|
|
size_t const prefixSize = 100; /* unrelated data before the 1st frame */
|
|
size_t const bufSize = 200 KB;
|
|
size_t totalSize = 0, n, srcPos, pos;
|
|
BYTE* ref = NULL;
|
|
BYTE* const buf = (BYTE*)malloc(bufSize);
|
|
FILE* const f = tmpfile();
|
|
LZ4_readFile_t* rf = NULL;
|
|
unsigned op;
|
|
int result = 1;
|
|
|
|
for (n = 0; n < nbFrames; n++) totalSize += sizes[n];
|
|
ref = (BYTE*)malloc(totalSize);
|
|
SD_CHECK(ref != NULL && buf != NULL && f != NULL, "allocation failure");
|
|
FUZ_fillCompressibleNoiseBuffer(ref, totalSize, 0.5, &seed);
|
|
srcPos = sizes[0] + sizes[1] + sizes[2];
|
|
for (n = srcPos + 64 KB; n < srcPos + 128 KB; n++) ref[n] = (BYTE)FUZ_rand(&seed); /* uncompressed block in the 4th frame */
|
|
|
|
for (n = 0; n < prefixSize; n++) buf[n] = (BYTE)FUZ_rand(&seed);
|
|
SD_CHECK(fwrite(buf, 1, prefixSize, f) == prefixSize, "write failure");
|
|
for (srcPos = 0, n = 0; n < nbFrames; srcPos += sizes[n], n++) {
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.frameInfo.blockMode = (n == 0 || n == 4) ? LZ4F_blockLinked : LZ4F_blockIndependent;
|
|
if (n == 0 || n == 3) prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
if (n == 3) {
|
|
prefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
prefs.frameInfo.contentSize = sizes[n];
|
|
}
|
|
SD_CHECK(!fs_writeFrame(f, ref + srcPos, sizes[n], &prefs), "frame %u : write failure", (unsigned)n);
|
|
if (n == 0) {
|
|
BYTE skippable[8 + 100];
|
|
FUZ_writeLE32(skippable, LZ4F_MAGIC_SKIPPABLE_START + 5);
|
|
FUZ_writeLE32(skippable + 4, 100);
|
|
memset(skippable + 8, 0x5A, 100);
|
|
SD_CHECK(fwrite(skippable, 1, sizeof(skippable), f) == sizeof(skippable), "write failure");
|
|
} }
|
|
SD_CHECK(fseek(f, (long)prefixSize, SEEK_SET) == 0, "fseek failure");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_readOpen(&rf, f)), "LZ4F_readOpen failure");
|
|
|
|
/* random seeks and reads */
|
|
for (pos = 0, op = 0; op < 400; op++) {
|
|
unsigned const kind = FUZ_rand(&seed) % 4;
|
|
if (kind == 0) { /* absolute */
|
|
size_t const target = FUZ_rand(&seed) % (totalSize + 1);
|
|
size_t const r = LZ4F_seek(rf, (long long)target, SEEK_SET);
|
|
SD_CHECK(!LZ4F_isError(r), "op %u : seek to %u : %s", op, (unsigned)target, LZ4F_getErrorName(r));
|
|
pos = target;
|
|
} else if (kind == 1) { /* relative, nearby */
|
|
long long const delta = (long long)(FUZ_rand(&seed) % (400 KB)) - 200 KB;
|
|
long long const target = MIN(MAX((long long)pos + delta, 0), (long long)totalSize);
|
|
size_t const r = LZ4F_seek(rf, target - (long long)pos, SEEK_CUR);
|
|
SD_CHECK(!LZ4F_isError(r), "op %u : seek to %u : %s", op, (unsigned)target, LZ4F_getErrorName(r));
|
|
pos = (size_t)target;
|
|
} else { /* read, large or small */
|
|
size_t const size = 1 + FUZ_rand(&seed) % ((kind == 2) ? bufSize : 100);
|
|
size_t const r = LZ4F_read(rf, buf, size);
|
|
SD_CHECK(r == MIN(size, totalSize - pos), "op %u : read %u bytes at %u : %s", op, (unsigned)size, (unsigned)pos,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "wrong size");
|
|
SD_CHECK(!memcmp(buf, ref + pos, r), "op %u : wrong data at %u", op, (unsigned)pos);
|
|
pos += r;
|
|
}
|
|
SD_CHECK(LZ4F_tell(rf) == pos, "op %u : LZ4F_tell() = %u instead of %u", op, (unsigned)LZ4F_tell(rf), (unsigned)pos);
|
|
}
|
|
|
|
/* invalid seeks don't move */
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_seek(NULL, 0, SEEK_SET)) == LZ4F_ERROR_parameter_null, "NULL accepted");
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_seek(rf, 0, SEEK_END)) == LZ4F_ERROR_parameter_invalid, "SEEK_END accepted");
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_seek(rf, -1, SEEK_SET)) == LZ4F_ERROR_parameter_invalid, "negative position accepted");
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_seek(rf, -(long long)pos - 1, SEEK_CUR)) == LZ4F_ERROR_parameter_invalid, "position before start accepted");
|
|
SD_CHECK(LZ4F_tell(rf) == pos, "failed seek moved the position");
|
|
|
|
/* end of data */
|
|
SD_CHECK(!LZ4F_isError(LZ4F_seek(rf, (long long)totalSize, SEEK_SET)) && LZ4F_read(rf, buf, 1) == 0, "data beyond end");
|
|
SD_CHECK(LZ4F_getErrorCode(LZ4F_seek(rf, (long long)totalSize + 1, SEEK_SET)) == LZ4F_ERROR_parameter_invalid, "seek beyond end accepted");
|
|
SD_CHECK(LZ4F_tell(rf) == totalSize, "seek beyond end should stop at end");
|
|
|
|
/* full sequential read, from start */
|
|
SD_CHECK(!LZ4F_isError(LZ4F_seek(rf, 0, SEEK_SET)), "seek to start failed");
|
|
for (pos = 0; pos < totalSize; ) {
|
|
size_t const r = LZ4F_read(rf, buf, bufSize);
|
|
SD_CHECK(!LZ4F_isError(r) && r == MIN(bufSize, totalSize - pos) && !memcmp(buf, ref + pos, r),
|
|
"sequential read : wrong data at %u", (unsigned)pos);
|
|
pos += r;
|
|
}
|
|
/* end of file is reached cleanly, also when restarting from its resume point */
|
|
SD_CHECK(LZ4F_read(rf, buf, 1) == 0, "data beyond end after sequential read");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_seek(rf, 0, SEEK_SET)), "seek to start failed");
|
|
SD_CHECK(LZ4F_read(rf, buf, 1) == 1 && buf[0] == ref[0], "read at start failed");
|
|
SD_CHECK(!LZ4F_isError(LZ4F_seek(rf, (long long)totalSize, SEEK_SET)), "seek to indexed end failed");
|
|
SD_CHECK(LZ4F_read(rf, buf, 1) == 0, "end of file reported as truncated after restart");
|
|
result = 0;
|
|
|
|
_output_error:
|
|
LZ4F_readClose(rf);
|
|
if (f != NULL) fclose(f);
|
|
free(ref);
|
|
free(buf);
|
|
return result;
|
|
}
|
|
|
|
/* LZ4F_read() : complete files decode fully, then report end of file.
|
|
* Files ending within a frame report ERROR_io_read. */
|
|
static int testTruncatedFile(U32 seed)
|
|
{
|
|
static const struct {
|
|
size_t srcSize;
|
|
size_t readSize;
|
|
size_t cut; /* bytes removed from the end of the last frame */
|
|
int headerOnly; /* last frame reduced to its header */
|
|
} cases[] = {
|
|
{ 0, 1, 0, 0 }, /* empty frame */
|
|
{ 256, 1, 0, 0 }, /* complete, read by small, exact and large chunks */
|
|
{ 256, 256, 0, 0 },
|
|
{ 256, 257, 0, 0 },
|
|
{ 256, 1, 1, 0 }, /* truncated end mark, or checksum */
|
|
{ 256, 257, 1, 0 },
|
|
{ 256, 257, 4, 0 },
|
|
{ 256, 257, 8, 0 }, /* truncated block, without checksums */
|
|
{ 256, 257, 0, 1 } /* header only */
|
|
};
|
|
size_t const nbCases = sizeof(cases) / sizeof(cases[0]);
|
|
BYTE src[256];
|
|
BYTE frame[1 KB];
|
|
BYTE decoded[1 KB];
|
|
FILE* f = NULL;
|
|
LZ4_readFile_t* rf = NULL;
|
|
unsigned checksums, nbFrames, k;
|
|
size_t n;
|
|
int result = 1;
|
|
|
|
FUZ_fillCompressibleNoiseBuffer(src, sizeof(src), 0.5, &seed);
|
|
for (checksums = 0; checksums < 4; checksums++)
|
|
for (nbFrames = 1; nbFrames <= 2; nbFrames++)
|
|
for (n = 0; n < nbCases; n++) {
|
|
size_t const expected = cases[n].srcSize * nbFrames;
|
|
int const truncated = cases[n].cut || cases[n].headerOnly;
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
size_t frameSize, lastSize, total = 0, r;
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)(checksums & 1);
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)(checksums >> 1);
|
|
if (cases[n].headerOnly) prefs.frameInfo.contentSize = cases[n].srcSize; /* 15-byte header : LZ4F_readOpen() needs 11 bytes */
|
|
frameSize = LZ4F_compressFrame(frame, sizeof(frame), src, cases[n].srcSize, &prefs);
|
|
SD_CHECK(!LZ4F_isError(frameSize), "case %u : compression failed", (unsigned)n);
|
|
lastSize = cases[n].headerOnly ? LZ4F_headerSize(frame, frameSize) : frameSize - cases[n].cut;
|
|
|
|
f = tmpfile();
|
|
SD_CHECK(f != NULL, "tmpfile failure");
|
|
for (k = 1; k < nbFrames; k++) SD_CHECK(fwrite(frame, 1, frameSize, f) == frameSize, "write failure");
|
|
SD_CHECK(fwrite(frame, 1, lastSize, f) == lastSize, "write failure");
|
|
rewind(f);
|
|
SD_CHECK(!LZ4F_isError(LZ4F_readOpen(&rf, f)), "case %u : LZ4F_readOpen failure", (unsigned)n);
|
|
do {
|
|
r = LZ4F_read(rf, decoded + total, cases[n].readSize);
|
|
if (LZ4F_isError(r)) break;
|
|
total += r;
|
|
} while (r != 0 && total <= expected);
|
|
if (truncated) {
|
|
SD_CHECK(LZ4F_getErrorCode(r) == LZ4F_ERROR_io_read,
|
|
"case %u, %u frame(s), checksums %u : truncation not detected", (unsigned)n, nbFrames, checksums);
|
|
} else {
|
|
SD_CHECK(!LZ4F_isError(r) && total == expected,
|
|
"case %u, %u frame(s), checksums %u : decoded %u bytes", (unsigned)n, nbFrames, checksums, (unsigned)total);
|
|
for (k = 0; k < nbFrames; k++)
|
|
SD_CHECK(!memcmp(decoded + k * cases[n].srcSize, src, cases[n].srcSize), "case %u : wrong data", (unsigned)n);
|
|
SD_CHECK(LZ4F_read(rf, decoded, 1) == 0, "case %u : data beyond end", (unsigned)n);
|
|
}
|
|
LZ4F_readClose(rf); rf = NULL;
|
|
fclose(f); f = NULL;
|
|
}
|
|
result = 0;
|
|
|
|
_output_error:
|
|
LZ4F_readClose(rf);
|
|
if (f != NULL) fclose(f);
|
|
return result;
|
|
}
|
|
|
|
/* Level 12 with linked blocks, on short-period data (ABAB...) :
|
|
* all candidates in the 64 KB history match up to the end of the block.
|
|
* Comparing each of them in full (up to 16384 x 64 KB per block)
|
|
* makes it ~40x slower than level 12 on regular data.
|
|
* CPU time is compared to a reference of the same size,
|
|
* so that the test doesn't depend on platform speed (valgrind, qemu, sanitizers). */
|
|
static int testPeriodicLinkedSpeed(U32 seed)
|
|
{
|
|
size_t const srcSize = 1 MB;
|
|
size_t const dstCapacity = LZ4F_compressFrameBound(srcSize, NULL);
|
|
BYTE* const periodic = (BYTE*)malloc(srcSize);
|
|
BYTE* const regular = (BYTE*)malloc(srcSize);
|
|
BYTE* const dst = (BYTE*)malloc(dstCapacity);
|
|
LZ4F_preferences_t prefs = LZ4F_INIT_PREFERENCES;
|
|
clock_t start, refTime, periodicTime;
|
|
size_t n, r;
|
|
int result = 1;
|
|
|
|
SD_CHECK(periodic != NULL && regular != NULL && dst != NULL, "allocation failure");
|
|
for (n = 0; n < srcSize; n++) periodic[n] = (n & 1) ? 'B' : 'A';
|
|
FUZ_fillCompressibleNoiseBuffer(regular, srcSize, 0.5, &seed);
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.frameInfo.blockMode = LZ4F_blockLinked;
|
|
prefs.compressionLevel = 12;
|
|
|
|
start = clock();
|
|
r = LZ4F_compressFrame(dst, dstCapacity, regular, srcSize, &prefs);
|
|
refTime = clock() - start;
|
|
SD_CHECK(!LZ4F_isError(r), "reference : %s", LZ4F_getErrorName(r));
|
|
start = clock();
|
|
r = LZ4F_compressFrame(dst, dstCapacity, periodic, srcSize, &prefs);
|
|
periodicTime = clock() - start;
|
|
SD_CHECK(!LZ4F_isError(r), "periodic : %s", LZ4F_getErrorName(r));
|
|
DISPLAYLEVEL(4, "(periodic : %.3f s, reference : %.3f s) ",
|
|
(double)periodicTime / CLOCKS_PER_SEC, (double)refTime / CLOCKS_PER_SEC);
|
|
SD_CHECK(periodicTime <= refTime, "periodic data : %.3f s, reference : %.3f s",
|
|
(double)periodicTime / CLOCKS_PER_SEC, (double)refTime / CLOCKS_PER_SEC);
|
|
result = 0;
|
|
|
|
_output_error:
|
|
free(periodic);
|
|
free(regular);
|
|
free(dst);
|
|
return result;
|
|
}
|
|
|
|
|
|
static int unitTests(U32 seed, double compressibility)
|
|
{
|
|
#define COMPRESSIBLE_NOISE_LENGTH (2 MB)
|
|
void* const CNBuffer = malloc(COMPRESSIBLE_NOISE_LENGTH);
|
|
size_t const cBuffSize = LZ4F_compressFrameBound(COMPRESSIBLE_NOISE_LENGTH, NULL);
|
|
void* const compressedBuffer = malloc(cBuffSize);
|
|
void* const decodedBuffer = malloc(COMPRESSIBLE_NOISE_LENGTH);
|
|
U32 randVal = seed;
|
|
U32* const randState = &randVal;
|
|
size_t cSize, testSize;
|
|
LZ4F_decompressionContext_t dCtx = NULL;
|
|
LZ4F_compressionContext_t cctx = NULL;
|
|
U64 crcOrig;
|
|
int basicTests_error = 0;
|
|
LZ4F_preferences_t prefs;
|
|
memset(&prefs, 0, sizeof(prefs));
|
|
alloc_state_init((Test_alloc_state*)lz4f_cmem_test.opaqueState);
|
|
|
|
if (!CNBuffer || !compressedBuffer || !decodedBuffer) {
|
|
DISPLAY("allocation error, not enough memory to start fuzzer tests \n");
|
|
goto _output_error;
|
|
}
|
|
FUZ_fillCompressibleNoiseBuffer(CNBuffer, COMPRESSIBLE_NOISE_LENGTH, compressibility, randState);
|
|
crcOrig = XXH64(CNBuffer, COMPRESSIBLE_NOISE_LENGTH, extCrcSeed);
|
|
|
|
/* LZ4F_compressBound() : special case : srcSize == 0 */
|
|
DISPLAYLEVEL(3, "LZ4F_compressBound(0) = ");
|
|
{ size_t const cBound = LZ4F_compressBound(0, NULL);
|
|
if (cBound < 64 KB) goto _output_error;
|
|
DISPLAYLEVEL(3, " %u \n", (U32)cBound);
|
|
}
|
|
|
|
/* LZ4F_compressBound() : special case : automatic flushing enabled */
|
|
DISPLAYLEVEL(3, "LZ4F_compressBound(1 KB, autoFlush=1) = ");
|
|
{ size_t cBound;
|
|
LZ4F_preferences_t autoFlushPrefs;
|
|
memset(&autoFlushPrefs, 0, sizeof(autoFlushPrefs));
|
|
autoFlushPrefs.autoFlush = 1;
|
|
cBound = LZ4F_compressBound(1 KB, &autoFlushPrefs);
|
|
if (cBound > 64 KB) goto _output_error;
|
|
DISPLAYLEVEL(3, " %u \n", (U32)cBound);
|
|
}
|
|
|
|
/* LZ4F_compressBound() : special case : automatic flushing disabled */
|
|
DISPLAYLEVEL(3, "LZ4F_compressBound(1 KB, autoFlush=0) = ");
|
|
{ size_t const cBound = LZ4F_compressBound(1 KB, &prefs);
|
|
if (cBound < 64 KB) goto _output_error;
|
|
DISPLAYLEVEL(3, " %u \n", (U32)cBound);
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "Mixed compressed and uncompressed updates respect compressBound : ");
|
|
if (testMixedUpdates(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_flush and LZ4F_compressEnd require a frame in progress : ");
|
|
if (testFlushOutsideFrame()) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressBegin only needs the actual header size : ");
|
|
if (testSmallDstBegin(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame succeeds if and only if the frame fits : ");
|
|
if (testSmallDstFrame(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "Streaming compression succeeds if and only if each output fits : ");
|
|
if (testSmallDstStream(seed, 24)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "favorDecSpeed applies to blocks after an incompressible block : ");
|
|
if (testFavorDecSpeedAfterRawBlock(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "favorDecSpeed applies to frames started with a dictionary : ");
|
|
if (testFavorDecSpeedWithDict(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "Frames don't depend on the history of the compression context : ");
|
|
if (testReproducibleFrames(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "skipChecksums skips all checksums : ");
|
|
if (testSkipChecksums(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "Decoding can resume from any block start (random access) : ");
|
|
if (testRandomAccess(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_resumeFrame() resumes decoding from any block start : ");
|
|
if (testResumeFrame(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_seek() within a file : ");
|
|
if (testFileSeek(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_read() reports truncated files : ");
|
|
if (testTruncatedFile(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "Level 12 with linked blocks isn't slow on short-period data : ");
|
|
if (testPeriodicLinkedSpeed(seed)) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
/* Special case : null-content frame */
|
|
testSize = 0;
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame, compress null content : ");
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, NULL), CNBuffer, testSize, NULL));
|
|
DISPLAYLEVEL(3, "null content encoded into a %u bytes frame \n", (unsigned)cSize);
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_createDecompressionContext \n");
|
|
CHECK ( LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION) );
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_getFrameInfo on null-content frame (#157) \n");
|
|
assert(cSize >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH);
|
|
{ LZ4F_frameInfo_t frame_info;
|
|
size_t const fhs = LZ4F_headerSize(compressedBuffer, LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH);
|
|
size_t avail_in = fhs;
|
|
CHECK( fhs );
|
|
CHECK( LZ4F_getFrameInfo(dCtx, &frame_info, compressedBuffer, &avail_in) );
|
|
if (avail_in != fhs) goto _output_error; /* must consume all, since header size is supposed to be exact */
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_freeDecompressionContext \n");
|
|
CHECK( LZ4F_freeDecompressionContext(dCtx) );
|
|
dCtx = NULL;
|
|
|
|
/* test one-pass frame compression */
|
|
testSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame, using fast level -3 : ");
|
|
{ LZ4F_preferences_t fastCompressPrefs;
|
|
memset(&fastCompressPrefs, 0, sizeof(fastCompressPrefs));
|
|
fastCompressPrefs.compressionLevel = -3;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, NULL), CNBuffer, testSize, &fastCompressPrefs));
|
|
DISPLAYLEVEL(3, "Compressed %u bytes into a %u bytes frame \n", (U32)testSize, (U32)cSize);
|
|
}
|
|
|
|
/* acceleration (-level + 1) is capped at LZ4_ACCELERATION_MAX,
|
|
* so all levels below the cap must produce the same frame, including INT_MIN and INT_MIN+1 */
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame, very negative levels behave like the capped level : ");
|
|
{ int const cappedLevel = 1 - LZ4_ACCELERATION_MAX;
|
|
int const extremeLevels[] = { -LZ4_ACCELERATION_MAX, INT_MIN + 1, INT_MIN }; /* cappedLevel - 1 */
|
|
size_t const cBound = LZ4F_compressFrameBound(testSize, NULL);
|
|
void* const refBuffer = malloc(cBound);
|
|
LZ4F_preferences_t fastPrefs;
|
|
size_t refSize;
|
|
int n;
|
|
if (refBuffer == NULL) goto _output_error;
|
|
memset(&fastPrefs, 0, sizeof(fastPrefs));
|
|
fastPrefs.compressionLevel = cappedLevel;
|
|
refSize = LZ4F_compressFrame(refBuffer, cBound, CNBuffer, testSize, &fastPrefs);
|
|
if (LZ4F_isError(refSize)) { free(refBuffer); goto _output_error; }
|
|
for (n = 0; n < (int)(sizeof(extremeLevels) / sizeof(extremeLevels[0])); n++) {
|
|
fastPrefs.compressionLevel = extremeLevels[n];
|
|
cSize = LZ4F_compressFrame(compressedBuffer, cBound, CNBuffer, testSize, &fastPrefs);
|
|
if (LZ4F_isError(cSize) || cSize != refSize || memcmp(compressedBuffer, refBuffer, cSize)) {
|
|
DISPLAYLEVEL(3, "level %i : different frame (%u bytes vs %u) \n", extremeLevels[n], (U32)cSize, (U32)refSize);
|
|
free(refBuffer);
|
|
goto _output_error;
|
|
}
|
|
}
|
|
free(refBuffer);
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
}
|
|
|
|
/* Verify compressBound() with LZ4F_write and checksums disabled */
|
|
DISPLAYLEVEL(3, "LZ4F_write checksums disabled edge case : ");
|
|
{
|
|
FILE* tmpFile = tmpfile();
|
|
LZ4_writeFile_t* writeCtx = NULL;
|
|
LZ4F_preferences_t writePrefs;
|
|
size_t blockSize = LZ4F_getBlockSize(LZ4F_default);
|
|
char* data = (char*)calloc(blockSize, sizeof(char));
|
|
int failed = 0;
|
|
|
|
memset(&writePrefs, 0, sizeof(writePrefs));
|
|
writePrefs.frameInfo.contentChecksumFlag = LZ4F_noContentChecksum;
|
|
writePrefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
|
|
|
|
do {
|
|
if (!tmpFile || !data){ failed = 1; break; }
|
|
if (LZ4F_isError(LZ4F_writeOpen(&writeCtx, tmpFile, &writePrefs))){ failed = 1; break; }
|
|
if (LZ4F_isError(LZ4F_write(writeCtx, data, blockSize - 1))){ failed = 1; break; }
|
|
if (LZ4F_isError(LZ4F_write(writeCtx, data, 1))){ failed = 1; break; }
|
|
} while(0);
|
|
|
|
if (writeCtx && LZ4F_isError(LZ4F_writeClose(writeCtx))) { failed = 1; }
|
|
free(data);
|
|
if (tmpFile) fclose(tmpFile);
|
|
|
|
if (failed) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
}
|
|
|
|
/* LZ4F_writeOpen() must reject an invalid blockSizeID,
|
|
* instead of sizing its buffers from the error code returned by LZ4F_getBlockSize() */
|
|
DISPLAYLEVEL(3, "LZ4F_writeOpen rejects invalid blockSizeID : ");
|
|
{ int const invalidBSID[] = { 1, 2, 3, 8, 9 };
|
|
int const validBSID[] = { 0 /* == default */, 4, 5, 6, 7 };
|
|
FILE* const tmpFile = tmpfile();
|
|
LZ4F_preferences_t writePrefs;
|
|
size_t nb;
|
|
int failed = (tmpFile == NULL);
|
|
|
|
for (nb = 0; !failed && nb < sizeof(invalidBSID) / sizeof(invalidBSID[0]); nb++) {
|
|
LZ4_writeFile_t* writeCtx = NULL;
|
|
size_t r;
|
|
memset(&writePrefs, 0, sizeof(writePrefs)); /* blockLinked : wrapped buffer size is small, so allocation succeeds */
|
|
writePrefs.frameInfo.blockSizeID = (LZ4F_blockSizeID_t)invalidBSID[nb];
|
|
r = LZ4F_writeOpen(&writeCtx, tmpFile, &writePrefs);
|
|
if (LZ4F_getErrorCode(r) != LZ4F_ERROR_maxBlockSize_invalid) {
|
|
DISPLAYLEVEL(3, "blockSizeID %i not rejected (%s) ", invalidBSID[nb],
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "accepted");
|
|
failed = 1; /* don't use writeCtx: its buffers are undersized */
|
|
} }
|
|
|
|
for (nb = 0; !failed && nb < sizeof(validBSID) / sizeof(validBSID[0]); nb++) {
|
|
LZ4_writeFile_t* writeCtx = NULL;
|
|
memset(&writePrefs, 0, sizeof(writePrefs));
|
|
writePrefs.frameInfo.blockSizeID = (LZ4F_blockSizeID_t)validBSID[nb];
|
|
if (LZ4F_isError(LZ4F_writeOpen(&writeCtx, tmpFile, &writePrefs))
|
|
|| LZ4F_isError(LZ4F_writeClose(writeCtx))) {
|
|
DISPLAYLEVEL(3, "valid blockSizeID %i rejected ", validBSID[nb]);
|
|
failed = 1;
|
|
} }
|
|
|
|
if (tmpFile) fclose(tmpFile);
|
|
if (failed) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
}
|
|
|
|
/* a failed open sets the handle to NULL,
|
|
* LZ4F_writeClose() reports an error from a previous LZ4F_write() */
|
|
DISPLAYLEVEL(3, "lz4file error reporting : ");
|
|
{ LZ4_writeFile_t* writeCtx = (LZ4_writeFile_t*)CNBuffer; /* stale value */
|
|
LZ4_readFile_t* readCtx = (LZ4_readFile_t*)CNBuffer;
|
|
FILE* const tmpFile = tmpfile();
|
|
int failed = (tmpFile == NULL);
|
|
|
|
if (!LZ4F_isError(LZ4F_writeOpen(&writeCtx, NULL, NULL)) || writeCtx != NULL) failed = 1;
|
|
if (!LZ4F_isError(LZ4F_readOpen(&readCtx, NULL)) || readCtx != NULL) failed = 1;
|
|
if (!failed && (LZ4F_isError(LZ4F_writeOpen(&writeCtx, tmpFile, NULL))
|
|
|| LZ4F_writeClose(writeCtx) != LZ4F_OK_NoError)) failed = 1;
|
|
if (tmpFile) fclose(tmpFile);
|
|
#if defined(__linux__)
|
|
{ FILE* const fullFile = fopen("/dev/full", "wb"); /* writes fail when stdio flushes its buffer */
|
|
if (!failed && fullFile != NULL) {
|
|
if (LZ4F_isError(LZ4F_writeOpen(&writeCtx, fullFile, NULL))) failed = 1; /* header remains buffered */
|
|
else {
|
|
size_t const wResult = LZ4F_write(writeCtx, CNBuffer, COMPRESSIBLE_NOISE_LENGTH);
|
|
size_t const cResult = LZ4F_writeClose(writeCtx);
|
|
if (!LZ4F_isError(wResult) || !LZ4F_isError(cResult)) failed = 1;
|
|
} }
|
|
if (fullFile) fclose(fullFile);
|
|
}
|
|
#endif
|
|
if (failed) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame, using default preferences : ");
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, NULL), CNBuffer, testSize, NULL));
|
|
DISPLAYLEVEL(3, "Compressed %u bytes into a %u bytes frame \n", (U32)testSize, (U32)cSize);
|
|
|
|
DISPLAYLEVEL(3, "Decompression test : \n");
|
|
{ size_t decodedBufferSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
size_t compressedBufferSize = cSize;
|
|
|
|
CHECK( LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION) );
|
|
|
|
DISPLAYLEVEL(3, "Single Pass decompression : ");
|
|
CHECK( LZ4F_decompress(dCtx, decodedBuffer, &decodedBufferSize, compressedBuffer, &compressedBufferSize, NULL) );
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedBufferSize, extCrcSeed);
|
|
if (crcDest != crcOrig) goto _output_error; }
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedBufferSize);
|
|
|
|
DISPLAYLEVEL(3, "Reusing decompression context \n");
|
|
{ size_t const missingBytes = 4;
|
|
size_t iSize = compressedBufferSize - missingBytes;
|
|
const BYTE* cBuff = (const BYTE*) compressedBuffer;
|
|
BYTE* const ostart = (BYTE*)decodedBuffer;
|
|
BYTE* op = ostart;
|
|
BYTE* const oend = (BYTE*)decodedBuffer + COMPRESSIBLE_NOISE_LENGTH;
|
|
size_t decResult, oSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
DISPLAYLEVEL(3, "Missing last %u bytes : ", (U32)missingBytes);
|
|
CHECK_V(decResult, LZ4F_decompress(dCtx, op, &oSize, cBuff, &iSize, NULL));
|
|
if (decResult != missingBytes) {
|
|
DISPLAY("%u bytes missing != %u bytes requested \n", (U32)missingBytes, (U32)decResult);
|
|
goto _output_error;
|
|
}
|
|
DISPLAYLEVEL(3, "indeed, requests %u bytes \n", (unsigned)decResult);
|
|
cBuff += iSize;
|
|
iSize = decResult;
|
|
op += oSize;
|
|
oSize = (size_t)(oend-op);
|
|
decResult = LZ4F_decompress(dCtx, op, &oSize, cBuff, &iSize, NULL);
|
|
if (decResult != 0) goto _output_error; /* should finish now */
|
|
op += oSize;
|
|
if (op>oend) { DISPLAY("decompression write overflow \n"); goto _output_error; }
|
|
{ U64 const crcDest = XXH64(decodedBuffer, (size_t)(op-ostart), extCrcSeed);
|
|
if (crcDest != crcOrig) goto _output_error;
|
|
} }
|
|
|
|
{ size_t oSize = 0;
|
|
size_t iSize = 0;
|
|
LZ4F_frameInfo_t fi;
|
|
const BYTE* ip = (BYTE*)compressedBuffer;
|
|
|
|
DISPLAYLEVEL(3, "Start by feeding 0 bytes, to get next input size : ");
|
|
CHECK( LZ4F_decompress(dCtx, NULL, &oSize, ip, &iSize, NULL) );
|
|
//DISPLAYLEVEL(3, " %u \n", (unsigned)errorCode);
|
|
DISPLAYLEVEL(3, " OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_getFrameInfo on zero-size input : ");
|
|
{ size_t nullSize = 0;
|
|
size_t const fiError = LZ4F_getFrameInfo(dCtx, &fi, ip, &nullSize);
|
|
if (LZ4F_getErrorCode(fiError) != LZ4F_ERROR_frameHeader_incomplete) {
|
|
DISPLAYLEVEL(3, "incorrect error : %s != ERROR_frameHeader_incomplete \n",
|
|
LZ4F_getErrorName(fiError));
|
|
goto _output_error;
|
|
}
|
|
DISPLAYLEVEL(3, " correctly failed : %s \n", LZ4F_getErrorName(fiError));
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_getFrameInfo on not enough input : ");
|
|
{ size_t inputSize = 6;
|
|
size_t const fiError = LZ4F_getFrameInfo(dCtx, &fi, ip, &inputSize);
|
|
if (LZ4F_getErrorCode(fiError) != LZ4F_ERROR_frameHeader_incomplete) {
|
|
DISPLAYLEVEL(3, "incorrect error : %s != ERROR_frameHeader_incomplete \n", LZ4F_getErrorName(fiError));
|
|
goto _output_error;
|
|
}
|
|
DISPLAYLEVEL(3, " correctly failed : %s \n", LZ4F_getErrorName(fiError));
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_getFrameInfo on enough input : ");
|
|
iSize = LZ4F_headerSize(ip, LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH);
|
|
CHECK( iSize );
|
|
CHECK( LZ4F_getFrameInfo(dCtx, &fi, ip, &iSize) );
|
|
DISPLAYLEVEL(3, " correctly decoded \n");
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "Decode a buggy input : ");
|
|
assert(COMPRESSIBLE_NOISE_LENGTH > 64);
|
|
assert(cSize > 48);
|
|
memcpy(decodedBuffer, (char*)compressedBuffer+16, 32); /* save correct data */
|
|
memcpy((char*)compressedBuffer+16, (const char*)decodedBuffer+32, 32); /* insert noise */
|
|
{ size_t dbSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
size_t cbSize = cSize;
|
|
size_t const decompressError = LZ4F_decompress(dCtx, decodedBuffer, &dbSize,
|
|
compressedBuffer, &cbSize,
|
|
NULL);
|
|
if (!LZ4F_isError(decompressError)) goto _output_error;
|
|
DISPLAYLEVEL(3, "error detected : %s \n", LZ4F_getErrorName(decompressError));
|
|
}
|
|
memcpy((char*)compressedBuffer+16, decodedBuffer, 32); /* restore correct data */
|
|
|
|
DISPLAYLEVEL(3, "Reset decompression context, since it's left in error state \n");
|
|
LZ4F_resetDecompressionContext(dCtx); /* always successful */
|
|
|
|
DISPLAYLEVEL(3, "Byte after byte : ");
|
|
{ BYTE* const ostart = (BYTE*)decodedBuffer;
|
|
BYTE* op = ostart;
|
|
BYTE* const oend = (BYTE*)decodedBuffer + COMPRESSIBLE_NOISE_LENGTH;
|
|
const BYTE* ip = (const BYTE*) compressedBuffer;
|
|
const BYTE* const iend = ip + cSize;
|
|
while (ip < iend) {
|
|
size_t oSize = (size_t)(oend-op);
|
|
size_t iSize = 1;
|
|
CHECK( LZ4F_decompress(dCtx, op, &oSize, ip, &iSize, NULL) );
|
|
op += oSize;
|
|
ip += iSize;
|
|
}
|
|
{ U64 const crcDest = XXH64(decodedBuffer, COMPRESSIBLE_NOISE_LENGTH, extCrcSeed);
|
|
if (crcDest != crcOrig) goto _output_error;
|
|
}
|
|
DISPLAYLEVEL(3, "Regenerated %u/%u bytes \n", (unsigned)(op-ostart), (unsigned)COMPRESSIBLE_NOISE_LENGTH);
|
|
}
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "Using 64 KB block : ");
|
|
prefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs));
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "without checksum : ");
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_noContentChecksum;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs));
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "Using 256 KB block : ");
|
|
prefs.frameInfo.blockSizeID = LZ4F_max256KB;
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs));
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "Decompression test : \n");
|
|
{ size_t const decodedBufferSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
unsigned const maxBits = FUZ_highbit((U32)decodedBufferSize);
|
|
BYTE* const ostart = (BYTE*)decodedBuffer;
|
|
BYTE* op = ostart;
|
|
BYTE* const oend = ostart + COMPRESSIBLE_NOISE_LENGTH;
|
|
const BYTE* ip = (const BYTE*)compressedBuffer;
|
|
const BYTE* const iend = (const BYTE*)compressedBuffer + cSize;
|
|
|
|
DISPLAYLEVEL(3, "random segment sizes : ");
|
|
while (ip < iend) {
|
|
unsigned const nbBits = FUZ_rand(randState) % maxBits;
|
|
size_t iSize = RAND_BITS(nbBits) + 1;
|
|
size_t oSize = (size_t)(oend-op);
|
|
if (iSize > (size_t)(iend-ip)) iSize = (size_t)(iend-ip);
|
|
CHECK( LZ4F_decompress(dCtx, op, &oSize, ip, &iSize, NULL) );
|
|
op += oSize;
|
|
ip += iSize;
|
|
}
|
|
{ size_t const decodedSize = (size_t)(op - ostart);
|
|
U64 const crcDest = XXH64(decodedBuffer, decodedSize, extCrcSeed);
|
|
if (crcDest != crcOrig) goto _output_error;
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedSize);
|
|
}
|
|
|
|
CHECK( LZ4F_freeDecompressionContext(dCtx) );
|
|
dCtx = NULL;
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "without checksum : ");
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_noContentChecksum;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs) );
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "Using 1 MB block : ");
|
|
prefs.frameInfo.blockSizeID = LZ4F_max1MB;
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs) );
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "without frame checksum : ");
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_noContentChecksum;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs) );
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "Using 4 MB block : ");
|
|
prefs.frameInfo.blockSizeID = LZ4F_max4MB;
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
{ size_t const dstCapacity = LZ4F_compressFrameBound(testSize, &prefs);
|
|
DISPLAYLEVEL(4, "dstCapacity = %u ; ", (U32)dstCapacity);
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, dstCapacity, CNBuffer, testSize, &prefs) );
|
|
DISPLAYLEVEL(3, "Compressed %u bytes into a %u bytes frame \n", (U32)testSize, (U32)cSize);
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "without frame checksum : ");
|
|
prefs.frameInfo.contentChecksumFlag = LZ4F_noContentChecksum;
|
|
{ size_t const dstCapacity = LZ4F_compressFrameBound(testSize, &prefs);
|
|
DISPLAYLEVEL(4, "dstCapacity = %u ; ", (U32)dstCapacity);
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, dstCapacity, CNBuffer, testSize, &prefs) );
|
|
DISPLAYLEVEL(3, "Compressed %u bytes into a %u bytes frame \n", (U32)testSize, (U32)cSize);
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame with block checksum : ");
|
|
memset(&prefs, 0, sizeof(prefs));
|
|
prefs.frameInfo.blockChecksumFlag = LZ4F_blockChecksumEnabled;
|
|
CHECK_V(cSize, LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(testSize, &prefs), CNBuffer, testSize, &prefs) );
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)cSize);
|
|
|
|
DISPLAYLEVEL(3, "Decompress with block checksum : ");
|
|
{ size_t iSize = cSize;
|
|
size_t decodedSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
LZ4F_decompressionContext_t dctx;
|
|
CHECK( LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) );
|
|
CHECK( LZ4F_decompress(dctx, decodedBuffer, &decodedSize, compressedBuffer, &iSize, NULL) );
|
|
if (decodedSize != testSize) goto _output_error;
|
|
if (iSize != cSize) goto _output_error;
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedSize, extCrcSeed);
|
|
U64 const crcSrc = XXH64(CNBuffer, testSize, extCrcSeed);
|
|
if (crcDest != crcSrc) goto _output_error;
|
|
}
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedSize);
|
|
|
|
CHECK( LZ4F_freeDecompressionContext(dctx) );
|
|
}
|
|
|
|
/* frame content size tests */
|
|
{ size_t cErr;
|
|
BYTE* const ostart = (BYTE*)compressedBuffer;
|
|
BYTE* op = ostart;
|
|
CHECK( LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) );
|
|
|
|
DISPLAYLEVEL(3, "compress without frameSize : ");
|
|
memset(&(prefs.frameInfo), 0, sizeof(prefs.frameInfo));
|
|
CHECK_V(cErr, LZ4F_compressBegin(cctx, compressedBuffer, testSize, &prefs));
|
|
op += cErr;
|
|
CHECK_V(cErr, LZ4F_compressUpdate(cctx, op, LZ4F_compressBound(testSize, &prefs), CNBuffer, testSize, NULL));
|
|
op += cErr;
|
|
CHECK( LZ4F_compressEnd(cctx, compressedBuffer, testSize, NULL) );
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)(op-ostart));
|
|
|
|
DISPLAYLEVEL(3, "compress with frameSize : ");
|
|
prefs.frameInfo.contentSize = testSize;
|
|
op = ostart;
|
|
CHECK_V(cErr, LZ4F_compressBegin(cctx, compressedBuffer, testSize, &prefs));
|
|
op += cErr;
|
|
CHECK_V(cErr, LZ4F_compressUpdate(cctx, op, LZ4F_compressBound(testSize, &prefs), CNBuffer, testSize, NULL));
|
|
op += cErr;
|
|
CHECK( LZ4F_compressEnd(cctx, compressedBuffer, testSize, NULL) );
|
|
DISPLAYLEVEL(3, "Compressed %i bytes into a %i bytes frame \n", (int)testSize, (int)(op-ostart));
|
|
|
|
DISPLAYLEVEL(3, "compress with wrong frameSize : ");
|
|
prefs.frameInfo.contentSize = testSize+1;
|
|
op = ostart;
|
|
CHECK_V(cErr, LZ4F_compressBegin(cctx, compressedBuffer, testSize, &prefs));
|
|
op += cErr;
|
|
CHECK_V(cErr, LZ4F_compressUpdate(cctx, op, LZ4F_compressBound(testSize, &prefs), CNBuffer, testSize, NULL));
|
|
op += cErr;
|
|
cErr = LZ4F_compressEnd(cctx, op, testSize, NULL);
|
|
if (!LZ4F_isError(cErr)) goto _output_error;
|
|
DISPLAYLEVEL(3, "Error correctly detected : %s \n", LZ4F_getErrorName(cErr));
|
|
|
|
CHECK( LZ4F_freeCompressionContext(cctx) );
|
|
cctx = NULL;
|
|
}
|
|
|
|
/* blockSizeID validation tests */
|
|
{ int const invalidBSID[] = { 1, 2, 3, 8, 9 };
|
|
int const validBSID[] = { 0 /* == default */, 4, 5, 6, 7 };
|
|
size_t const srcSize = 256 KB;
|
|
size_t nb;
|
|
LZ4F_preferences_t bsidPrefs;
|
|
|
|
assert(srcSize <= COMPRESSIBLE_NOISE_LENGTH);
|
|
CHECK( LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) );
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressBegin rejects invalid blockSizeID : ");
|
|
for (nb = 0; nb < sizeof(invalidBSID) / sizeof(invalidBSID[0]); nb++) {
|
|
memset(&bsidPrefs, 0, sizeof(bsidPrefs));
|
|
bsidPrefs.frameInfo.blockSizeID = (LZ4F_blockSizeID_t)invalidBSID[nb];
|
|
{ size_t const r = LZ4F_compressBegin(cctx, compressedBuffer, cBuffSize, &bsidPrefs);
|
|
if (!LZ4F_isError(r)) {
|
|
DISPLAYLEVEL(3, "blockSizeID %i not rejected ! \n", invalidBSID[nb]);
|
|
goto _output_error;
|
|
} } }
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "valid blockSizeID still accepted : ");
|
|
for (nb = 0; nb < sizeof(validBSID) / sizeof(validBSID[0]); nb++) {
|
|
memset(&bsidPrefs, 0, sizeof(bsidPrefs));
|
|
bsidPrefs.frameInfo.blockSizeID = (LZ4F_blockSizeID_t)validBSID[nb];
|
|
CHECK( LZ4F_compressFrame(compressedBuffer, cBuffSize,
|
|
CNBuffer, srcSize, &bsidPrefs) );
|
|
}
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "failed LZ4F_compressBegin preserves cctx : ");
|
|
{ BYTE* const ostart = (BYTE*)compressedBuffer;
|
|
BYTE* op = ostart;
|
|
size_t cErr;
|
|
|
|
memset(&bsidPrefs, 0, sizeof(bsidPrefs));
|
|
bsidPrefs.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
bsidPrefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
|
|
CHECK_V(cErr, LZ4F_compressBegin(cctx, op, cBuffSize, &bsidPrefs));
|
|
op += cErr;
|
|
|
|
memset(&bsidPrefs, 0, sizeof(bsidPrefs)); /* must differ by more than blockSizeID */
|
|
bsidPrefs.frameInfo.blockSizeID = (LZ4F_blockSizeID_t)3; /* invalid */
|
|
cErr = LZ4F_compressBegin(cctx, op, cBuffSize, &bsidPrefs);
|
|
if (!LZ4F_isError(cErr)) goto _output_error;
|
|
|
|
/* resume the frame started above */
|
|
CHECK_V(cErr, LZ4F_compressUpdate(cctx, op, cBuffSize - (size_t)(op-ostart),
|
|
CNBuffer, srcSize, NULL));
|
|
op += cErr;
|
|
CHECK_V(cErr, LZ4F_compressEnd(cctx, op, cBuffSize - (size_t)(op-ostart), NULL));
|
|
op += cErr;
|
|
|
|
{ size_t iSize = (size_t)(op - ostart);
|
|
size_t decodedSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
LZ4F_decompressionContext_t dctx;
|
|
CHECK( LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) );
|
|
CHECK_V(cErr, LZ4F_decompress(dctx, decodedBuffer, &decodedSize, ostart, &iSize, NULL));
|
|
CHECK( LZ4F_freeDecompressionContext(dctx) );
|
|
if (cErr != 0) goto _output_error; /* frame must be entirely decoded */
|
|
if (decodedSize != srcSize) goto _output_error;
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedSize, extCrcSeed);
|
|
U64 const crcSrc = XXH64(CNBuffer, srcSize, extCrcSeed);
|
|
if (crcDest != crcSrc) goto _output_error;
|
|
} } }
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
CHECK( LZ4F_freeCompressionContext(cctx) );
|
|
cctx = NULL;
|
|
}
|
|
|
|
/* dictID tests */
|
|
{ size_t cErr;
|
|
U32 const dictID = 0x99;
|
|
|
|
/* test advanced variant with custom allocator functions */
|
|
cctx = LZ4F_createCompressionContext_advanced(lz4f_cmem_test, LZ4F_VERSION);
|
|
if (cctx==NULL) goto _output_error;
|
|
|
|
DISPLAYLEVEL(3, "insert a dictID : ");
|
|
memset(&prefs.frameInfo, 0, sizeof(prefs.frameInfo));
|
|
prefs.frameInfo.dictID = dictID;
|
|
CHECK_V(cErr, LZ4F_compressBegin(cctx, compressedBuffer, testSize, &prefs));
|
|
DISPLAYLEVEL(3, "created frame header of size %i bytes \n", (int)cErr);
|
|
|
|
DISPLAYLEVEL(3, "read a dictID : ");
|
|
CHECK( LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION) );
|
|
memset(&prefs.frameInfo, 0, sizeof(prefs.frameInfo));
|
|
CHECK( LZ4F_getFrameInfo(dCtx, &prefs.frameInfo, compressedBuffer, &cErr) );
|
|
if (prefs.frameInfo.dictID != dictID) goto _output_error;
|
|
DISPLAYLEVEL(3, "%u \n", (U32)prefs.frameInfo.dictID);
|
|
|
|
CHECK( LZ4F_freeDecompressionContext(dCtx) ); dCtx = NULL;
|
|
CHECK( LZ4F_freeCompressionContext(cctx) ); cctx = NULL;
|
|
}
|
|
|
|
/* Raw Dictionary compression test */
|
|
{ size_t const dictSize = 7 KB; /* small enough for LZ4_MEMORY_USAGE == 10 */
|
|
size_t const srcSize = 66 KB; /* must be > 64 KB to avoid short-size optimizations */
|
|
size_t const dstCapacity = LZ4F_compressFrameBound(srcSize, NULL);
|
|
size_t cSizeNoDict, cSizeWithDict;
|
|
const void* dict = CNBuffer;
|
|
const void* src = (const char*)CNBuffer + dictSize;
|
|
char* cPtr = (char*)compressedBuffer;
|
|
CHECK( LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) );
|
|
|
|
/* compress without dictionary, just to establish a comparison point */
|
|
CHECK_V(cSizeNoDict,
|
|
LZ4F_compressFrame(compressedBuffer, dstCapacity,
|
|
src, srcSize,
|
|
NULL) );
|
|
/* note: NULL preferences ==> 64 KB linked blocks */
|
|
|
|
/* now compress with dictionary */
|
|
DISPLAYLEVEL(3, "LZ4F_compressBegin_usingDict: ");
|
|
cSizeWithDict = 0;
|
|
{ size_t hSize = LZ4F_compressBegin_usingDict(cctx, cPtr, dstCapacity, dict, dictSize, NULL);
|
|
//size_t hSize = LZ4F_compressBegin(cctx, cPtr, dstCapacity, NULL);
|
|
CHECK(hSize);
|
|
cSizeWithDict += hSize;
|
|
cPtr += hSize;
|
|
}
|
|
{ size_t bSize = LZ4F_compressUpdate(cctx, cPtr, dstCapacity, src, srcSize, NULL);
|
|
CHECK(bSize);
|
|
cSizeWithDict += bSize;
|
|
cPtr += bSize;
|
|
}
|
|
{ size_t endSize = LZ4F_compressEnd(cctx, cPtr, dstCapacity, NULL);
|
|
CHECK(endSize);
|
|
cSizeWithDict += endSize;
|
|
}
|
|
DISPLAYLEVEL(3, "compress %u bytes into %u bytes with dict (< %u bytes without) \n",
|
|
(unsigned)srcSize, (unsigned)cSizeWithDict, (unsigned)cSizeNoDict);
|
|
if (cSizeWithDict >= cSizeNoDict) {
|
|
DISPLAYLEVEL(3, "cSizeWithDict (%u) should have been more compact than cSizeNoDict(%u) \n", (unsigned)cSizeWithDict, (unsigned)cSizeNoDict);
|
|
goto _output_error; /* must be more efficient */
|
|
}
|
|
crcOrig = XXH64(src, srcSize, 0);
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_decompress_usingDict: ");
|
|
{ LZ4F_dctx* dctx;
|
|
size_t decodedSize = srcSize;
|
|
size_t compressedSize = cSizeWithDict;
|
|
CHECK( LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) );
|
|
CHECK( LZ4F_decompress_usingDict(dctx,
|
|
decodedBuffer, &decodedSize,
|
|
compressedBuffer, &compressedSize,
|
|
CNBuffer, dictSize,
|
|
NULL) );
|
|
if (compressedSize != cSizeWithDict) goto _output_error;
|
|
if (decodedSize != srcSize) goto _output_error;
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedSize, 0);
|
|
if (crcDest != crcOrig) goto _output_error; }
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedSize);
|
|
CHECK( LZ4F_freeDecompressionContext(dctx) );
|
|
}
|
|
|
|
/* clean */
|
|
CHECK( LZ4F_freeCompressionContext(cctx) );
|
|
}
|
|
|
|
/* Digested Dictionary (cdict) compression test */
|
|
{ size_t const dictSize = 7 KB; /* small enough for LZ4_MEMORY_USAGE == 10 */
|
|
size_t const srcSize = 65 KB; /* must be > 64 KB to avoid short-size optimizations */
|
|
size_t const dstCapacity = LZ4F_compressFrameBound(srcSize, NULL);
|
|
size_t cSizeNoDict, cSizeWithDict;
|
|
LZ4F_CDict* cdict = NULL;
|
|
|
|
CHECK( LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) );
|
|
cdict = LZ4F_createCDict(CNBuffer, dictSize);
|
|
if (cdict == NULL)
|
|
goto _output_error;
|
|
|
|
DISPLAYLEVEL(3, "Testing LZ4F_createCDict_advanced : ");
|
|
{ LZ4F_CDict* const cda = LZ4F_createCDict_advanced(lz4f_cmem_test, CNBuffer, dictSize);
|
|
if (cda == NULL) goto _output_error;
|
|
LZ4F_freeCDict(cda);
|
|
}
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame_usingCDict, with NULL dict : ");
|
|
CHECK_V(cSizeNoDict,
|
|
LZ4F_compressFrame_usingCDict(cctx, compressedBuffer, dstCapacity,
|
|
CNBuffer, srcSize,
|
|
NULL, NULL) );
|
|
DISPLAYLEVEL(3, "%u bytes \n", (unsigned)cSizeNoDict);
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame_usingCDict, with dict : ");
|
|
CHECK( LZ4F_freeCompressionContext(cctx) );
|
|
CHECK( LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) );
|
|
CHECK_V(cSizeWithDict,
|
|
LZ4F_compressFrame_usingCDict(cctx, compressedBuffer, dstCapacity,
|
|
CNBuffer, srcSize,
|
|
cdict, NULL) );
|
|
DISPLAYLEVEL(3, "compressed %u bytes into %u bytes \n",
|
|
(unsigned)srcSize, (unsigned)cSizeWithDict);
|
|
if (cSizeWithDict > cSizeNoDict) {
|
|
DISPLAYLEVEL(3, "cSizeWithDict (%u) should have been more compact than cSizeNoDict(%u) \n", (unsigned)cSizeWithDict, (unsigned)cSizeNoDict);
|
|
goto _output_error; /* must be more efficient */
|
|
}
|
|
crcOrig = XXH64(CNBuffer, srcSize, 0);
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_decompress_usingDict : ");
|
|
{ LZ4F_dctx* dctx;
|
|
size_t decodedSize = srcSize;
|
|
size_t compressedSize = cSizeWithDict;
|
|
CHECK( LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) );
|
|
CHECK( LZ4F_decompress_usingDict(dctx,
|
|
decodedBuffer, &decodedSize,
|
|
compressedBuffer, &compressedSize,
|
|
CNBuffer, dictSize,
|
|
NULL) );
|
|
if (compressedSize != cSizeWithDict) goto _output_error;
|
|
if (decodedSize != srcSize) goto _output_error;
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedSize, 0);
|
|
if (crcDest != crcOrig) goto _output_error; }
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedSize);
|
|
CHECK( LZ4F_freeDecompressionContext(dctx) );
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame_usingCDict, with dict, negative level : ");
|
|
{ size_t cSizeLevelMax;
|
|
LZ4F_preferences_t cParams;
|
|
memset(&cParams, 0, sizeof(cParams));
|
|
cParams.compressionLevel = -3;
|
|
CHECK_V(cSizeLevelMax,
|
|
LZ4F_compressFrame_usingCDict(cctx, compressedBuffer, dstCapacity,
|
|
CNBuffer, dictSize,
|
|
cdict, &cParams) );
|
|
DISPLAYLEVEL(3, "%u bytes \n", (unsigned)cSizeLevelMax);
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame_usingCDict, with dict, level max : ");
|
|
{ size_t cSizeLevelMax;
|
|
LZ4F_preferences_t cParams;
|
|
memset(&cParams, 0, sizeof(cParams));
|
|
cParams.compressionLevel = LZ4F_compressionLevel_max();
|
|
CHECK_V(cSizeLevelMax,
|
|
LZ4F_compressFrame_usingCDict(cctx, compressedBuffer, dstCapacity,
|
|
CNBuffer, dictSize,
|
|
cdict, &cParams) );
|
|
DISPLAYLEVEL(3, "%u bytes \n", (unsigned)cSizeLevelMax);
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame_usingCDict, multiple linked blocks : ");
|
|
{ size_t cSizeContiguous;
|
|
size_t const inSize = dictSize * 3;
|
|
size_t const outCapacity = LZ4F_compressFrameBound(inSize, NULL);
|
|
LZ4F_preferences_t cParams;
|
|
memset(&cParams, 0, sizeof(cParams));
|
|
cParams.frameInfo.blockMode = LZ4F_blockLinked;
|
|
cParams.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
CHECK_V(cSizeContiguous,
|
|
LZ4F_compressFrame_usingCDict(cctx, compressedBuffer, outCapacity,
|
|
CNBuffer, inSize,
|
|
cdict, &cParams) );
|
|
DISPLAYLEVEL(3, "compressed %u bytes into %u bytes \n",
|
|
(unsigned)inSize, (unsigned)cSizeContiguous);
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_decompress_usingDict on multiple linked blocks : ");
|
|
{ LZ4F_dctx* dctx;
|
|
size_t decodedSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
size_t compressedSize = cSizeContiguous;
|
|
CHECK( LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) );
|
|
CHECK( LZ4F_decompress_usingDict(dctx,
|
|
decodedBuffer, &decodedSize,
|
|
compressedBuffer, &compressedSize,
|
|
CNBuffer, dictSize,
|
|
NULL) );
|
|
if (compressedSize != cSizeContiguous) goto _output_error;
|
|
if (decodedSize != inSize) goto _output_error;
|
|
crcOrig = XXH64(CNBuffer, inSize, 0);
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedSize, 0);
|
|
if (crcDest != crcOrig) goto _output_error; }
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedSize);
|
|
CHECK( LZ4F_freeDecompressionContext(dctx) );
|
|
}
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_compressFrame_usingCDict, multiple independent blocks : ");
|
|
{ size_t cSizeIndep;
|
|
size_t const inSize = dictSize * 3;
|
|
size_t const outCapacity = LZ4F_compressFrameBound(inSize, NULL);
|
|
LZ4F_preferences_t cParams;
|
|
memset(&cParams, 0, sizeof(cParams));
|
|
cParams.frameInfo.blockMode = LZ4F_blockIndependent;
|
|
cParams.frameInfo.blockSizeID = LZ4F_max64KB;
|
|
CHECK_V(cSizeIndep,
|
|
LZ4F_compressFrame_usingCDict(cctx, compressedBuffer, outCapacity,
|
|
CNBuffer, inSize,
|
|
cdict, &cParams) );
|
|
DISPLAYLEVEL(3, "compressed %u bytes into %u bytes \n",
|
|
(unsigned)inSize, (unsigned)cSizeIndep);
|
|
|
|
DISPLAYLEVEL(3, "LZ4F_decompress_usingDict on multiple independent blocks : ");
|
|
{ LZ4F_dctx* dctx;
|
|
size_t decodedSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
size_t compressedSize = cSizeIndep;
|
|
CHECK( LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) );
|
|
CHECK( LZ4F_decompress_usingDict(dctx,
|
|
decodedBuffer, &decodedSize,
|
|
compressedBuffer, &compressedSize,
|
|
CNBuffer, dictSize,
|
|
NULL) );
|
|
if (compressedSize != cSizeIndep) goto _output_error;
|
|
if (decodedSize != inSize) goto _output_error;
|
|
crcOrig = XXH64(CNBuffer, inSize, 0);
|
|
{ U64 const crcDest = XXH64(decodedBuffer, decodedSize, 0);
|
|
if (crcDest != crcOrig) goto _output_error; }
|
|
DISPLAYLEVEL(3, "Regenerated %u bytes \n", (U32)decodedSize);
|
|
CHECK( LZ4F_freeDecompressionContext(dctx) );
|
|
}
|
|
}
|
|
|
|
LZ4F_freeCDict(cdict);
|
|
CHECK( LZ4F_freeCompressionContext(cctx) ); cctx = NULL;
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "getBlockSize test: \n");
|
|
{ size_t result;
|
|
unsigned blockSizeID;
|
|
for (blockSizeID = 4; blockSizeID < 8; ++blockSizeID) {
|
|
result = LZ4F_getBlockSize((LZ4F_blockSizeID_t)blockSizeID);
|
|
CHECK(result);
|
|
DISPLAYLEVEL(3, "Returned block size of %u bytes for blockID %u \n",
|
|
(unsigned)result, blockSizeID);
|
|
}
|
|
|
|
/* Test an invalid input that's too large */
|
|
result = LZ4F_getBlockSize((LZ4F_blockSizeID_t)8);
|
|
if(!LZ4F_isError(result) ||
|
|
LZ4F_getErrorCode(result) != LZ4F_ERROR_maxBlockSize_invalid)
|
|
goto _output_error;
|
|
|
|
/* Test an invalid input that's too small */
|
|
result = LZ4F_getBlockSize((LZ4F_blockSizeID_t)3);
|
|
if(!LZ4F_isError(result) ||
|
|
LZ4F_getErrorCode(result) != LZ4F_ERROR_maxBlockSize_invalid)
|
|
goto _output_error;
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "check bug1227: reused dctx after error => ");
|
|
if (bug1227()) goto _output_error;
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAYLEVEL(3, "Skippable frame test : \n");
|
|
{ size_t decodedBufferSize = COMPRESSIBLE_NOISE_LENGTH;
|
|
unsigned maxBits = FUZ_highbit((U32)decodedBufferSize);
|
|
BYTE* op = (BYTE*)decodedBuffer;
|
|
BYTE* const oend = (BYTE*)decodedBuffer + COMPRESSIBLE_NOISE_LENGTH;
|
|
BYTE* ip = (BYTE*)compressedBuffer;
|
|
BYTE* iend = (BYTE*)compressedBuffer + cSize + 8;
|
|
|
|
CHECK( LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION) );
|
|
|
|
/* generate skippable frame */
|
|
FUZ_writeLE32(ip, LZ4F_MAGIC_SKIPPABLE_START);
|
|
FUZ_writeLE32(ip+4, (U32)cSize);
|
|
|
|
DISPLAYLEVEL(3, "random segment sizes : \n");
|
|
while (ip < iend) {
|
|
unsigned nbBits = FUZ_rand(randState) % maxBits;
|
|
size_t iSize = RAND_BITS(nbBits) + 1;
|
|
size_t oSize = (size_t)(oend-op);
|
|
if (iSize > (size_t)(iend-ip)) iSize = (size_t)(iend-ip);
|
|
CHECK( LZ4F_decompress(dCtx, op, &oSize, ip, &iSize, NULL) );
|
|
op += oSize;
|
|
ip += iSize;
|
|
}
|
|
DISPLAYLEVEL(3, "Skipped %i bytes \n", (int)decodedBufferSize);
|
|
|
|
/* generate zero-size skippable frame */
|
|
DISPLAYLEVEL(3, "zero-size skippable frame\n");
|
|
ip = (BYTE*)compressedBuffer;
|
|
op = (BYTE*)decodedBuffer;
|
|
FUZ_writeLE32(ip, LZ4F_MAGIC_SKIPPABLE_START+1);
|
|
FUZ_writeLE32(ip+4, 0);
|
|
iend = ip+8;
|
|
|
|
while (ip < iend) {
|
|
unsigned const nbBits = FUZ_rand(randState) % maxBits;
|
|
size_t iSize = RAND_BITS(nbBits) + 1;
|
|
size_t oSize = (size_t)(oend-op);
|
|
if (iSize > (size_t)(iend-ip)) iSize = (size_t)(iend-ip);
|
|
CHECK( LZ4F_decompress(dCtx, op, &oSize, ip, &iSize, NULL) );
|
|
op += oSize;
|
|
ip += iSize;
|
|
}
|
|
DISPLAYLEVEL(3, "Skipped %i bytes \n", (int)(ip - (BYTE*)compressedBuffer - 8));
|
|
|
|
DISPLAYLEVEL(3, "Skippable frame header complete in first call \n");
|
|
ip = (BYTE*)compressedBuffer;
|
|
op = (BYTE*)decodedBuffer;
|
|
FUZ_writeLE32(ip, LZ4F_MAGIC_SKIPPABLE_START+2);
|
|
FUZ_writeLE32(ip+4, 10);
|
|
iend = ip+18;
|
|
while (ip < iend) {
|
|
size_t iSize = 10;
|
|
size_t oSize = 10;
|
|
if (iSize > (size_t)(iend-ip)) iSize = (size_t)(iend-ip);
|
|
CHECK( LZ4F_decompress(dCtx, op, &oSize, ip, &iSize, NULL) );
|
|
op += oSize;
|
|
ip += iSize;
|
|
}
|
|
DISPLAYLEVEL(3, "Skipped %i bytes \n", (int)(ip - (BYTE*)compressedBuffer - 8));
|
|
}
|
|
|
|
DISPLAYLEVEL(3, "Context size test: ");
|
|
{
|
|
size_t c_result;
|
|
size_t d_result;
|
|
LZ4F_cctx* cc;
|
|
LZ4F_dctx* dc;
|
|
Test_alloc_state c_allocs;
|
|
Test_alloc_state d_allocs;
|
|
LZ4F_CustomMem c_mem = lz4f_cmem_test;
|
|
LZ4F_CustomMem d_mem = lz4f_cmem_test;
|
|
|
|
alloc_state_init(&c_allocs);
|
|
alloc_state_init(&d_allocs);
|
|
c_mem.opaqueState = &c_allocs;
|
|
d_mem.opaqueState = &d_allocs;
|
|
|
|
if (c_allocs.live_alloc_total_space != 0) goto _output_error;
|
|
if (d_allocs.live_alloc_total_space != 0) goto _output_error;
|
|
|
|
if (LZ4F_cctx_size(NULL) != 0) goto _output_error;
|
|
if (LZ4F_dctx_size(NULL) != 0) goto _output_error;
|
|
|
|
cc = LZ4F_createCompressionContext_advanced(c_mem, LZ4F_VERSION);
|
|
dc = LZ4F_createDecompressionContext_advanced(d_mem, LZ4F_VERSION);
|
|
|
|
if (cc == NULL) goto _output_error;
|
|
if (dc == NULL) goto _output_error;
|
|
|
|
if (LZ4F_cctx_size(cc) != c_allocs.live_alloc_total_space) goto _output_error;
|
|
if (LZ4F_dctx_size(dc) != d_allocs.live_alloc_total_space) goto _output_error;
|
|
|
|
c_result = LZ4F_compressFrame_usingCDict(
|
|
cc,
|
|
compressedBuffer, LZ4F_compressFrameBound(testSize, NULL),
|
|
CNBuffer, testSize,
|
|
NULL, NULL);
|
|
CHECK(c_result);
|
|
|
|
d_result = testSize + 1;
|
|
CHECK(LZ4F_decompress(dc, decodedBuffer, &d_result, compressedBuffer, &c_result, NULL));
|
|
|
|
if (d_result != testSize) goto _output_error;
|
|
|
|
if (LZ4F_cctx_size(cc) != c_allocs.live_alloc_total_space) {
|
|
DISPLAYLEVEL(3, "%llu allocated in cctx but it says its size is %llu.\n", (long long unsigned)c_allocs.live_alloc_total_space, (long long unsigned)LZ4F_cctx_size(cc));
|
|
goto _output_error;
|
|
}
|
|
if (LZ4F_dctx_size(dc) != d_allocs.live_alloc_total_space) {
|
|
DISPLAYLEVEL(3, "%llu allocated in dctx but it says its size is %llu.\n", (long long unsigned)d_allocs.live_alloc_total_space, (long long unsigned)LZ4F_dctx_size(dc));
|
|
goto _output_error;
|
|
}
|
|
|
|
/* sizes remain accurate when contexts are reused with different parameters,
|
|
* since buffers are only reallocated when too small */
|
|
{ struct { LZ4F_blockSizeID_t bsid; LZ4F_blockMode_t mode; int level; } const frames[] = {
|
|
{ LZ4F_max4MB, LZ4F_blockIndependent, 1 },
|
|
{ LZ4F_max64KB, LZ4F_blockIndependent, 1 }, /* smaller blocks : buffers are reused */
|
|
{ LZ4F_max256KB, LZ4F_blockLinked, 9 },
|
|
{ LZ4F_max64KB, LZ4F_blockLinked, 1 },
|
|
{ LZ4F_max1MB, LZ4F_blockIndependent, 9 },
|
|
{ LZ4F_max64KB, LZ4F_blockIndependent, 1 }
|
|
};
|
|
size_t const srcSize = 1 KB;
|
|
size_t f;
|
|
for (f = 0; f < sizeof(frames) / sizeof(frames[0]); f++) {
|
|
LZ4F_preferences_t fPrefs = LZ4F_INIT_PREFERENCES;
|
|
BYTE* const ostart = (BYTE*)compressedBuffer;
|
|
BYTE* op = ostart;
|
|
size_t r, frameSize, dSize = srcSize;
|
|
fPrefs.frameInfo.blockSizeID = frames[f].bsid;
|
|
fPrefs.frameInfo.blockMode = frames[f].mode;
|
|
fPrefs.compressionLevel = frames[f].level;
|
|
CHECK_V(r, LZ4F_compressBegin(cc, op, cBuffSize, &fPrefs));
|
|
op += r;
|
|
CHECK_V(r, LZ4F_compressUpdate(cc, op, cBuffSize - (size_t)(op - ostart), CNBuffer, srcSize, NULL));
|
|
op += r;
|
|
CHECK_V(r, LZ4F_compressEnd(cc, op, cBuffSize - (size_t)(op - ostart), NULL));
|
|
op += r;
|
|
frameSize = (size_t)(op - ostart);
|
|
CHECK_V(r, LZ4F_decompress(dc, decodedBuffer, &dSize, compressedBuffer, &frameSize, NULL));
|
|
if (r != 0 || dSize != srcSize) goto _output_error;
|
|
if (LZ4F_cctx_size(cc) != c_allocs.live_alloc_total_space) {
|
|
DISPLAYLEVEL(3, "frame %u : %llu allocated in cctx but it says its size is %llu.\n", (unsigned)f, (long long unsigned)c_allocs.live_alloc_total_space, (long long unsigned)LZ4F_cctx_size(cc));
|
|
goto _output_error;
|
|
}
|
|
if (LZ4F_dctx_size(dc) != d_allocs.live_alloc_total_space) {
|
|
DISPLAYLEVEL(3, "frame %u : %llu allocated in dctx but it says its size is %llu.\n", (unsigned)f, (long long unsigned)d_allocs.live_alloc_total_space, (long long unsigned)LZ4F_dctx_size(dc));
|
|
goto _output_error;
|
|
} } }
|
|
|
|
LZ4F_freeCompressionContext(cc);
|
|
LZ4F_freeDecompressionContext(dc);
|
|
alloc_state_destroy(&c_allocs);
|
|
alloc_state_destroy(&d_allocs);
|
|
}
|
|
DISPLAYLEVEL(3, "OK \n");
|
|
|
|
DISPLAY("Basic tests completed \n");
|
|
_end:
|
|
free(CNBuffer);
|
|
free(compressedBuffer);
|
|
free(decodedBuffer);
|
|
LZ4F_freeDecompressionContext(dCtx); dCtx = NULL;
|
|
LZ4F_freeCompressionContext(cctx); cctx = NULL;
|
|
alloc_state_destroy((Test_alloc_state*)lz4f_cmem_test.opaqueState);
|
|
return basicTests_error;
|
|
|
|
_output_error:
|
|
basicTests_error = 1;
|
|
DISPLAY("Error detected ! \n");
|
|
goto _end;
|
|
}
|
|
|
|
|
|
typedef enum { o_contiguous, o_noncontiguous, o_overwrite } o_scenario_e;
|
|
|
|
static void locateBuffDiff(const void* buff1, const void* buff2, size_t size, o_scenario_e o_scenario)
|
|
{
|
|
if (displayLevel >= 2) {
|
|
size_t p=0;
|
|
const BYTE* b1=(const BYTE*)buff1;
|
|
const BYTE* b2=(const BYTE*)buff2;
|
|
DISPLAY("locateBuffDiff: looking for error position \n");
|
|
if (o_scenario != o_contiguous) {
|
|
DISPLAY("mode %i: non-contiguous output (%u bytes), cannot search \n",
|
|
(int)o_scenario, (unsigned)size);
|
|
return;
|
|
}
|
|
while (p < size && b1[p]==b2[p]) p++;
|
|
if (p != size) {
|
|
DISPLAY("Error at pos %i/%i : %02X != %02X \n", (int)p, (int)size, b1[p], b2[p]);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define EXIT_MSG(...) { DISPLAY("Error => "); DISPLAY(__VA_ARGS__); \
|
|
DISPLAY(" (seed %u, test nb %u) \n", seed, testNb); exit(1); }
|
|
#undef CHECK
|
|
#define CHECK(cond, ...) { if (cond) { EXIT_MSG(__VA_ARGS__); } }
|
|
|
|
static LZ4F_errorCode_t LZ4F_returnErrorCode(LZ4F_errorCodes code)
|
|
{
|
|
return (LZ4F_errorCode_t)-(ptrdiff_t)code;
|
|
}
|
|
|
|
#define RETURN_ERROR(e) return LZ4F_returnErrorCode(LZ4F_ERROR_ ## e)
|
|
|
|
|
|
/* @o_scenario: output behavior: contiguous, non-contiguous, or overwrite
|
|
* @crcOrig: xxh64 of original data using extCrcSeed
|
|
* @return : 0 on success, or LZ4F_ErrorCode
|
|
* */
|
|
size_t test_lz4f_decompression_wBuffers(
|
|
const void* cSrc, size_t cSize,
|
|
void* dst, size_t dstCapacity,
|
|
o_scenario_e o_scenario,
|
|
const void* srcRef, size_t decompressedSize,
|
|
U64 crcOrig,
|
|
U32* const randState,
|
|
LZ4F_dctx* const dCtx,
|
|
U32 seed, U32 testNb,
|
|
int shouldSucceed)
|
|
{
|
|
const BYTE* ip = (const BYTE*)cSrc;
|
|
const BYTE* const iend = ip + cSize;
|
|
|
|
BYTE* op = (BYTE*)dst;
|
|
BYTE* const oend = op + dstCapacity;
|
|
|
|
unsigned const suggestedBits = FUZ_highbit((U32)cSize);
|
|
unsigned const maxBits = MAX(3, suggestedBits);
|
|
size_t totalOut = 0;
|
|
size_t moreToFlush = 0;
|
|
XXH64_state_t xxh64;
|
|
XXH64_reset(&xxh64, extCrcSeed);
|
|
o_scenario = o_contiguous;
|
|
assert(ip < iend);
|
|
while (ip < iend) {
|
|
unsigned const nbBitsI = (FUZ_rand(randState) % (maxBits-1)) + 1;
|
|
unsigned const nbBitsO = (FUZ_rand(randState) % (maxBits)) + 1;
|
|
size_t const iSizeCand = RAND_BITS(nbBitsI) + 1;
|
|
size_t const iSizeMax = MIN(iSizeCand, (size_t)(iend-ip));
|
|
size_t iSize = iSizeMax;
|
|
size_t const oSizeCand = RAND_BITS(nbBitsO) + 2;
|
|
size_t const oSizeMax = MIN(oSizeCand, (size_t)(oend-op));
|
|
int const sentinelTest = (op + oSizeMax < oend);
|
|
size_t oSize = oSizeMax;
|
|
BYTE const mark = (BYTE)(RAND_BITS(8));
|
|
LZ4F_decompressOptions_t dOptions;
|
|
memset(&dOptions, 0, sizeof(dOptions));
|
|
dOptions.stableDst = RAND_BITS(1);
|
|
if (o_scenario == o_overwrite) dOptions.stableDst = 0; /* overwrite mode */
|
|
dOptions.skipChecksums = RAND_BITS(1);
|
|
if (sentinelTest) op[oSizeMax] = mark;
|
|
|
|
DISPLAYLEVEL(7, "dstCapacity=%u, presentedInput=%u \n", (unsigned)oSize, (unsigned)iSize);
|
|
|
|
/* read data from byte-exact buffer to catch out-of-bound reads */
|
|
{ void* const iBuffer = malloc(iSizeMax);
|
|
/*test NULL supported if size==0 */
|
|
void* const tmpop = RAND_BITS(oSize == 0) ? NULL : op;
|
|
const void* const tmpip = RAND_BITS(iSize == 0) ? NULL : iBuffer;
|
|
assert(iBuffer != NULL);
|
|
memcpy(iBuffer, ip, iSizeMax);
|
|
moreToFlush = LZ4F_decompress(dCtx, tmpop, &oSize, tmpip, &iSize, &dOptions);
|
|
free(iBuffer);
|
|
}
|
|
DISPLAYLEVEL(7, "oSize=%u, readSize=%u \n", (unsigned)oSize, (unsigned)iSize);
|
|
|
|
if (sentinelTest) {
|
|
CHECK(op[oSizeMax] != mark, "op[oSizeMax] = %02X != %02X : "
|
|
"Decompression overwrites beyond assigned dst size",
|
|
op[oSizeMax], mark);
|
|
}
|
|
if (LZ4F_getErrorCode(moreToFlush) == LZ4F_ERROR_contentChecksum_invalid) {
|
|
if (shouldSucceed) DISPLAYLEVEL(1, "checksum error reported after decompressing segment with LZ4F_decompress() \n");
|
|
if (shouldSucceed) locateBuffDiff(srcRef, dst, decompressedSize, o_scenario);
|
|
RETURN_ERROR(contentChecksum_invalid);
|
|
}
|
|
if (LZ4F_isError(moreToFlush)) {
|
|
if (shouldSucceed) DISPLAYLEVEL(1, "error reported after decompressing segment with LZ4F_decompress() \n");
|
|
return moreToFlush;
|
|
}
|
|
|
|
XXH64_update(&xxh64, op, oSize);
|
|
totalOut += oSize;
|
|
op += oSize;
|
|
ip += iSize;
|
|
if (o_scenario == o_noncontiguous) {
|
|
if (op == oend) RETURN_ERROR(GENERIC); /* can theoretically happen with bogus data */
|
|
op++; /* create a gap between consecutive output */
|
|
}
|
|
if (o_scenario==o_overwrite) op = (BYTE*)dst; /* overwrite destination */
|
|
if ( (op == oend) /* no more room for output; can happen with bogus input */
|
|
&& (iSize == 0)) /* no input consumed */
|
|
break;
|
|
}
|
|
if (moreToFlush != 0) RETURN_ERROR(decompressionFailed);
|
|
if (totalOut) { /* otherwise, it's a skippable frame */
|
|
U64 const crcDecoded = XXH64_digest(&xxh64);
|
|
if (crcDecoded != crcOrig) {
|
|
if (shouldSucceed) DISPLAYLEVEL(1, "checksum error detected at end of decompression \n");
|
|
if (shouldSucceed) locateBuffDiff(srcRef, dst, decompressedSize, o_scenario);
|
|
RETURN_ERROR(contentChecksum_invalid);
|
|
} }
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* @return : 0 on success, or LZ4F_ErrorCode */
|
|
size_t test_lz4f_decompression(const void* cSrc, size_t cSize,
|
|
const void* srcRef, size_t decompressedSize,
|
|
U64 crcOrig,
|
|
U32* const randState,
|
|
LZ4F_dctx* const dCtx,
|
|
U32 seed, U32 testNb,
|
|
int findErrorPos)
|
|
{
|
|
o_scenario_e const o_scenario = (o_scenario_e)(FUZ_rand(randState) % 3); /* 0 : contiguous; 1 : non-contiguous; 2 : dst overwritten */
|
|
/* tighten dst buffer conditions */
|
|
size_t const dstCapacity = (o_scenario == o_noncontiguous) ?
|
|
(decompressedSize * 2) + 128 : /* should provide enough room to insert spaces beween blocks */
|
|
decompressedSize;
|
|
size_t result;
|
|
void* const dstBuffer = malloc(dstCapacity);
|
|
assert(dstBuffer != NULL);
|
|
|
|
result = test_lz4f_decompression_wBuffers(cSrc, cSize,
|
|
dstBuffer, dstCapacity, o_scenario,
|
|
srcRef, decompressedSize,
|
|
crcOrig,
|
|
randState,
|
|
dCtx,
|
|
seed, testNb, findErrorPos);
|
|
|
|
free(dstBuffer);
|
|
return result;
|
|
}
|
|
|
|
|
|
int fuzzerTests(U32 seed, unsigned nbTests, unsigned startTest, double compressibility, U32 duration_s)
|
|
{
|
|
unsigned testNb = 0;
|
|
size_t const CNBufferLength = 9 MB; /* needs to be > 2x4MB to test large blocks */
|
|
void* CNBuffer = NULL;
|
|
size_t const compressedBufferSize = LZ4F_compressFrameBound(CNBufferLength, NULL) + 4 MB; /* needs some margin */
|
|
void* compressedBuffer = NULL;
|
|
void* decodedBuffer = NULL;
|
|
U32 coreRand = seed;
|
|
LZ4F_decompressionContext_t dCtx = NULL;
|
|
LZ4F_compressionContext_t cCtx = NULL;
|
|
clock_t const startClock = clock();
|
|
clock_t const clockDuration = duration_s * CLOCKS_PER_SEC;
|
|
|
|
/* Create states & buffers */
|
|
{ size_t const creationStatus = LZ4F_createDecompressionContext(&dCtx, LZ4F_VERSION);
|
|
CHECK(LZ4F_isError(creationStatus), "Allocation failed (error %i)", (int)creationStatus); }
|
|
{ size_t const creationStatus = LZ4F_createCompressionContext(&cCtx, LZ4F_VERSION);
|
|
CHECK(LZ4F_isError(creationStatus), "Allocation failed (error %i)", (int)creationStatus); }
|
|
CNBuffer = malloc(CNBufferLength);
|
|
CHECK(CNBuffer==NULL, "CNBuffer Allocation failed");
|
|
compressedBuffer = malloc(compressedBufferSize);
|
|
CHECK(compressedBuffer==NULL, "compressedBuffer Allocation failed");
|
|
decodedBuffer = calloc(1, CNBufferLength); /* calloc avoids decodedBuffer being considered "garbage" by scan-build */
|
|
CHECK(decodedBuffer==NULL, "decodedBuffer Allocation failed");
|
|
FUZ_fillCompressibleNoiseBuffer(CNBuffer, CNBufferLength, compressibility, &coreRand);
|
|
|
|
/* jump to requested testNb */
|
|
for (testNb =0; (testNb < startTest); testNb++) (void)FUZ_rand(&coreRand); /* sync randomizer */
|
|
|
|
/* main fuzzer test loop */
|
|
for ( ; (testNb < nbTests) || (clockDuration > FUZ_GetClockSpan(startClock)) ; testNb++) {
|
|
U32 randState = coreRand ^ prime1;
|
|
unsigned const srcBits = (FUZ_rand(&randState) % (FUZ_highbit((U32)(CNBufferLength-1)) - 1)) + 1;
|
|
size_t const srcSize = (FUZ_rand(&randState) & ((1<<srcBits)-1));
|
|
size_t const srcStartId = FUZ_rand(&randState) % (CNBufferLength - srcSize);
|
|
const BYTE* const srcStart = (const BYTE*)CNBuffer + srcStartId;
|
|
unsigned const neverFlush = (FUZ_rand(&randState) & 15) == 1;
|
|
U64 const crcOrig = XXH64(srcStart, srcSize, extCrcSeed);
|
|
LZ4F_preferences_t prefs;
|
|
const LZ4F_preferences_t* prefsPtr = &prefs;
|
|
size_t cSize;
|
|
|
|
(void)FUZ_rand(&coreRand); /* update seed */
|
|
memset(&prefs, 0, sizeof(prefs));
|
|
prefs.frameInfo.blockMode = (LZ4F_blockMode_t)(FUZ_rand(&randState) & 1);
|
|
prefs.frameInfo.blockSizeID = (LZ4F_blockSizeID_t)(4 + (FUZ_rand(&randState) & 3));
|
|
prefs.frameInfo.blockChecksumFlag = (LZ4F_blockChecksum_t)(FUZ_rand(&randState) & 1);
|
|
prefs.frameInfo.contentChecksumFlag = (LZ4F_contentChecksum_t)(FUZ_rand(&randState) & 1);
|
|
prefs.frameInfo.contentSize = ((FUZ_rand(&randState) & 0xF) == 1) ? srcSize : 0;
|
|
prefs.autoFlush = neverFlush ? 0 : (FUZ_rand(&randState) & 7) == 2;
|
|
prefs.compressionLevel = -5 + (int)(FUZ_rand(&randState) % 11);
|
|
if ((FUZ_rand(&randState) & 0xF) == 1) prefsPtr = NULL;
|
|
|
|
DISPLAYUPDATE(2, "\r%5u ", testNb);
|
|
|
|
if ((FUZ_rand(&randState) & 0xFFF) == 0) {
|
|
/* create a skippable frame (rare case) */
|
|
BYTE* op = (BYTE*)compressedBuffer;
|
|
FUZ_writeLE32(op, LZ4F_MAGIC_SKIPPABLE_START + (FUZ_rand(&randState) & 15));
|
|
FUZ_writeLE32(op+4, (U32)srcSize);
|
|
cSize = srcSize+8;
|
|
|
|
} else if ((FUZ_rand(&randState) & 0xF) == 2) { /* single pass compression (simple) */
|
|
cSize = LZ4F_compressFrame(compressedBuffer, LZ4F_compressFrameBound(srcSize, prefsPtr), srcStart, srcSize, prefsPtr);
|
|
CHECK(LZ4F_isError(cSize), "LZ4F_compressFrame failed : error %i (%s)", (int)cSize, LZ4F_getErrorName(cSize));
|
|
|
|
if ((FUZ_rand(&randState) & 3) == 1) {
|
|
/* any capacity : must succeed if and only if the frame fits, producing the same frame */
|
|
size_t const bound = LZ4F_compressFrameBound(srcSize, prefsPtr);
|
|
size_t const delta = FUZ_rand(&randState) % 16;
|
|
size_t dstCapacity;
|
|
switch (FUZ_rand(&randState) & 3) {
|
|
case 0: dstCapacity = FUZ_rand(&randState) % (bound + 1); break;
|
|
case 1: dstCapacity = cSize - 1 - MIN(delta, cSize - 1); break;
|
|
case 2: dstCapacity = MIN(cSize + delta, bound); break;
|
|
default: dstCapacity = cSize; break;
|
|
}
|
|
{ BYTE* const dst = (BYTE*)malloc(dstCapacity + 1);
|
|
BYTE const canaryByte = (BYTE)FUZ_rand(&randState);
|
|
size_t r;
|
|
CHECK(dst == NULL, "allocation failed");
|
|
dst[dstCapacity] = canaryByte;
|
|
r = LZ4F_compressFrame(dst, dstCapacity, srcStart, srcSize, prefsPtr);
|
|
CHECK(dst[dstCapacity] != canaryByte, "LZ4F_compressFrame writes beyond dstCapacity");
|
|
if (dstCapacity >= cSize) {
|
|
CHECK(r != cSize, "LZ4F_compressFrame into %u bytes (frame size %u) : %s",
|
|
(unsigned)dstCapacity, (unsigned)cSize,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "different frame size");
|
|
CHECK(memcmp(dst, compressedBuffer, cSize),
|
|
"LZ4F_compressFrame into %u bytes : different frame content", (unsigned)dstCapacity);
|
|
} else {
|
|
CHECK(LZ4F_getErrorCode(r) != LZ4F_ERROR_dstCapacity_tooSmall,
|
|
"LZ4F_compressFrame into %u bytes (frame size %u) : %s",
|
|
(unsigned)dstCapacity, (unsigned)cSize,
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
}
|
|
free(dst);
|
|
} }
|
|
|
|
} else { /* multi-segments compression */
|
|
const BYTE* ip = srcStart;
|
|
const BYTE* const iend = srcStart + srcSize;
|
|
BYTE* op = (BYTE*)compressedBuffer;
|
|
BYTE* const oend = op + (neverFlush ? LZ4F_compressFrameBound(srcSize, prefsPtr) : compressedBufferSize); /* when flushes are possible, can't guarantee a max compressed size */
|
|
unsigned const maxBits = FUZ_highbit((U32)srcSize);
|
|
LZ4F_compressOptions_t cOptions;
|
|
int frameAbandoned = 0;
|
|
memset(&cOptions, 0, sizeof(cOptions));
|
|
{ size_t const fhSize = LZ4F_compressBegin(cCtx, op, (size_t)(oend-op), prefsPtr);
|
|
CHECK(LZ4F_isError(fhSize), "Compression header failed (error %i)",
|
|
(int)fhSize);
|
|
op += fhSize;
|
|
}
|
|
while (ip < iend) {
|
|
unsigned const nbBitsSeg = FUZ_rand(&randState) % maxBits;
|
|
size_t sampleMax = (FUZ_rand(&randState) & ((1<<nbBitsSeg)-1)) + 1;
|
|
unsigned const uncompressed = !neverFlush && prefsPtr != NULL
|
|
&& prefs.frameInfo.blockMode == LZ4F_blockIndependent
|
|
&& (FUZ_rand(&randState) & 15) == 0;
|
|
/* Regularly cross block boundaries after buffering a partial block.
|
|
* Choosing each segment's mode independently exercises both switches. */
|
|
if (!neverFlush && prefsPtr != NULL
|
|
&& prefs.frameInfo.blockMode == LZ4F_blockIndependent
|
|
&& (FUZ_rand(&randState) & 3) == 0) {
|
|
size_t const blockSize = LZ4F_getBlockSize(prefs.frameInfo.blockSizeID);
|
|
unsigned const edge = FUZ_rand(&randState) & 3;
|
|
sampleMax = edge == 3 ? 2 * blockSize : blockSize + edge - 1;
|
|
}
|
|
cOptions.stableSrc = ((FUZ_rand(&randState) & 3) == 1);
|
|
|
|
{ size_t const iSize = MIN(sampleMax, (size_t)(iend-ip));
|
|
size_t const oSize = LZ4F_compressBound(iSize, prefsPtr);
|
|
/* occasionally provide less than the bound : the update may then fail */
|
|
unsigned const smallDst = (FUZ_rand(&randState) & 31) == 7;
|
|
size_t const dstCapacity = smallDst ? FUZ_rand(&randState) % (oSize + 1) : oSize;
|
|
BYTE* const dst = (BYTE*)malloc(dstCapacity + 1);
|
|
BYTE const canaryByte = (BYTE)FUZ_rand(&randState);
|
|
size_t flushedSize;
|
|
CHECK(dst == NULL, "Update output allocation failed");
|
|
dst[dstCapacity] = canaryByte;
|
|
DISPLAYLEVEL(6, "Sending %u bytes (uncompressed:%u, stableSrc:%u, capacity:%u) \n",
|
|
(unsigned)iSize, uncompressed, cOptions.stableSrc, (unsigned)dstCapacity);
|
|
flushedSize = uncompressed
|
|
? LZ4F_uncompressedUpdate(cCtx, dst, dstCapacity, ip, iSize, &cOptions)
|
|
: LZ4F_compressUpdate(cCtx, dst, dstCapacity, ip, iSize, &cOptions);
|
|
CHECK(dst[dstCapacity] != canaryByte, "Update writes beyond dstCapacity (uncompressed:%u)", uncompressed);
|
|
if (smallDst && LZ4F_isError(flushedSize)) {
|
|
CHECK(LZ4F_getErrorCode(flushedSize) != LZ4F_ERROR_dstCapacity_tooSmall,
|
|
"Update into %u bytes (bound %u) : unexpected error %s",
|
|
(unsigned)dstCapacity, (unsigned)oSize, LZ4F_getErrorName(flushedSize));
|
|
free(dst);
|
|
frameAbandoned = 1;
|
|
break;
|
|
}
|
|
CHECK(LZ4F_isError(flushedSize), "Compression failed (error %i : %s)",
|
|
(int)flushedSize, LZ4F_getErrorName(flushedSize));
|
|
CHECK(flushedSize > dstCapacity, "Update exceeds compressBound (uncompressed:%u)", uncompressed);
|
|
CHECK(flushedSize > (size_t)(oend-op), "Compression output buffer too small");
|
|
memcpy(op, dst, flushedSize);
|
|
free(dst);
|
|
op += flushedSize;
|
|
ip += iSize;
|
|
}
|
|
|
|
{ unsigned const forceFlush = neverFlush ? 0 : ((FUZ_rand(&randState) & 3) == 1);
|
|
if (forceFlush) {
|
|
/* occasionally provide less than the bound : the flush may then fail */
|
|
unsigned const smallDst = (FUZ_rand(&randState) & 31) == 7;
|
|
size_t const smallCapacity = FUZ_rand(&randState) % (LZ4F_compressBound(0, prefsPtr) + 1);
|
|
size_t const flushCapacity = smallDst
|
|
? MIN(smallCapacity, (size_t)(oend-op) - 1)
|
|
: (size_t)(oend-op);
|
|
BYTE const canaryByte = (BYTE)FUZ_rand(&randState);
|
|
size_t flushSize;
|
|
if (smallDst) op[flushCapacity] = canaryByte;
|
|
flushSize = LZ4F_flush(cCtx, op, flushCapacity, &cOptions);
|
|
DISPLAYLEVEL(6, "flushing %u bytes \n", (unsigned)flushSize);
|
|
if (smallDst) {
|
|
CHECK(op[flushCapacity] != canaryByte, "LZ4F_flush writes beyond dstCapacity");
|
|
if (LZ4F_isError(flushSize)) {
|
|
CHECK(LZ4F_getErrorCode(flushSize) != LZ4F_ERROR_dstCapacity_tooSmall,
|
|
"Flush into %u bytes : unexpected error %s",
|
|
(unsigned)flushCapacity, LZ4F_getErrorName(flushSize));
|
|
frameAbandoned = 1;
|
|
break;
|
|
} }
|
|
CHECK(LZ4F_isError(flushSize), "Compression failed (error %i)", (int)flushSize);
|
|
op += flushSize;
|
|
if ((FUZ_rand(&randState) % 1024) == 3) {
|
|
/* add an empty block (requires uncompressed flag) */
|
|
op[0] = op[1] = op[2] = 0;
|
|
op[3] = 0x80; /* 0x80000000U in little-endian format */
|
|
op += 4;
|
|
if ((prefsPtr!= NULL) && prefsPtr->frameInfo.blockChecksumFlag) {
|
|
/* add block checksum (even for empty blocks) */
|
|
FUZ_writeLE32(op, XXH32(op, 0, 0));
|
|
op += 4;
|
|
} } } }
|
|
} /* while (ip<iend) */
|
|
|
|
if (frameAbandoned) {
|
|
/* after a failed operation, the frame can't be continued */
|
|
size_t const r = LZ4F_compressEnd(cCtx, op, (size_t)(oend-op), &cOptions);
|
|
CHECK(LZ4F_getErrorCode(r) != LZ4F_ERROR_compressionState_uninitialized,
|
|
"LZ4F_compressEnd after a failed operation : %s",
|
|
LZ4F_isError(r) ? LZ4F_getErrorName(r) : "should have failed");
|
|
continue;
|
|
}
|
|
|
|
/* check compressBound guarantees */
|
|
if (neverFlush) CHECK(op>=oend, "LZ4F_compressFrameBound overflow");
|
|
|
|
{ size_t const dstEndSafeSize = LZ4F_compressBound(0, prefsPtr);
|
|
int const tooSmallDstEnd = ((FUZ_rand(&randState) & 31) == 3);
|
|
size_t const dstEndTooSmallSize = (FUZ_rand(&randState) % dstEndSafeSize) + 1;
|
|
size_t const dstEndSize = tooSmallDstEnd ? dstEndTooSmallSize : dstEndSafeSize;
|
|
BYTE const canaryByte = (BYTE)(FUZ_rand(&randState) & 255);
|
|
size_t flushedSize;
|
|
DISPLAYLEVEL(7,"canaryByte at pos %u / %u \n",
|
|
(unsigned)((size_t)(op - (BYTE*)compressedBuffer) + dstEndSize),
|
|
(unsigned)compressedBufferSize);
|
|
assert(op + dstEndSize < (BYTE*)compressedBuffer + compressedBufferSize);
|
|
op[dstEndSize] = canaryByte;
|
|
flushedSize = LZ4F_compressEnd(cCtx, op, dstEndSize, &cOptions);
|
|
CHECK(op[dstEndSize] != canaryByte, "LZ4F_compressEnd writes beyond dstCapacity !");
|
|
if (LZ4F_isError(flushedSize)) {
|
|
CHECK(!tooSmallDstEnd, "Compression completion failed (error %i : %s)",
|
|
(int)flushedSize, LZ4F_getErrorName(flushedSize));
|
|
/* failure is allowed */
|
|
CHECK(LZ4F_getErrorCode(flushedSize) != LZ4F_ERROR_dstCapacity_tooSmall,
|
|
"LZ4F_compressEnd into %u bytes : unexpected error %s",
|
|
(unsigned)dstEndSize, LZ4F_getErrorName(flushedSize));
|
|
/* the frame can't be completed anymore */
|
|
flushedSize = LZ4F_compressEnd(cCtx, op, dstEndSafeSize, &cOptions);
|
|
CHECK(LZ4F_getErrorCode(flushedSize) != LZ4F_ERROR_compressionState_uninitialized,
|
|
"LZ4F_compressEnd after a failed LZ4F_compressEnd : %s",
|
|
LZ4F_isError(flushedSize) ? LZ4F_getErrorName(flushedSize) : "should have failed");
|
|
continue;
|
|
}
|
|
op += flushedSize;
|
|
}
|
|
cSize = (size_t)(op - (BYTE*)compressedBuffer);
|
|
DISPLAYLEVEL(5, "\nCompressed %u bytes into %u \n", (U32)srcSize, (U32)cSize);
|
|
}
|
|
|
|
|
|
/* multi-segments decompression */
|
|
DISPLAYLEVEL(6, "normal decompression \n");
|
|
{ size_t result = test_lz4f_decompression(compressedBuffer, cSize, srcStart, srcSize, crcOrig, &randState, dCtx, seed, testNb, 1 /*findError*/ );
|
|
CHECK (LZ4F_isError(result), "multi-segment decompression failed (error %i => %s)",
|
|
(int)result, LZ4F_getErrorName(result));
|
|
}
|
|
|
|
/* insert noise into src - ensure decoder survives with no sanitizer error */
|
|
{ U32 const maxNbBits = FUZ_highbit((U32)cSize);
|
|
size_t pos = 0;
|
|
for (;;) {
|
|
/* keep some original src */
|
|
{ U32 const nbBits = FUZ_rand(&randState) % maxNbBits;
|
|
size_t const mask = (1<<nbBits) - 1;
|
|
size_t const skipLength = FUZ_rand(&randState) & mask;
|
|
pos += skipLength;
|
|
}
|
|
if (pos >= cSize) break;
|
|
/* add noise */
|
|
{ U32 const nbBitsCodes = FUZ_rand(&randState) % maxNbBits;
|
|
U32 const nbBits = nbBitsCodes ? nbBitsCodes-1 : 0;
|
|
size_t const mask = (1<<nbBits) - 1;
|
|
size_t const rNoiseLength = (FUZ_rand(&randState) & mask) + 1;
|
|
size_t const noiseLength = MIN(rNoiseLength, cSize-pos);
|
|
size_t const noiseStart = FUZ_rand(&randState) % (CNBufferLength - noiseLength);
|
|
memcpy((BYTE*)compressedBuffer + pos, (const char*)CNBuffer + noiseStart, noiseLength);
|
|
pos += noiseLength;
|
|
} } }
|
|
|
|
/* test decompression on noisy src */
|
|
DISPLAYLEVEL(6, "noisy decompression \n");
|
|
test_lz4f_decompression(compressedBuffer, cSize, srcStart, srcSize, crcOrig, &randState, dCtx, seed, testNb, 0 /*don't search error Pos*/ );
|
|
/* note : we don't analyze result here : it probably failed, which is expected.
|
|
* The sole purpose is to catch potential out-of-bound reads and writes. */
|
|
LZ4F_resetDecompressionContext(dCtx); /* state must be reset to clean after an error */
|
|
|
|
} /* for ( ; (testNb < nbTests) ; ) */
|
|
|
|
DISPLAYLEVEL(2, "\rAll tests completed \n");
|
|
|
|
LZ4F_freeDecompressionContext(dCtx);
|
|
LZ4F_freeCompressionContext(cCtx);
|
|
free(CNBuffer);
|
|
free(compressedBuffer);
|
|
free(decodedBuffer);
|
|
|
|
if (use_pause) {
|
|
DISPLAY("press enter to finish \n");
|
|
(void)getchar();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int FUZ_usage(const char* programName)
|
|
{
|
|
DISPLAY( "Usage :\n");
|
|
DISPLAY( " %s [args]\n", programName);
|
|
DISPLAY( "\n");
|
|
DISPLAY( "Arguments :\n");
|
|
DISPLAY( " -i# : Nb of tests (default:%u) \n", nbTestsDefault);
|
|
DISPLAY( " -T# : Duration of tests, in seconds (default: use Nb of tests) \n");
|
|
DISPLAY( " -s# : Select seed (default:prompt user)\n");
|
|
DISPLAY( " -t# : Select starting test number (default:0)\n");
|
|
DISPLAY( " -P# : Select compressibility in %% (default:%i%%)\n", FUZ_COMPRESSIBILITY_DEFAULT);
|
|
DISPLAY( " -v : verbose\n");
|
|
DISPLAY( " -h : display help and exit\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
int main(int argc, const char** argv)
|
|
{
|
|
U32 seed=0;
|
|
int seedset=0;
|
|
int argNb;
|
|
unsigned nbTests = nbTestsDefault;
|
|
unsigned testNb = 0;
|
|
int proba = FUZ_COMPRESSIBILITY_DEFAULT;
|
|
U32 duration=0;
|
|
const char* const programName = argv[0];
|
|
|
|
/* Check command line */
|
|
for (argNb=1; argNb<argc; argNb++) {
|
|
const char* argument = argv[argNb];
|
|
|
|
if(!argument) continue; /* Protection if argument empty */
|
|
|
|
/* Decode command (note : aggregated short commands are allowed) */
|
|
if (argument[0]=='-') {
|
|
if (!strcmp(argument, "--no-prompt")) {
|
|
no_prompt=1;
|
|
seedset=1;
|
|
displayLevel=1;
|
|
continue;
|
|
}
|
|
argument++;
|
|
|
|
while (*argument!=0) {
|
|
switch(*argument)
|
|
{
|
|
case 'h':
|
|
return FUZ_usage(programName);
|
|
case 'v':
|
|
argument++;
|
|
displayLevel++;
|
|
break;
|
|
case 'q':
|
|
argument++;
|
|
displayLevel--;
|
|
break;
|
|
case 'p': /* pause at the end */
|
|
argument++;
|
|
use_pause = 1;
|
|
break;
|
|
|
|
case 'i':
|
|
argument++;
|
|
nbTests=0; duration=0;
|
|
while ((*argument>='0') && (*argument<='9')) {
|
|
nbTests *= 10;
|
|
nbTests += (unsigned)(*argument - '0');
|
|
argument++;
|
|
}
|
|
break;
|
|
|
|
case 'T':
|
|
argument++;
|
|
nbTests = 0; duration = 0;
|
|
for (;;) {
|
|
switch(*argument)
|
|
{
|
|
case 'm': duration *= 60; argument++; continue;
|
|
case 's':
|
|
case 'n': argument++; continue;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9': duration *= 10; duration += (U32)(*argument++ - '0'); continue;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
argument++;
|
|
seed=0;
|
|
seedset=1;
|
|
while ((*argument>='0') && (*argument<='9')) {
|
|
seed *= 10;
|
|
seed += (U32)(*argument - '0');
|
|
argument++;
|
|
}
|
|
break;
|
|
case 't':
|
|
argument++;
|
|
testNb=0;
|
|
while ((*argument>='0') && (*argument<='9')) {
|
|
testNb *= 10;
|
|
testNb += (unsigned)(*argument - '0');
|
|
argument++;
|
|
}
|
|
break;
|
|
case 'P': /* compressibility % */
|
|
argument++;
|
|
proba=0;
|
|
while ((*argument>='0') && (*argument<='9')) {
|
|
proba *= 10;
|
|
proba += *argument - '0';
|
|
argument++;
|
|
}
|
|
if (proba<0) proba=0;
|
|
if (proba>100) proba=100;
|
|
break;
|
|
default:
|
|
;
|
|
return FUZ_usage(programName);
|
|
}
|
|
}
|
|
} /* if (argument[0]=='-') */
|
|
} /* for (argNb=1; argNb<argc; argNb++) */
|
|
|
|
DISPLAY("Starting lz4frame tester (%i-bits, %s) \n", (int)(sizeof(size_t)*8), LZ4_VERSION_STRING);
|
|
|
|
/* Select a random seed if none given */
|
|
if (!seedset) {
|
|
time_t const t = time(NULL);
|
|
U32 const h = XXH32(&t, sizeof(t), 1);
|
|
seed = h % 10000;
|
|
}
|
|
DISPLAY("Seed = %u \n", seed);
|
|
if (proba != FUZ_COMPRESSIBILITY_DEFAULT)
|
|
DISPLAY("Compressibility : %i%% \n", proba);
|
|
|
|
{ double const compressibility = (double)proba / 100;
|
|
/* start by unit tests if not requesting a specific run nb */
|
|
if (testNb==0) {
|
|
if (unitTests(seed, compressibility))
|
|
return 1;
|
|
}
|
|
|
|
nbTests += (nbTests==0); /* avoid zero */
|
|
return fuzzerTests(seed, nbTests, testNb, compressibility, duration);
|
|
}
|
|
}
|