feat(rust): migrate high-level runtime paths

Move long-distance matching and high-level decompression from C shims into
Rust. The decoder now owns context, dictionary, parameter, one-shot, and
buffered streaming state while C retains allocation/configuration, legacy,
and trace leaves.

Move CLI parsing, safety policy, and dispatch into a separate Rust static
archive. Keeping it separate prevents library builds from retaining FIO
symbols, while C continues to own file opening, replacement, and I/O.
Program targets now select matching compression/decompression archives.

The remaining C boundary is intentional: high-level compression, optimal
parsing, dictionary building, legacy callbacks, and CLI file I/O still need
migration.

Test Plan:
- cargo test --all-targets (native and i686)
- cargo test --all-targets in rust/cli (native and i686)
- CLI crate compression-only and decompression-only feature tests
- native and i686 fuzzer/zstreamtest runs, plus legacy and dictionary tests
- ZSTD_C_PREDICT and ZSTD_HEAPMODE=0 fuzzer coverage
- library, dynamic-link, and program-target build/round-trip matrix

Refs: rust/README.md
This commit is contained in:
2026-07-11 09:03:41 +02:00
parent 031592da1e
commit fe7e24c770
14 changed files with 6886 additions and 4651 deletions
+271 -673
View File
@@ -8,265 +8,225 @@
* You may select, at your option, one of the above-listed licenses. * You may select, at your option, one of the above-listed licenses.
*/ */
/* The LDM algorithms live in rust/src/zstd_ldm.rs. This file retains private
* match-state extraction and block-compressor dispatch only. */
#include "zstd_ldm.h" #include "zstd_ldm.h"
#include "../common/debug.h" #include "../common/debug.h"
#include "../common/xxhash.h" #include "zstd_fast.h"
#include "zstd_fast.h" /* ZSTD_fillHashTable() */ #include "zstd_double_fast.h"
#include "zstd_double_fast.h" /* ZSTD_fillDoubleHashTable() */
#include "zstd_ldm_geartab.h" #include "zstd_ldm_geartab.h"
#define LDM_BUCKET_SIZE_LOG 4 typedef struct {
#define LDM_MIN_MATCH_LENGTH 64 U32 offset;
#define LDM_HASH_RLOG 7 U32 checksum;
} ZSTD_rust_ldm_entry_layout;
typedef struct { typedef struct {
U64 rolling; U32 offset;
U64 stopMask; U32 litLength;
} ldmRollingHashState_t; U32 matchLength;
} ZSTD_rust_raw_seq_layout;
/** ZSTD_ldm_gear_init(): typedef struct {
* rawSeq* seq;
* Initializes the rolling hash state such that it will honor the size_t pos;
* settings in params. */ size_t posInSequence;
static void ZSTD_ldm_gear_init(ldmRollingHashState_t* state, ldmParams_t const* params) size_t size;
size_t capacity;
} ZSTD_rust_raw_seq_store_layout;
typedef struct {
ZSTD_ParamSwitch_e enableLdm;
U32 hashLog;
U32 bucketSizeLog;
U32 minMatchLength;
U32 hashRateLog;
U32 windowLog;
} ZSTD_rust_ldm_params_layout;
typedef struct {
BYTE const* nextSrc;
BYTE const* base;
BYTE const* dictBase;
U32 dictLimit;
U32 lowLimit;
U32 nbOverflowCorrections;
} ZSTD_rust_ldm_window_layout;
typedef char ZSTD_rust_ldm_entry_layout_check[
sizeof(ldmEntry_t) == sizeof(ZSTD_rust_ldm_entry_layout) ? 1 : -1];
typedef char ZSTD_rust_raw_seq_layout_check[
sizeof(rawSeq) == sizeof(ZSTD_rust_raw_seq_layout) ? 1 : -1];
typedef char ZSTD_rust_raw_seq_store_layout_check[
sizeof(RawSeqStore_t) == sizeof(ZSTD_rust_raw_seq_store_layout) ? 1 : -1];
typedef char ZSTD_rust_ldm_params_layout_check[
sizeof(ldmParams_t) == sizeof(ZSTD_rust_ldm_params_layout) ? 1 : -1];
typedef char ZSTD_rust_ldm_window_layout_check[
sizeof(ZSTD_window_t) == sizeof(ZSTD_rust_ldm_window_layout) ? 1 : -1];
typedef char ZSTD_rust_param_switch_layout_check[
sizeof(ZSTD_ParamSwitch_e) == sizeof(int) ? 1 : -1];
#define ZSTD_RUST_LDM_OFFSET_CHECK(name, c_type, c_field, rust_type, rust_field) \
typedef char name[offsetof(c_type, c_field) == offsetof(rust_type, rust_field) ? 1 : -1]
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_entry_offset_check,
ldmEntry_t, offset,
ZSTD_rust_ldm_entry_layout, offset);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_entry_checksum_check,
ldmEntry_t, checksum,
ZSTD_rust_ldm_entry_layout, checksum);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_offset_check,
rawSeq, offset,
ZSTD_rust_raw_seq_layout, offset);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_lit_length_check,
rawSeq, litLength,
ZSTD_rust_raw_seq_layout, litLength);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_match_length_check,
rawSeq, matchLength,
ZSTD_rust_raw_seq_layout, matchLength);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_store_seq_check,
RawSeqStore_t, seq,
ZSTD_rust_raw_seq_store_layout, seq);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_store_pos_check,
RawSeqStore_t, pos,
ZSTD_rust_raw_seq_store_layout, pos);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_store_pos_in_sequence_check,
RawSeqStore_t, posInSequence,
ZSTD_rust_raw_seq_store_layout, posInSequence);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_store_size_check,
RawSeqStore_t, size,
ZSTD_rust_raw_seq_store_layout, size);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_raw_seq_store_capacity_check,
RawSeqStore_t, capacity,
ZSTD_rust_raw_seq_store_layout, capacity);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_params_enable_check,
ldmParams_t, enableLdm,
ZSTD_rust_ldm_params_layout, enableLdm);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_params_hash_log_check,
ldmParams_t, hashLog,
ZSTD_rust_ldm_params_layout, hashLog);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_params_bucket_size_log_check,
ldmParams_t, bucketSizeLog,
ZSTD_rust_ldm_params_layout, bucketSizeLog);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_params_min_match_length_check,
ldmParams_t, minMatchLength,
ZSTD_rust_ldm_params_layout, minMatchLength);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_params_hash_rate_log_check,
ldmParams_t, hashRateLog,
ZSTD_rust_ldm_params_layout, hashRateLog);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_params_window_log_check,
ldmParams_t, windowLog,
ZSTD_rust_ldm_params_layout, windowLog);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_window_next_src_check,
ZSTD_window_t, nextSrc,
ZSTD_rust_ldm_window_layout, nextSrc);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_window_base_check,
ZSTD_window_t, base,
ZSTD_rust_ldm_window_layout, base);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_window_dict_base_check,
ZSTD_window_t, dictBase,
ZSTD_rust_ldm_window_layout, dictBase);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_window_dict_limit_check,
ZSTD_window_t, dictLimit,
ZSTD_rust_ldm_window_layout, dictLimit);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_window_low_limit_check,
ZSTD_window_t, lowLimit,
ZSTD_rust_ldm_window_layout, lowLimit);
ZSTD_RUST_LDM_OFFSET_CHECK(
ZSTD_rust_ldm_window_overflow_corrections_check,
ZSTD_window_t, nbOverflowCorrections,
ZSTD_rust_ldm_window_layout, nbOverflowCorrections);
#undef ZSTD_RUST_LDM_OFFSET_CHECK
typedef char ZSTD_rust_ldm_rep_count[(ZSTD_REP_NUM == 3) ? 1 : -1];
void ZSTD_rust_ldm_adjustParameters(
void* params, U32 windowLog, int strategy,
U32 hashLogMax, U32 bucketSizeLogMax, int btultra);
size_t ZSTD_rust_ldm_getTableSize(
const void* params, int enableLdm, size_t redzoneSize);
size_t ZSTD_rust_ldm_getMaxNbSeq(
const void* params, int enableLdm, size_t maxChunkSize);
void ZSTD_rust_ldm_fillHashTable(
void* hashTable, BYTE* bucketOffsets, const BYTE* base,
const BYTE* ip, const BYTE* iend, const void* params);
size_t ZSTD_rust_ldm_generateSequences(
void* hashTable, BYTE* bucketOffsets, void* window, U32* loadedDictEnd,
void* rawSeqStore, const void* params,
const void* src, size_t srcSize, int overflowCorrectFrequently);
void ZSTD_rust_ldm_skipSequences(void* rawSeqStore, size_t srcSize, U32 minMatch);
void ZSTD_rust_ldm_skipRawSeqStoreBytes(void* rawSeqStore, size_t nbBytes);
size_t ZSTD_rust_ldm_blockCompress(
void* rawSeqStore, void* blockContext, void* seqStore, U32 rep[ZSTD_REP_NUM],
const void* src, size_t srcSize, U32 minMatch, int useOptimalParser);
const U64* ZSTD_ldm_rust_gearTable(void);
void ZSTD_ldm_rust_prepareBlock(void* blockContext, const void* anchor);
size_t ZSTD_ldm_rust_compressLiterals(
void* blockContext, void* seqStore, U32 rep[ZSTD_REP_NUM],
const void* src, size_t srcSize);
void ZSTD_ldm_rust_storeSeq(
void* seqStore, size_t litLength, const void* literals, const void* litLimit,
U32 offBase, size_t matchLength);
void ZSTD_ldm_rust_setLdmSeqStore(void* blockContext, const void* rawSeqStore);
const U64* ZSTD_ldm_rust_gearTable(void)
{ {
unsigned maxBitsInMask = MIN(params->minMatchLength, 64); return ZSTD_ldm_gearTab;
unsigned hashRateLog = params->hashRateLog;
state->rolling = ~(U32)0;
/* The choice of the splitting criterion is subject to two conditions:
* 1. it has to trigger on average every 2^(hashRateLog) bytes;
* 2. ideally, it has to depend on a window of minMatchLength bytes.
*
* In the gear hash algorithm, bit n depends on the last n bytes;
* so in order to obtain a good quality splitting criterion it is
* preferable to use bits with high weight.
*
* To match condition 1 we use a mask with hashRateLog bits set
* and, because of the previous remark, we make sure these bits
* have the highest possible weight while still respecting
* condition 2.
*/
if (hashRateLog > 0 && hashRateLog <= maxBitsInMask) {
state->stopMask = (((U64)1 << hashRateLog) - 1) << (maxBitsInMask - hashRateLog);
} else {
/* In this degenerate case we simply honor the hash rate. */
state->stopMask = ((U64)1 << hashRateLog) - 1;
}
} }
/** ZSTD_ldm_gear_reset() typedef struct {
* Feeds [data, data + minMatchLength) into the hash without registering any ZSTD_MatchState_t* ms;
* splits. This effectively resets the hash state. This is used when skipping ZSTD_BlockCompressor_f blockCompressor;
* over data, either at the beginning of a block, or skipping sections. } ZSTD_rust_ldm_block_context;
*/
static void ZSTD_ldm_gear_reset(ldmRollingHashState_t* state, static void ZSTD_rust_ldm_limitTableUpdate(ZSTD_MatchState_t* ms, const BYTE* anchor)
BYTE const* data, size_t minMatchLength)
{ {
U64 hash = state->rolling; U32 const curr = (U32)(anchor - ms->window.base);
size_t n = 0; if (curr > ms->nextToUpdate + 1024) {
ms->nextToUpdate = curr - MIN(512, curr - ms->nextToUpdate - 1024);
#define GEAR_ITER_ONCE() do { \
hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; \
n += 1; \
} while (0)
while (n + 3 < minMatchLength) {
GEAR_ITER_ONCE();
GEAR_ITER_ONCE();
GEAR_ITER_ONCE();
GEAR_ITER_ONCE();
} }
while (n < minMatchLength) {
GEAR_ITER_ONCE();
}
#undef GEAR_ITER_ONCE
} }
/** ZSTD_ldm_gear_feed(): static void ZSTD_rust_ldm_fillFastTables(ZSTD_MatchState_t* ms, const BYTE* end)
*
* Registers in the splits array all the split points found in the first
* size bytes following the data pointer. This function terminates when
* either all the data has been processed or LDM_BATCH_SIZE splits are
* present in the splits array.
*
* Precondition: The splits array must not be full.
* Returns: The number of bytes processed. */
static size_t ZSTD_ldm_gear_feed(ldmRollingHashState_t* state,
BYTE const* data, size_t size,
size_t* splits, unsigned* numSplits)
{
size_t n;
U64 hash, mask;
hash = state->rolling;
mask = state->stopMask;
n = 0;
#define GEAR_ITER_ONCE() do { \
hash = (hash << 1) + ZSTD_ldm_gearTab[data[n] & 0xff]; \
n += 1; \
if (UNLIKELY((hash & mask) == 0)) { \
splits[*numSplits] = n; \
*numSplits += 1; \
if (*numSplits == LDM_BATCH_SIZE) \
goto done; \
} \
} while (0)
while (n + 3 < size) {
GEAR_ITER_ONCE();
GEAR_ITER_ONCE();
GEAR_ITER_ONCE();
GEAR_ITER_ONCE();
}
while (n < size) {
GEAR_ITER_ONCE();
}
#undef GEAR_ITER_ONCE
done:
state->rolling = hash;
return n;
}
void ZSTD_ldm_adjustParameters(ldmParams_t* params,
const ZSTD_compressionParameters* cParams)
{
params->windowLog = cParams->windowLog;
ZSTD_STATIC_ASSERT(LDM_BUCKET_SIZE_LOG <= ZSTD_LDM_BUCKETSIZELOG_MAX);
DEBUGLOG(4, "ZSTD_ldm_adjustParameters");
if (params->hashRateLog == 0) {
if (params->hashLog > 0) {
/* if params->hashLog is set, derive hashRateLog from it */
assert(params->hashLog <= ZSTD_HASHLOG_MAX);
if (params->windowLog > params->hashLog) {
params->hashRateLog = params->windowLog - params->hashLog;
}
} else {
assert(1 <= (int)cParams->strategy && (int)cParams->strategy <= 9);
/* mapping from [fast, rate7] to [btultra2, rate4] */
params->hashRateLog = 7 - (cParams->strategy/3);
}
}
if (params->hashLog == 0) {
params->hashLog = BOUNDED(ZSTD_HASHLOG_MIN, params->windowLog - params->hashRateLog, ZSTD_HASHLOG_MAX);
}
if (params->minMatchLength == 0) {
params->minMatchLength = LDM_MIN_MATCH_LENGTH;
if (cParams->strategy >= ZSTD_btultra)
params->minMatchLength /= 2;
}
if (params->bucketSizeLog==0) {
assert(1 <= (int)cParams->strategy && (int)cParams->strategy <= 9);
params->bucketSizeLog = BOUNDED(LDM_BUCKET_SIZE_LOG, (U32)cParams->strategy, ZSTD_LDM_BUCKETSIZELOG_MAX);
}
params->bucketSizeLog = MIN(params->bucketSizeLog, params->hashLog);
}
size_t ZSTD_ldm_getTableSize(ldmParams_t params)
{
size_t const ldmHSize = ((size_t)1) << params.hashLog;
size_t const ldmBucketSizeLog = MIN(params.bucketSizeLog, params.hashLog);
size_t const ldmBucketSize = ((size_t)1) << (params.hashLog - ldmBucketSizeLog);
size_t const totalSize = ZSTD_cwksp_alloc_size(ldmBucketSize)
+ ZSTD_cwksp_alloc_size(ldmHSize * sizeof(ldmEntry_t));
return params.enableLdm == ZSTD_ps_enable ? totalSize : 0;
}
size_t ZSTD_ldm_getMaxNbSeq(ldmParams_t params, size_t maxChunkSize)
{
return params.enableLdm == ZSTD_ps_enable ? (maxChunkSize / params.minMatchLength) : 0;
}
/** ZSTD_ldm_getBucket() :
* Returns a pointer to the start of the bucket associated with hash. */
static ldmEntry_t* ZSTD_ldm_getBucket(
const ldmState_t* ldmState, size_t hash, U32 const bucketSizeLog)
{
return ldmState->hashTable + (hash << bucketSizeLog);
}
/** ZSTD_ldm_insertEntry() :
* Insert the entry with corresponding hash into the hash table */
static void ZSTD_ldm_insertEntry(ldmState_t* ldmState,
size_t const hash, const ldmEntry_t entry,
U32 const bucketSizeLog)
{
BYTE* const pOffset = ldmState->bucketOffsets + hash;
unsigned const offset = *pOffset;
*(ZSTD_ldm_getBucket(ldmState, hash, bucketSizeLog) + offset) = entry;
*pOffset = (BYTE)((offset + 1) & ((1u << bucketSizeLog) - 1));
}
/** ZSTD_ldm_countBackwardsMatch() :
* Returns the number of bytes that match backwards before pIn and pMatch.
*
* We count only bytes where pMatch >= pBase and pIn >= pAnchor. */
static size_t ZSTD_ldm_countBackwardsMatch(
const BYTE* pIn, const BYTE* pAnchor,
const BYTE* pMatch, const BYTE* pMatchBase)
{
size_t matchLength = 0;
while (pIn > pAnchor && pMatch > pMatchBase && pIn[-1] == pMatch[-1]) {
pIn--;
pMatch--;
matchLength++;
}
return matchLength;
}
/** ZSTD_ldm_countBackwardsMatch_2segments() :
* Returns the number of bytes that match backwards from pMatch,
* even with the backwards match spanning 2 different segments.
*
* On reaching `pMatchBase`, start counting from mEnd */
static size_t ZSTD_ldm_countBackwardsMatch_2segments(
const BYTE* pIn, const BYTE* pAnchor,
const BYTE* pMatch, const BYTE* pMatchBase,
const BYTE* pExtDictStart, const BYTE* pExtDictEnd)
{
size_t matchLength = ZSTD_ldm_countBackwardsMatch(pIn, pAnchor, pMatch, pMatchBase);
if (pMatch - matchLength != pMatchBase || pMatchBase == pExtDictStart) {
/* If backwards match is entirely in the extDict or prefix, immediately return */
return matchLength;
}
DEBUGLOG(7, "ZSTD_ldm_countBackwardsMatch_2segments: found 2-parts backwards match (length in prefix==%zu)", matchLength);
matchLength += ZSTD_ldm_countBackwardsMatch(pIn - matchLength, pAnchor, pExtDictEnd, pExtDictStart);
DEBUGLOG(7, "final backwards match length = %zu", matchLength);
return matchLength;
}
/** ZSTD_ldm_fillFastTables() :
*
* Fills the relevant tables for the ZSTD_fast and ZSTD_dfast strategies.
* This is similar to ZSTD_loadDictionaryContent.
*
* The tables for the other strategies are filled within their
* block compressors. */
static size_t ZSTD_ldm_fillFastTables(ZSTD_MatchState_t* ms,
void const* end)
{
const BYTE* const iend = (const BYTE*)end;
switch(ms->cParams.strategy)
{ {
switch (ms->cParams.strategy) {
case ZSTD_fast: case ZSTD_fast:
ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); ZSTD_fillHashTable(ms, end, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx);
break; break;
case ZSTD_dfast: case ZSTD_dfast:
#ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR #ifndef ZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR
ZSTD_fillDoubleHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); ZSTD_fillDoubleHashTable(ms, end, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx);
#else #else
assert(0); /* shouldn't be called: cparams should've been adjusted. */ assert(0);
#endif #endif
break; break;
case ZSTD_greedy: case ZSTD_greedy:
case ZSTD_lazy: case ZSTD_lazy:
case ZSTD_lazy2: case ZSTD_lazy2:
@@ -276,406 +236,95 @@ static size_t ZSTD_ldm_fillFastTables(ZSTD_MatchState_t* ms,
case ZSTD_btultra2: case ZSTD_btultra2:
break; break;
default: default:
assert(0); /* not possible : not a valid strategy id */ assert(0);
}
} }
return 0; void ZSTD_ldm_rust_prepareBlock(void* blockContext, const void* anchor)
{
ZSTD_rust_ldm_block_context* const context = (ZSTD_rust_ldm_block_context*)blockContext;
ZSTD_rust_ldm_limitTableUpdate(context->ms, (const BYTE*)anchor);
ZSTD_rust_ldm_fillFastTables(context->ms, (const BYTE*)anchor);
}
size_t ZSTD_ldm_rust_compressLiterals(
void* blockContext, void* seqStore, U32 rep[ZSTD_REP_NUM],
const void* src, size_t srcSize)
{
ZSTD_rust_ldm_block_context* const context = (ZSTD_rust_ldm_block_context*)blockContext;
return context->blockCompressor(context->ms, (SeqStore_t*)seqStore, rep, src, srcSize);
}
void ZSTD_ldm_rust_storeSeq(
void* seqStore, size_t litLength, const void* literals, const void* litLimit,
U32 offBase, size_t matchLength)
{
ZSTD_storeSeq((SeqStore_t*)seqStore, litLength, (const BYTE*)literals,
(const BYTE*)litLimit, offBase, matchLength);
}
void ZSTD_ldm_rust_setLdmSeqStore(void* blockContext, const void* rawSeqStore)
{
ZSTD_rust_ldm_block_context* const context = (ZSTD_rust_ldm_block_context*)blockContext;
context->ms->ldmSeqStore = (const RawSeqStore_t*)rawSeqStore;
}
void ZSTD_ldm_adjustParameters(ldmParams_t* params,
const ZSTD_compressionParameters* cParams)
{
ZSTD_rust_ldm_adjustParameters(
params, cParams->windowLog, (int)cParams->strategy,
ZSTD_HASHLOG_MAX, ZSTD_LDM_BUCKETSIZELOG_MAX, (int)ZSTD_btultra);
}
size_t ZSTD_ldm_getTableSize(ldmParams_t params)
{
#if ZSTD_ADDRESS_SANITIZER && !defined(ZSTD_ASAN_DONT_POISON_WORKSPACE)
size_t const redzoneSize = ZSTD_CWKSP_ASAN_REDZONE_SIZE;
#else
size_t const redzoneSize = 0;
#endif
return ZSTD_rust_ldm_getTableSize(
&params, params.enableLdm == ZSTD_ps_enable, redzoneSize);
}
size_t ZSTD_ldm_getMaxNbSeq(ldmParams_t params, size_t maxChunkSize)
{
return ZSTD_rust_ldm_getMaxNbSeq(
&params, params.enableLdm == ZSTD_ps_enable, maxChunkSize);
} }
void ZSTD_ldm_fillHashTable( void ZSTD_ldm_fillHashTable(
ldmState_t* ldmState, const BYTE* ip, ldmState_t* ldmState, const BYTE* ip,
const BYTE* iend, ldmParams_t const* params) const BYTE* iend, ldmParams_t const* params)
{ {
U32 const minMatchLength = params->minMatchLength; ZSTD_rust_ldm_fillHashTable(
U32 const bucketSizeLog = params->bucketSizeLog; ldmState->hashTable, ldmState->bucketOffsets, ldmState->window.base,
U32 const hBits = params->hashLog - bucketSizeLog; ip, iend, params);
BYTE const* const base = ldmState->window.base;
BYTE const* const istart = ip;
ldmRollingHashState_t hashState;
size_t* const splits = ldmState->splitIndices;
unsigned numSplits;
DEBUGLOG(5, "ZSTD_ldm_fillHashTable");
ZSTD_ldm_gear_init(&hashState, params);
while (ip < iend) {
size_t hashed;
unsigned n;
numSplits = 0;
hashed = ZSTD_ldm_gear_feed(&hashState, ip, (size_t)(iend - ip), splits, &numSplits);
for (n = 0; n < numSplits; n++) {
if (ip + splits[n] >= istart + minMatchLength) {
BYTE const* const split = ip + splits[n] - minMatchLength;
U64 const xxhash = XXH64(split, minMatchLength, 0);
U32 const hash = (U32)(xxhash & (((U32)1 << hBits) - 1));
ldmEntry_t entry;
entry.offset = (U32)(split - base);
entry.checksum = (U32)(xxhash >> 32);
ZSTD_ldm_insertEntry(ldmState, hash, entry, params->bucketSizeLog);
}
}
ip += hashed;
}
}
/** ZSTD_ldm_limitTableUpdate() :
*
* Sets cctx->nextToUpdate to a position corresponding closer to anchor
* if it is far way
* (after a long match, only update tables a limited amount). */
static void ZSTD_ldm_limitTableUpdate(ZSTD_MatchState_t* ms, const BYTE* anchor)
{
U32 const curr = (U32)(anchor - ms->window.base);
if (curr > ms->nextToUpdate + 1024) {
ms->nextToUpdate =
curr - MIN(512, curr - ms->nextToUpdate - 1024);
}
}
static
ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
size_t ZSTD_ldm_generateSequences_internal(
ldmState_t* ldmState, RawSeqStore_t* rawSeqStore,
ldmParams_t const* params, void const* src, size_t srcSize)
{
/* LDM parameters */
int const extDict = ZSTD_window_hasExtDict(ldmState->window);
U32 const minMatchLength = params->minMatchLength;
U32 const entsPerBucket = 1U << params->bucketSizeLog;
U32 const hBits = params->hashLog - params->bucketSizeLog;
/* Prefix and extDict parameters */
U32 const dictLimit = ldmState->window.dictLimit;
U32 const lowestIndex = extDict ? ldmState->window.lowLimit : dictLimit;
BYTE const* const base = ldmState->window.base;
BYTE const* const dictBase = extDict ? ldmState->window.dictBase : NULL;
BYTE const* const dictStart = extDict ? dictBase + lowestIndex : NULL;
BYTE const* const dictEnd = extDict ? dictBase + dictLimit : NULL;
BYTE const* const lowPrefixPtr = base + dictLimit;
/* Input bounds */
BYTE const* const istart = (BYTE const*)src;
BYTE const* const iend = istart + srcSize;
BYTE const* const ilimit = iend - HASH_READ_SIZE;
/* Input positions */
BYTE const* anchor = istart;
BYTE const* ip = istart;
/* Rolling hash state */
ldmRollingHashState_t hashState;
/* Arrays for staged-processing */
size_t* const splits = ldmState->splitIndices;
ldmMatchCandidate_t* const candidates = ldmState->matchCandidates;
unsigned numSplits;
if (srcSize < minMatchLength)
return iend - anchor;
/* Initialize the rolling hash state with the first minMatchLength bytes */
ZSTD_ldm_gear_init(&hashState, params);
ZSTD_ldm_gear_reset(&hashState, ip, minMatchLength);
ip += minMatchLength;
while (ip < ilimit) {
size_t hashed;
unsigned n;
numSplits = 0;
hashed = ZSTD_ldm_gear_feed(&hashState, ip, ilimit - ip,
splits, &numSplits);
for (n = 0; n < numSplits; n++) {
BYTE const* const split = ip + splits[n] - minMatchLength;
U64 const xxhash = XXH64(split, minMatchLength, 0);
U32 const hash = (U32)(xxhash & (((U32)1 << hBits) - 1));
candidates[n].split = split;
candidates[n].hash = hash;
candidates[n].checksum = (U32)(xxhash >> 32);
candidates[n].bucket = ZSTD_ldm_getBucket(ldmState, hash, params->bucketSizeLog);
PREFETCH_L1(candidates[n].bucket);
}
for (n = 0; n < numSplits; n++) {
size_t forwardMatchLength = 0, backwardMatchLength = 0,
bestMatchLength = 0, mLength;
U32 offset;
BYTE const* const split = candidates[n].split;
U32 const checksum = candidates[n].checksum;
U32 const hash = candidates[n].hash;
ldmEntry_t* const bucket = candidates[n].bucket;
ldmEntry_t const* cur;
ldmEntry_t const* bestEntry = NULL;
ldmEntry_t newEntry;
newEntry.offset = (U32)(split - base);
newEntry.checksum = checksum;
/* If a split point would generate a sequence overlapping with
* the previous one, we merely register it in the hash table and
* move on */
if (split < anchor) {
ZSTD_ldm_insertEntry(ldmState, hash, newEntry, params->bucketSizeLog);
continue;
}
for (cur = bucket; cur < bucket + entsPerBucket; cur++) {
size_t curForwardMatchLength, curBackwardMatchLength,
curTotalMatchLength;
if (cur->checksum != checksum || cur->offset <= lowestIndex) {
continue;
}
if (extDict) {
BYTE const* const curMatchBase =
cur->offset < dictLimit ? dictBase : base;
BYTE const* const pMatch = curMatchBase + cur->offset;
BYTE const* const matchEnd =
cur->offset < dictLimit ? dictEnd : iend;
BYTE const* const lowMatchPtr =
cur->offset < dictLimit ? dictStart : lowPrefixPtr;
curForwardMatchLength =
ZSTD_count_2segments(split, pMatch, iend, matchEnd, lowPrefixPtr);
if (curForwardMatchLength < minMatchLength) {
continue;
}
curBackwardMatchLength = ZSTD_ldm_countBackwardsMatch_2segments(
split, anchor, pMatch, lowMatchPtr, dictStart, dictEnd);
} else { /* !extDict */
BYTE const* const pMatch = base + cur->offset;
curForwardMatchLength = ZSTD_count(split, pMatch, iend);
if (curForwardMatchLength < minMatchLength) {
continue;
}
curBackwardMatchLength =
ZSTD_ldm_countBackwardsMatch(split, anchor, pMatch, lowPrefixPtr);
}
curTotalMatchLength = curForwardMatchLength + curBackwardMatchLength;
if (curTotalMatchLength > bestMatchLength) {
bestMatchLength = curTotalMatchLength;
forwardMatchLength = curForwardMatchLength;
backwardMatchLength = curBackwardMatchLength;
bestEntry = cur;
}
}
/* No match found -- insert an entry into the hash table
* and process the next candidate match */
if (bestEntry == NULL) {
ZSTD_ldm_insertEntry(ldmState, hash, newEntry, params->bucketSizeLog);
continue;
}
/* Match found */
offset = (U32)(split - base) - bestEntry->offset;
mLength = forwardMatchLength + backwardMatchLength;
{
rawSeq* const seq = rawSeqStore->seq + rawSeqStore->size;
/* Out of sequence storage */
if (rawSeqStore->size == rawSeqStore->capacity)
return ERROR(dstSize_tooSmall);
seq->litLength = (U32)(split - backwardMatchLength - anchor);
seq->matchLength = (U32)mLength;
seq->offset = offset;
rawSeqStore->size++;
}
/* Insert the current entry into the hash table --- it must be
* done after the previous block to avoid clobbering bestEntry */
ZSTD_ldm_insertEntry(ldmState, hash, newEntry, params->bucketSizeLog);
anchor = split + forwardMatchLength;
/* If we find a match that ends after the data that we've hashed
* then we have a repeating, overlapping, pattern. E.g. all zeros.
* If one repetition of the pattern matches our `stopMask` then all
* repetitions will. We don't need to insert them all into out table,
* only the first one. So skip over overlapping matches.
* This is a major speed boost (20x) for compressing a single byte
* repeated, when that byte ends up in the table.
*/
if (anchor > ip + hashed) {
ZSTD_ldm_gear_reset(&hashState, anchor - minMatchLength, minMatchLength);
/* Continue the outer loop at anchor (ip + hashed == anchor). */
ip = anchor - hashed;
break;
}
}
ip += hashed;
}
return iend - anchor;
}
/*! ZSTD_ldm_reduceTable() :
* reduce table indexes by `reducerValue` */
static void ZSTD_ldm_reduceTable(ldmEntry_t* const table, U32 const size,
U32 const reducerValue)
{
U32 u;
for (u = 0; u < size; u++) {
if (table[u].offset < reducerValue) table[u].offset = 0;
else table[u].offset -= reducerValue;
}
} }
size_t ZSTD_ldm_generateSequences( size_t ZSTD_ldm_generateSequences(
ldmState_t* ldmState, RawSeqStore_t* sequences, ldmState_t* ldmState, RawSeqStore_t* sequences,
ldmParams_t const* params, void const* src, size_t srcSize) ldmParams_t const* params, void const* src, size_t srcSize)
{ {
U32 const maxDist = 1U << params->windowLog; assert(ldmState->window.nextSrc >= (const BYTE*)src + srcSize);
BYTE const* const istart = (BYTE const*)src;
BYTE const* const iend = istart + srcSize;
size_t const kMaxChunkSize = 1 << 20;
size_t const nbChunks = (srcSize / kMaxChunkSize) + ((srcSize % kMaxChunkSize) != 0);
size_t chunk;
size_t leftoverSize = 0;
assert(ZSTD_CHUNKSIZE_MAX >= kMaxChunkSize);
/* Check that ZSTD_window_update() has been called for this chunk prior
* to passing it to this function.
*/
assert(ldmState->window.nextSrc >= (BYTE const*)src + srcSize);
/* The input could be very large (in zstdmt), so it must be broken up into
* chunks to enforce the maximum distance and handle overflow correction.
*/
assert(sequences->pos <= sequences->size); assert(sequences->pos <= sequences->size);
assert(sequences->size <= sequences->capacity); assert(sequences->size <= sequences->capacity);
for (chunk = 0; chunk < nbChunks && sequences->size < sequences->capacity; ++chunk) { return ZSTD_rust_ldm_generateSequences(
BYTE const* const chunkStart = istart + chunk * kMaxChunkSize; ldmState->hashTable, ldmState->bucketOffsets, &ldmState->window,
size_t const remaining = (size_t)(iend - chunkStart); &ldmState->loadedDictEnd, sequences, params, src, srcSize,
BYTE const *const chunkEnd = ZSTD_WINDOW_OVERFLOW_CORRECT_FREQUENTLY);
(remaining < kMaxChunkSize) ? iend : chunkStart + kMaxChunkSize;
size_t const chunkSize = chunkEnd - chunkStart;
size_t newLeftoverSize;
size_t const prevSize = sequences->size;
assert(chunkStart < iend);
/* 1. Perform overflow correction if necessary. */
if (ZSTD_window_needOverflowCorrection(ldmState->window, 0, maxDist, ldmState->loadedDictEnd, chunkStart, chunkEnd)) {
U32 const ldmHSize = 1U << params->hashLog;
U32 const correction = ZSTD_window_correctOverflow(
&ldmState->window, /* cycleLog */ 0, maxDist, chunkStart);
ZSTD_ldm_reduceTable(ldmState->hashTable, ldmHSize, correction);
/* invalidate dictionaries on overflow correction */
ldmState->loadedDictEnd = 0;
}
/* 2. We enforce the maximum offset allowed.
*
* kMaxChunkSize should be small enough that we don't lose too much of
* the window through early invalidation.
* TODO: * Test the chunk size.
* * Try invalidation after the sequence generation and test the
* offset against maxDist directly.
*
* NOTE: Because of dictionaries + sequence splitting we MUST make sure
* that any offset used is valid at the END of the sequence, since it may
* be split into two sequences. This condition holds when using
* ZSTD_window_enforceMaxDist(), but if we move to checking offsets
* against maxDist directly, we'll have to carefully handle that case.
*/
ZSTD_window_enforceMaxDist(&ldmState->window, chunkEnd, maxDist, &ldmState->loadedDictEnd, NULL);
/* 3. Generate the sequences for the chunk, and get newLeftoverSize. */
newLeftoverSize = ZSTD_ldm_generateSequences_internal(
ldmState, sequences, params, chunkStart, chunkSize);
if (ZSTD_isError(newLeftoverSize))
return newLeftoverSize;
/* 4. We add the leftover literals from previous iterations to the first
* newly generated sequence, or add the `newLeftoverSize` if none are
* generated.
*/
/* Prepend the leftover literals from the last call */
if (prevSize < sequences->size) {
sequences->seq[prevSize].litLength += (U32)leftoverSize;
leftoverSize = newLeftoverSize;
} else {
assert(newLeftoverSize == chunkSize);
leftoverSize += chunkSize;
}
}
return 0;
} }
void void ZSTD_ldm_skipSequences(RawSeqStore_t* rawSeqStore, size_t srcSize,
ZSTD_ldm_skipSequences(RawSeqStore_t* rawSeqStore, size_t srcSize, U32 const minMatch) U32 const minMatch)
{ {
while (srcSize > 0 && rawSeqStore->pos < rawSeqStore->size) { ZSTD_rust_ldm_skipSequences(rawSeqStore, srcSize, minMatch);
rawSeq* seq = rawSeqStore->seq + rawSeqStore->pos;
if (srcSize <= seq->litLength) {
/* Skip past srcSize literals */
seq->litLength -= (U32)srcSize;
return;
}
srcSize -= seq->litLength;
seq->litLength = 0;
if (srcSize < seq->matchLength) {
/* Skip past the first srcSize of the match */
seq->matchLength -= (U32)srcSize;
if (seq->matchLength < minMatch) {
/* The match is too short, omit it */
if (rawSeqStore->pos + 1 < rawSeqStore->size) {
seq[1].litLength += seq[0].matchLength;
}
rawSeqStore->pos++;
}
return;
}
srcSize -= seq->matchLength;
seq->matchLength = 0;
rawSeqStore->pos++;
}
} }
/** void ZSTD_ldm_skipRawSeqStoreBytes(RawSeqStore_t* rawSeqStore, size_t nbBytes)
* If the sequence length is longer than remaining then the sequence is split
* between this block and the next.
*
* Returns the current sequence to handle, or if the rest of the block should
* be literals, it returns a sequence with offset == 0.
*/
static rawSeq maybeSplitSequence(RawSeqStore_t* rawSeqStore,
U32 const remaining, U32 const minMatch)
{ {
rawSeq sequence = rawSeqStore->seq[rawSeqStore->pos]; ZSTD_rust_ldm_skipRawSeqStoreBytes(rawSeqStore, nbBytes);
assert(sequence.offset > 0);
/* Likely: No partial sequence */
if (remaining >= sequence.litLength + sequence.matchLength) {
rawSeqStore->pos++;
return sequence;
}
/* Cut the sequence short (offset == 0 ==> rest is literals). */
if (remaining <= sequence.litLength) {
sequence.offset = 0;
} else if (remaining < sequence.litLength + sequence.matchLength) {
sequence.matchLength = remaining - sequence.litLength;
if (sequence.matchLength < minMatch) {
sequence.offset = 0;
}
}
/* Skip past `remaining` bytes for the future sequences. */
ZSTD_ldm_skipSequences(rawSeqStore, remaining, minMatch);
return sequence;
}
void ZSTD_ldm_skipRawSeqStoreBytes(RawSeqStore_t* rawSeqStore, size_t nbBytes) {
U32 currPos = (U32)(rawSeqStore->posInSequence + nbBytes);
while (currPos && rawSeqStore->pos < rawSeqStore->size) {
rawSeq currSeq = rawSeqStore->seq[rawSeqStore->pos];
if (currPos >= currSeq.litLength + currSeq.matchLength) {
currPos -= currSeq.litLength + currSeq.matchLength;
rawSeqStore->pos++;
} else {
rawSeqStore->posInSequence = currPos;
break;
}
}
if (currPos == 0 || rawSeqStore->pos == rawSeqStore->size) {
rawSeqStore->posInSequence = 0;
}
} }
size_t ZSTD_ldm_blockCompress(RawSeqStore_t* rawSeqStore, size_t ZSTD_ldm_blockCompress(RawSeqStore_t* rawSeqStore,
@@ -683,63 +332,12 @@ size_t ZSTD_ldm_blockCompress(RawSeqStore_t* rawSeqStore,
ZSTD_ParamSwitch_e useRowMatchFinder, ZSTD_ParamSwitch_e useRowMatchFinder,
void const* src, size_t srcSize) void const* src, size_t srcSize)
{ {
const ZSTD_compressionParameters* const cParams = &ms->cParams; ZSTD_rust_ldm_block_context context;
unsigned const minMatch = cParams->minMatch; context.ms = ms;
ZSTD_BlockCompressor_f const blockCompressor = context.blockCompressor = ZSTD_selectBlockCompressor(
ZSTD_selectBlockCompressor(cParams->strategy, useRowMatchFinder, ZSTD_matchState_dictMode(ms)); ms->cParams.strategy, useRowMatchFinder, ZSTD_matchState_dictMode(ms));
/* Input bounds */ assert(context.blockCompressor != NULL);
BYTE const* const istart = (BYTE const*)src; return ZSTD_rust_ldm_blockCompress(
BYTE const* const iend = istart + srcSize; rawSeqStore, &context, seqStore, rep, src, srcSize,
/* Input positions */ ms->cParams.minMatch, ms->cParams.strategy >= ZSTD_btopt);
BYTE const* ip = istart;
DEBUGLOG(5, "ZSTD_ldm_blockCompress: srcSize=%zu", srcSize);
/* If using opt parser, use LDMs only as candidates rather than always accepting them */
if (cParams->strategy >= ZSTD_btopt) {
size_t lastLLSize;
ms->ldmSeqStore = rawSeqStore;
lastLLSize = blockCompressor(ms, seqStore, rep, src, srcSize);
ZSTD_ldm_skipRawSeqStoreBytes(rawSeqStore, srcSize);
return lastLLSize;
}
assert(rawSeqStore->pos <= rawSeqStore->size);
assert(rawSeqStore->size <= rawSeqStore->capacity);
/* Loop through each sequence and apply the block compressor to the literals */
while (rawSeqStore->pos < rawSeqStore->size && ip < iend) {
/* maybeSplitSequence updates rawSeqStore->pos */
rawSeq const sequence = maybeSplitSequence(rawSeqStore,
(U32)(iend - ip), minMatch);
/* End signal */
if (sequence.offset == 0)
break;
assert(ip + sequence.litLength + sequence.matchLength <= iend);
/* Fill tables for block compressor */
ZSTD_ldm_limitTableUpdate(ms, ip);
ZSTD_ldm_fillFastTables(ms, ip);
/* Run the block compressor */
DEBUGLOG(5, "pos %u : calling block compressor on segment of size %u", (unsigned)(ip-istart), sequence.litLength);
{
int i;
size_t const newLitLength =
blockCompressor(ms, seqStore, rep, ip, sequence.litLength);
ip += sequence.litLength;
/* Update the repcodes */
for (i = ZSTD_REP_NUM - 1; i > 0; i--)
rep[i] = rep[i-1];
rep[0] = sequence.offset;
/* Store the sequence */
ZSTD_storeSeq(seqStore, newLitLength, ip - newLitLength, iend,
OFFSET_TO_OFFBASE(sequence.offset),
sequence.matchLength);
ip += sequence.matchLength;
}
}
/* Fill the tables for the block compressor */
ZSTD_ldm_limitTableUpdate(ms, ip);
ZSTD_ldm_fillFastTables(ms, ip);
/* Compress the last literals */
return blockCompressor(ms, seqStore, rep, ip, iend - ip);
} }
+396 -2324
View File
@@ -1,925 +1,336 @@
/* /*
* Copyright (c) Meta Platforms, Inc. and affiliates. * Decoder orchestration is implemented in rust/src/zstd_decompress.rs.
* All rights reserved.
* *
* This source code is licensed under both the BSD-style license (found in the * `ZSTD_DCtx_s` is intentionally still allocated and laid out by C: its
* LICENSE file in the root directory of this source tree) and the GPLv2 (found * optional members vary with the build configuration and the block decoder
* in the COPYING file in the root directory of this source tree). * shares that object. This translation unit is therefore a deliberately
* You may select, at your option, one of the above-listed licenses. * narrow ABI adapter. It projects field addresses to Rust, keeps allocation
* ownership in the C allocator domain, and retains the configuration-bound
* legacy and trace leaves.
*/ */
#define ZSTD_STATIC_LINKING_ONLY
/* *************************************************************** #include "../common/zstd_deps.h"
* Tuning parameters #include "../common/allocations.h"
*****************************************************************/
/*!
* HEAPMODE :
* Select how default decompression function ZSTD_decompress() allocates its context,
* on stack (0), or into heap (1, default; requires malloc()).
* Note that functions with explicit context such as ZSTD_decompressDCtx() are unaffected.
*/
#ifndef ZSTD_HEAPMODE
# define ZSTD_HEAPMODE 1
#endif
/*!
* LEGACY_SUPPORT :
* if set to 1+, ZSTD_decompress() can decode older formats (v0.1+)
*/
#ifndef ZSTD_LEGACY_SUPPORT
# define ZSTD_LEGACY_SUPPORT 0
#endif
/*!
* MAXWINDOWSIZE_DEFAULT :
* maximum window size accepted by DStream __by default__.
* Frames requiring more memory will be rejected.
* It's possible to set a different limit using ZSTD_DCtx_setMaxWindowSize().
*/
#ifndef ZSTD_MAXWINDOWSIZE_DEFAULT
# define ZSTD_MAXWINDOWSIZE_DEFAULT (((U32)1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT) + 1)
#endif
/*!
* NO_FORWARD_PROGRESS_MAX :
* maximum allowed nb of calls to ZSTD_decompressStream()
* without any forward progress
* (defined as: no byte read from input, and no byte flushed to output)
* before triggering an error.
*/
#ifndef ZSTD_NO_FORWARD_PROGRESS_MAX
# define ZSTD_NO_FORWARD_PROGRESS_MAX 16
#endif
/*-*******************************************************
* Dependencies
*********************************************************/
#include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */
#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */
#include "../common/error_private.h" #include "../common/error_private.h"
#include "../common/zstd_internal.h" /* blockProperties_t */ #include "../common/mem.h"
#include "../common/mem.h" /* low level memory routines */ #include "../common/zstd_internal.h"
#include "../common/bits.h" /* ZSTD_highbit32 */ #include "zstd_decompress_internal.h"
#define FSE_STATIC_LINKING_ONLY #include "zstd_ddict.h"
#include "../common/fse.h"
#include "../common/huf.h"
#include "../common/xxhash.h" /* XXH64_reset, XXH64_update, XXH64_digest, XXH64 */
#include "zstd_decompress_internal.h" /* ZSTD_DCtx */
#include "zstd_ddict.h" /* ZSTD_DDictDictContent */
#include "zstd_decompress_block.h" /* ZSTD_decompressBlock_internal */
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
# include "../legacy/zstd_legacy.h" # include "../legacy/zstd_legacy.h"
#endif #endif
/* Keep the three public build-time tuning knobs in the C configuration domain.
* Rust queries them through the small leaves below rather than baking a second
/************************************* * set of defaults into its source. */
* Multiple DDicts Hashset internals * #ifndef ZSTD_HEAPMODE
*************************************/ # define ZSTD_HEAPMODE 1
#define DDICT_HASHSET_MAX_LOAD_FACTOR_COUNT_MULT 4
#define DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT 3 /* These two constants represent SIZE_MULT/COUNT_MULT load factor without using a float.
* Currently, that means a 0.75 load factor.
* So, if count * COUNT_MULT / size * SIZE_MULT != 0, then we've exceeded
* the load factor of the ddict hash set.
*/
#define DDICT_HASHSET_TABLE_BASE_SIZE 64
#define DDICT_HASHSET_RESIZE_FACTOR 2
/* Hash function to determine starting position of dict insertion within the table
* Returns an index between [0, hashSet->ddictPtrTableSize]
*/
static size_t ZSTD_DDictHashSet_getIndex(const ZSTD_DDictHashSet* hashSet, U32 dictID) {
const U64 hash = XXH64(&dictID, sizeof(U32), 0);
/* DDict ptr table size is a multiple of 2, use size - 1 as mask to get index within [0, hashSet->ddictPtrTableSize) */
return hash & (hashSet->ddictPtrTableSize - 1);
}
/* Adds DDict to a hashset without resizing it.
* If inserting a DDict with a dictID that already exists in the set, replaces the one in the set.
* Returns 0 if successful, or a zstd error code if something went wrong.
*/
static size_t ZSTD_DDictHashSet_emplaceDDict(ZSTD_DDictHashSet* hashSet, const ZSTD_DDict* ddict) {
const U32 dictID = ZSTD_getDictID_fromDDict(ddict);
size_t idx = ZSTD_DDictHashSet_getIndex(hashSet, dictID);
const size_t idxRangeMask = hashSet->ddictPtrTableSize - 1;
RETURN_ERROR_IF(hashSet->ddictPtrCount == hashSet->ddictPtrTableSize, GENERIC, "Hash set is full!");
DEBUGLOG(4, "Hashed index: for dictID: %u is %zu", dictID, idx);
while (hashSet->ddictPtrTable[idx] != NULL) {
/* Replace existing ddict if inserting ddict with same dictID */
if (ZSTD_getDictID_fromDDict(hashSet->ddictPtrTable[idx]) == dictID) {
DEBUGLOG(4, "DictID already exists, replacing rather than adding");
hashSet->ddictPtrTable[idx] = ddict;
return 0;
}
idx &= idxRangeMask;
idx++;
}
DEBUGLOG(4, "Final idx after probing for dictID %u is: %zu", dictID, idx);
hashSet->ddictPtrTable[idx] = ddict;
hashSet->ddictPtrCount++;
return 0;
}
/* Expands hash table by factor of DDICT_HASHSET_RESIZE_FACTOR and
* rehashes all values, allocates new table, frees old table.
* Returns 0 on success, otherwise a zstd error code.
*/
static size_t ZSTD_DDictHashSet_expand(ZSTD_DDictHashSet* hashSet, ZSTD_customMem customMem) {
size_t newTableSize = hashSet->ddictPtrTableSize * DDICT_HASHSET_RESIZE_FACTOR;
const ZSTD_DDict** newTable = (const ZSTD_DDict**)ZSTD_customCalloc(sizeof(ZSTD_DDict*) * newTableSize, customMem);
const ZSTD_DDict** oldTable = hashSet->ddictPtrTable;
size_t oldTableSize = hashSet->ddictPtrTableSize;
size_t i;
DEBUGLOG(4, "Expanding DDict hash table! Old size: %zu new size: %zu", oldTableSize, newTableSize);
RETURN_ERROR_IF(!newTable, memory_allocation, "Expanded hashset allocation failed!");
hashSet->ddictPtrTable = newTable;
hashSet->ddictPtrTableSize = newTableSize;
hashSet->ddictPtrCount = 0;
for (i = 0; i < oldTableSize; ++i) {
if (oldTable[i] != NULL) {
FORWARD_IF_ERROR(ZSTD_DDictHashSet_emplaceDDict(hashSet, oldTable[i]), "");
}
}
ZSTD_customFree((void*)oldTable, customMem);
DEBUGLOG(4, "Finished re-hash");
return 0;
}
/* Fetches a DDict with the given dictID
* Returns the ZSTD_DDict* with the requested dictID. If it doesn't exist, then returns NULL.
*/
static const ZSTD_DDict* ZSTD_DDictHashSet_getDDict(ZSTD_DDictHashSet* hashSet, U32 dictID) {
size_t idx = ZSTD_DDictHashSet_getIndex(hashSet, dictID);
const size_t idxRangeMask = hashSet->ddictPtrTableSize - 1;
DEBUGLOG(4, "Hashed index: for dictID: %u is %zu", dictID, idx);
for (;;) {
size_t currDictID = ZSTD_getDictID_fromDDict(hashSet->ddictPtrTable[idx]);
if (currDictID == dictID || currDictID == 0) {
/* currDictID == 0 implies a NULL ddict entry */
break;
} else {
idx &= idxRangeMask; /* Goes to start of table when we reach the end */
idx++;
}
}
DEBUGLOG(4, "Final idx after probing for dictID %u is: %zu", dictID, idx);
return hashSet->ddictPtrTable[idx];
}
/* Allocates space for and returns a ddict hash set
* The hash set's ZSTD_DDict* table has all values automatically set to NULL to begin with.
* Returns NULL if allocation failed.
*/
static ZSTD_DDictHashSet* ZSTD_createDDictHashSet(ZSTD_customMem customMem) {
ZSTD_DDictHashSet* ret = (ZSTD_DDictHashSet*)ZSTD_customMalloc(sizeof(ZSTD_DDictHashSet), customMem);
DEBUGLOG(4, "Allocating new hash set");
if (!ret)
return NULL;
ret->ddictPtrTable = (const ZSTD_DDict**)ZSTD_customCalloc(DDICT_HASHSET_TABLE_BASE_SIZE * sizeof(ZSTD_DDict*), customMem);
if (!ret->ddictPtrTable) {
ZSTD_customFree(ret, customMem);
return NULL;
}
ret->ddictPtrTableSize = DDICT_HASHSET_TABLE_BASE_SIZE;
ret->ddictPtrCount = 0;
return ret;
}
/* Frees the table of ZSTD_DDict* within a hashset, then frees the hashset itself.
* Note: The ZSTD_DDict* within the table are NOT freed.
*/
static void ZSTD_freeDDictHashSet(ZSTD_DDictHashSet* hashSet, ZSTD_customMem customMem) {
DEBUGLOG(4, "Freeing ddict hash set");
if (hashSet && hashSet->ddictPtrTable) {
ZSTD_customFree((void*)hashSet->ddictPtrTable, customMem);
}
if (hashSet) {
ZSTD_customFree(hashSet, customMem);
}
}
/* Public function: Adds a DDict into the ZSTD_DDictHashSet, possibly triggering a resize of the hash set.
* Returns 0 on success, or a ZSTD error.
*/
static size_t ZSTD_DDictHashSet_addDDict(ZSTD_DDictHashSet* hashSet, const ZSTD_DDict* ddict, ZSTD_customMem customMem) {
DEBUGLOG(4, "Adding dict ID: %u to hashset with - Count: %zu Tablesize: %zu", ZSTD_getDictID_fromDDict(ddict), hashSet->ddictPtrCount, hashSet->ddictPtrTableSize);
if (hashSet->ddictPtrCount * DDICT_HASHSET_MAX_LOAD_FACTOR_COUNT_MULT / hashSet->ddictPtrTableSize * DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT != 0) {
FORWARD_IF_ERROR(ZSTD_DDictHashSet_expand(hashSet, customMem), "");
}
FORWARD_IF_ERROR(ZSTD_DDictHashSet_emplaceDDict(hashSet, ddict), "");
return 0;
}
/*-*************************************************************
* Context management
***************************************************************/
size_t ZSTD_sizeof_DCtx (const ZSTD_DCtx* dctx)
{
if (dctx==NULL) return 0; /* support sizeof NULL */
return sizeof(*dctx)
+ ZSTD_sizeof_DDict(dctx->ddictLocal)
+ dctx->inBuffSize + dctx->outBuffSize;
}
size_t ZSTD_estimateDCtxSize(void) { return sizeof(ZSTD_DCtx); }
static size_t ZSTD_startingInputLength(ZSTD_format_e format)
{
size_t const startingInputLength = ZSTD_FRAMEHEADERSIZE_PREFIX(format);
/* only supports formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless */
assert( (format == ZSTD_f_zstd1) || (format == ZSTD_f_zstd1_magicless) );
return startingInputLength;
}
static void ZSTD_DCtx_resetParameters(ZSTD_DCtx* dctx)
{
assert(dctx->streamStage == zdss_init);
dctx->format = ZSTD_f_zstd1;
dctx->maxWindowSize = ZSTD_MAXWINDOWSIZE_DEFAULT;
dctx->outBufferMode = ZSTD_bm_buffered;
dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum;
dctx->refMultipleDDicts = ZSTD_rmd_refSingleDDict;
dctx->disableHufAsm = 0;
dctx->maxBlockSizeParam = 0;
}
static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx)
{
dctx->staticSize = 0;
dctx->ddict = NULL;
dctx->ddictLocal = NULL;
dctx->dictEnd = NULL;
dctx->ddictIsCold = 0;
dctx->dictUses = ZSTD_dont_use;
dctx->inBuff = NULL;
dctx->inBuffSize = 0;
dctx->outBuffSize = 0;
dctx->streamStage = zdss_init;
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
dctx->legacyContext = NULL;
dctx->previousLegacyVersion = 0;
#endif #endif
dctx->noForwardProgress = 0;
dctx->oversizedDuration = 0; #ifndef ZSTD_MAXWINDOWSIZE_DEFAULT
dctx->isFrameDecompression = 1; # define ZSTD_MAXWINDOWSIZE_DEFAULT (((U32)1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT) + 1)
#endif
#ifndef ZSTD_NO_FORWARD_PROGRESS_MAX
# define ZSTD_NO_FORWARD_PROGRESS_MAX 16
#endif
/* Keep this in lock-step with ZSTD_rustDctxView in zstd_decompress.rs.
* Every pointer that names a scalar is the address of that scalar; array and
* embedded-object entries name their first byte. */
typedef struct {
void* dctx;
void* llt_ptr;
void* mlt_ptr;
void* oft_ptr;
void* huf_ptr;
void* entropy;
void* workspace;
size_t workspace_size;
void* previous_dst_end;
void* prefix_start;
void* virtual_start;
void* dict_end;
void* expected;
void* f_params;
void* processed_c_size;
void* decoded_size;
void* b_type;
void* stage;
void* lit_entropy;
void* fse_entropy;
void* xxh_state;
void* header_size;
void* format;
void* force_ignore_checksum;
void* validate_checksum;
void* lit_ptr;
void* custom_mem;
void* lit_size;
void* rle_size;
void* static_size;
void* is_frame_decompression;
void* ddict_local;
void* ddict;
void* dict_id;
void* ddict_is_cold;
void* dict_uses;
void* ddict_set;
void* ref_multiple_ddicts;
void* disable_huf_asm;
void* max_block_size_param;
void* stream_stage;
void* in_buff;
void* in_buff_size;
void* in_pos;
void* max_window_size;
void* out_buff;
void* out_buff_size;
void* out_start;
void* out_end;
void* lh_size;
void* legacy_context;
void* previous_legacy_version;
void* legacy_version;
void* hostage_byte;
void* no_forward_progress;
void* out_buffer_mode;
void* expected_out_buffer;
void* lit_buffer;
void* lit_buffer_end;
void* lit_buffer_location;
void* lit_extra_buffer;
size_t lit_extra_buffer_size;
void* header_buffer;
size_t header_buffer_size;
void* oversized_duration;
void* fuzz_begin;
void* fuzz_end;
size_t dctx_size;
} ZSTD_rustDctxView;
void ZSTD_rust_dctx_view(ZSTD_DCtx* dctx, ZSTD_rustDctxView* out);
size_t ZSTD_rust_dctx_sizeof(void);
ZSTD_DCtx* ZSTD_rust_dctx_alloc(ZSTD_customMem customMem);
void ZSTD_rust_dctx_free_storage(ZSTD_DCtx* dctx, ZSTD_customMem customMem);
void ZSTD_rust_dctx_init_platform(ZSTD_DCtx* dctx);
size_t ZSTD_rust_dctx_default_max_window_size(void);
int ZSTD_rust_no_forward_progress_max(void);
int ZSTD_rust_heapmode(void);
size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity,
const void* src, size_t srcSize);
void* ZSTD_rust_custom_malloc(size_t size, ZSTD_customMem customMem);
void* ZSTD_rust_custom_calloc(size_t size, ZSTD_customMem customMem);
void ZSTD_rust_custom_free(void* allocation, ZSTD_customMem customMem);
ZSTD_DDict* ZSTD_rust_create_ddict(const void* dict, size_t dictSize,
ZSTD_dictLoadMethod_e dictLoadMethod,
ZSTD_dictContentType_e dictContentType,
ZSTD_customMem customMem);
void ZSTD_rust_dctx_copy_prefix(ZSTD_DCtx* dstDCtx, const ZSTD_DCtx* srcDCtx);
void ZSTD_rust_dctx_trace_begin(ZSTD_DCtx* dctx);
void ZSTD_rust_dctx_trace_end(ZSTD_DCtx* dctx, U64 uncompressedSize,
U64 compressedSize, int streaming);
unsigned ZSTD_rust_legacy_is(const void* src, size_t srcSize);
unsigned long long ZSTD_rust_legacy_get_decompressed_size(const void* src,
size_t srcSize);
size_t ZSTD_rust_legacy_find_compressed_size(const void* src, size_t srcSize);
size_t ZSTD_rust_legacy_frame_size_info(const void* src, size_t srcSize,
size_t* compressedSize,
unsigned long long* decompressedBound,
size_t* nbBlocks);
size_t ZSTD_rust_legacy_decompress(void* dst, size_t dstCapacity,
const void* src, size_t srcSize,
const void* dict, size_t dictSize);
size_t ZSTD_rust_legacy_decompress_stream(ZSTD_DCtx* dctx,
ZSTD_outBuffer* output,
ZSTD_inBuffer* input,
const void* dict, size_t dictSize);
void ZSTD_rust_legacy_free_stream(ZSTD_DCtx* dctx);
void ZSTD_rust_dctx_view(ZSTD_DCtx* dctx, ZSTD_rustDctxView* out)
{
ZSTD_memset(out, 0, sizeof(*out));
if (dctx == NULL) return;
out->dctx = dctx;
out->llt_ptr = &dctx->LLTptr;
out->mlt_ptr = &dctx->MLTptr;
out->oft_ptr = &dctx->OFTptr;
out->huf_ptr = &dctx->HUFptr;
out->entropy = &dctx->entropy;
out->workspace = dctx->workspace;
out->workspace_size = sizeof(dctx->workspace);
out->previous_dst_end = &dctx->previousDstEnd;
out->prefix_start = &dctx->prefixStart;
out->virtual_start = &dctx->virtualStart;
out->dict_end = &dctx->dictEnd;
out->expected = &dctx->expected;
out->f_params = &dctx->fParams;
out->processed_c_size = &dctx->processedCSize;
out->decoded_size = &dctx->decodedSize;
out->b_type = &dctx->bType;
out->stage = &dctx->stage;
out->lit_entropy = &dctx->litEntropy;
out->fse_entropy = &dctx->fseEntropy;
out->xxh_state = &dctx->xxhState;
out->header_size = &dctx->headerSize;
out->format = &dctx->format;
out->force_ignore_checksum = &dctx->forceIgnoreChecksum;
out->validate_checksum = &dctx->validateChecksum;
out->lit_ptr = &dctx->litPtr;
out->custom_mem = &dctx->customMem;
out->lit_size = &dctx->litSize;
out->rle_size = &dctx->rleSize;
out->static_size = &dctx->staticSize;
out->is_frame_decompression = &dctx->isFrameDecompression;
out->ddict_local = &dctx->ddictLocal;
out->ddict = &dctx->ddict;
out->dict_id = &dctx->dictID;
out->ddict_is_cold = &dctx->ddictIsCold;
out->dict_uses = &dctx->dictUses;
out->ddict_set = &dctx->ddictSet;
out->ref_multiple_ddicts = &dctx->refMultipleDDicts;
out->disable_huf_asm = &dctx->disableHufAsm;
out->max_block_size_param = &dctx->maxBlockSizeParam;
out->stream_stage = &dctx->streamStage;
out->in_buff = &dctx->inBuff;
out->in_buff_size = &dctx->inBuffSize;
out->in_pos = &dctx->inPos;
out->max_window_size = &dctx->maxWindowSize;
out->out_buff = &dctx->outBuff;
out->out_buff_size = &dctx->outBuffSize;
out->out_start = &dctx->outStart;
out->out_end = &dctx->outEnd;
out->lh_size = &dctx->lhSize;
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
out->legacy_context = &dctx->legacyContext;
out->previous_legacy_version = &dctx->previousLegacyVersion;
out->legacy_version = &dctx->legacyVersion;
#endif
out->hostage_byte = &dctx->hostageByte;
out->no_forward_progress = &dctx->noForwardProgress;
out->out_buffer_mode = &dctx->outBufferMode;
out->expected_out_buffer = &dctx->expectedOutBuffer;
out->lit_buffer = &dctx->litBuffer;
out->lit_buffer_end = &dctx->litBufferEnd;
out->lit_buffer_location = &dctx->litBufferLocation;
out->lit_extra_buffer = dctx->litExtraBuffer;
out->lit_extra_buffer_size = sizeof(dctx->litExtraBuffer);
out->header_buffer = dctx->headerBuffer;
out->header_buffer_size = sizeof(dctx->headerBuffer);
out->oversized_duration = &dctx->oversizedDuration;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
out->fuzz_begin = &dctx->dictContentBeginForFuzzing;
out->fuzz_end = &dctx->dictContentEndForFuzzing;
#endif
out->dctx_size = sizeof(*dctx);
}
size_t ZSTD_rust_dctx_sizeof(void)
{
return sizeof(ZSTD_DCtx);
}
ZSTD_DCtx* ZSTD_rust_dctx_alloc(ZSTD_customMem customMem)
{
if ((!customMem.customAlloc) ^ (!customMem.customFree)) return NULL;
return (ZSTD_DCtx*)ZSTD_customMalloc(sizeof(ZSTD_DCtx), customMem);
}
void ZSTD_rust_dctx_free_storage(ZSTD_DCtx* dctx, ZSTD_customMem customMem)
{
ZSTD_customFree(dctx, customMem);
}
void ZSTD_rust_dctx_init_platform(ZSTD_DCtx* dctx)
{
#if DYNAMIC_BMI2 #if DYNAMIC_BMI2
dctx->bmi2 = ZSTD_cpuSupportsBmi2(); dctx->bmi2 = ZSTD_cpuSupportsBmi2();
#endif #else
dctx->ddictSet = NULL; (void)dctx;
ZSTD_DCtx_resetParameters(dctx);
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
dctx->dictContentEndForFuzzing = NULL;
#endif #endif
} }
ZSTD_DCtx* ZSTD_initStaticDCtx(void *workspace, size_t workspaceSize) size_t ZSTD_rust_dctx_default_max_window_size(void)
{ {
ZSTD_DCtx* const dctx = (ZSTD_DCtx*) workspace; return ZSTD_MAXWINDOWSIZE_DEFAULT;
if ((size_t)workspace & 7) return NULL; /* 8-aligned */
if (workspaceSize < sizeof(ZSTD_DCtx)) return NULL; /* minimum size */
ZSTD_initDCtx_internal(dctx);
dctx->staticSize = workspaceSize;
dctx->inBuff = (char*)(dctx+1);
return dctx;
} }
static ZSTD_DCtx* ZSTD_createDCtx_internal(ZSTD_customMem customMem) { int ZSTD_rust_no_forward_progress_max(void)
if ((!customMem.customAlloc) ^ (!customMem.customFree)) return NULL;
{ ZSTD_DCtx* const dctx = (ZSTD_DCtx*)ZSTD_customMalloc(sizeof(*dctx), customMem);
if (!dctx) return NULL;
dctx->customMem = customMem;
ZSTD_initDCtx_internal(dctx);
return dctx;
}
}
ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem)
{ {
return ZSTD_createDCtx_internal(customMem); return ZSTD_NO_FORWARD_PROGRESS_MAX;
} }
ZSTD_DCtx* ZSTD_createDCtx(void) int ZSTD_rust_heapmode(void)
{ {
DEBUGLOG(3, "ZSTD_createDCtx"); return ZSTD_HEAPMODE;
return ZSTD_createDCtx_internal(ZSTD_defaultCMem);
} }
static void ZSTD_clearDict(ZSTD_DCtx* dctx) size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity,
{
ZSTD_freeDDict(dctx->ddictLocal);
dctx->ddictLocal = NULL;
dctx->ddict = NULL;
dctx->dictUses = ZSTD_dont_use;
}
size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx)
{
if (dctx==NULL) return 0; /* support free on NULL */
RETURN_ERROR_IF(dctx->staticSize, memory_allocation, "not compatible with static DCtx");
{ ZSTD_customMem const cMem = dctx->customMem;
ZSTD_clearDict(dctx);
ZSTD_customFree(dctx->inBuff, cMem);
dctx->inBuff = NULL;
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
if (dctx->legacyContext)
ZSTD_freeLegacyStreamContext(dctx->legacyContext, dctx->previousLegacyVersion);
#endif
if (dctx->ddictSet) {
ZSTD_freeDDictHashSet(dctx->ddictSet, cMem);
dctx->ddictSet = NULL;
}
ZSTD_customFree(dctx, cMem);
return 0;
}
}
/* no longer useful */
void ZSTD_copyDCtx(ZSTD_DCtx* dstDCtx, const ZSTD_DCtx* srcDCtx)
{
size_t const toCopy = (size_t)((char*)(&dstDCtx->inBuff) - (char*)dstDCtx);
ZSTD_memcpy(dstDCtx, srcDCtx, toCopy); /* no need to copy workspace */
}
/* Given a dctx with a digested frame params, re-selects the correct ZSTD_DDict based on
* the requested dict ID from the frame. If there exists a reference to the correct ZSTD_DDict, then
* accordingly sets the ddict to be used to decompress the frame.
*
* If no DDict is found, then no action is taken, and the ZSTD_DCtx::ddict remains as-is.
*
* ZSTD_d_refMultipleDDicts must be enabled for this function to be called.
*/
static void ZSTD_DCtx_selectFrameDDict(ZSTD_DCtx* dctx) {
assert(dctx->refMultipleDDicts && dctx->ddictSet);
DEBUGLOG(4, "Adjusting DDict based on requested dict ID from frame");
if (dctx->ddict) {
const ZSTD_DDict* frameDDict = ZSTD_DDictHashSet_getDDict(dctx->ddictSet, dctx->fParams.dictID);
if (frameDDict) {
DEBUGLOG(4, "DDict found!");
ZSTD_clearDict(dctx);
dctx->dictID = dctx->fParams.dictID;
dctx->ddict = frameDDict;
dctx->dictUses = ZSTD_use_indefinitely;
}
}
}
/*-*************************************************************
* Frame header decoding
***************************************************************/
/*! ZSTD_isFrame() :
* Tells if the content of `buffer` starts with a valid Frame Identifier.
* Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0.
* Note 2 : Legacy Frame Identifiers are considered valid only if Legacy Support is enabled.
* Note 3 : Skippable Frame Identifiers are considered valid. */
unsigned ZSTD_isFrame(const void* buffer, size_t size)
{
if (size < ZSTD_FRAMEIDSIZE) return 0;
{ U32 const magic = MEM_readLE32(buffer);
if (magic == ZSTD_MAGICNUMBER) return 1;
if ((magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) return 1;
}
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
if (ZSTD_isLegacy(buffer, size)) return 1;
#endif
return 0;
}
/*! ZSTD_isSkippableFrame() :
* Tells if the content of `buffer` starts with a valid Frame Identifier for a skippable frame.
* Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0.
*/
unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size)
{
if (size < ZSTD_FRAMEIDSIZE) return 0;
{ U32 const magic = MEM_readLE32(buffer);
if ((magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) return 1;
}
return 0;
}
/** ZSTD_frameHeaderSize_internal() :
* srcSize must be large enough to reach header size fields.
* note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless.
* @return : size of the Frame Header
* or an error code, which can be tested with ZSTD_isError() */
static size_t ZSTD_frameHeaderSize_internal(const void* src, size_t srcSize, ZSTD_format_e format)
{
size_t const minInputSize = ZSTD_startingInputLength(format);
RETURN_ERROR_IF(srcSize < minInputSize, srcSize_wrong, "");
{ BYTE const fhd = ((const BYTE*)src)[minInputSize-1];
U32 const dictID= fhd & 3;
U32 const singleSegment = (fhd >> 5) & 1;
U32 const fcsId = fhd >> 6;
return minInputSize + !singleSegment
+ ZSTD_did_fieldSize[dictID] + ZSTD_fcs_fieldSize[fcsId]
+ (singleSegment && !fcsId);
}
}
/** ZSTD_frameHeaderSize() :
* srcSize must be >= ZSTD_frameHeaderSize_prefix.
* @return : size of the Frame Header,
* or an error code (if srcSize is too small) */
size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize)
{
return ZSTD_frameHeaderSize_internal(src, srcSize, ZSTD_f_zstd1);
}
/** ZSTD_getFrameHeader_advanced() :
* decode Frame Header, or require larger `srcSize`.
* note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless
* @return : 0, `zfhPtr` is correctly filled,
* >0, `srcSize` is too small, value is wanted `srcSize` amount,
** or an error code, which can be tested using ZSTD_isError() */
size_t ZSTD_getFrameHeader_advanced(ZSTD_FrameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format)
{
const BYTE* ip = (const BYTE*)src;
size_t const minInputSize = ZSTD_startingInputLength(format);
DEBUGLOG(5, "ZSTD_getFrameHeader_advanced: minInputSize = %zu, srcSize = %zu", minInputSize, srcSize);
if (srcSize > 0) {
/* note : technically could be considered an assert(), since it's an invalid entry */
RETURN_ERROR_IF(src==NULL, GENERIC, "invalid parameter : src==NULL, but srcSize>0");
}
if (srcSize < minInputSize) {
if (srcSize > 0 && format != ZSTD_f_zstd1_magicless) {
/* when receiving less than @minInputSize bytes,
* control these bytes at least correspond to a supported magic number
* in order to error out early if they don't.
**/
size_t const toCopy = MIN(4, srcSize);
unsigned char hbuf[4]; MEM_writeLE32(hbuf, ZSTD_MAGICNUMBER);
assert(src != NULL);
ZSTD_memcpy(hbuf, src, toCopy);
if ( MEM_readLE32(hbuf) != ZSTD_MAGICNUMBER ) {
/* not a zstd frame : let's check if it's a skippable frame */
MEM_writeLE32(hbuf, ZSTD_MAGIC_SKIPPABLE_START);
ZSTD_memcpy(hbuf, src, toCopy);
if ((MEM_readLE32(hbuf) & ZSTD_MAGIC_SKIPPABLE_MASK) != ZSTD_MAGIC_SKIPPABLE_START) {
RETURN_ERROR(prefix_unknown,
"first bytes don't correspond to any supported magic number");
} } }
return minInputSize;
}
ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr)); /* not strictly necessary, but static analyzers may not understand that zfhPtr will be read only if return value is zero, since they are 2 different signals */
if ( (format != ZSTD_f_zstd1_magicless)
&& (MEM_readLE32(src) != ZSTD_MAGICNUMBER) ) {
if ((MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
/* skippable frame */
if (srcSize < ZSTD_SKIPPABLEHEADERSIZE)
return ZSTD_SKIPPABLEHEADERSIZE; /* magic number + frame length */
ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr));
zfhPtr->frameType = ZSTD_skippableFrame;
zfhPtr->dictID = MEM_readLE32(src) - ZSTD_MAGIC_SKIPPABLE_START;
zfhPtr->headerSize = ZSTD_SKIPPABLEHEADERSIZE;
zfhPtr->frameContentSize = MEM_readLE32((const char *)src + ZSTD_FRAMEIDSIZE);
return 0;
}
RETURN_ERROR(prefix_unknown, "");
}
/* ensure there is enough `srcSize` to fully read/decode frame header */
{ size_t const fhsize = ZSTD_frameHeaderSize_internal(src, srcSize, format);
if (srcSize < fhsize) return fhsize;
zfhPtr->headerSize = (U32)fhsize;
}
{ BYTE const fhdByte = ip[minInputSize-1];
size_t pos = minInputSize;
U32 const dictIDSizeCode = fhdByte&3;
U32 const checksumFlag = (fhdByte>>2)&1;
U32 const singleSegment = (fhdByte>>5)&1;
U32 const fcsID = fhdByte>>6;
U64 windowSize = 0;
U32 dictID = 0;
U64 frameContentSize = ZSTD_CONTENTSIZE_UNKNOWN;
RETURN_ERROR_IF((fhdByte & 0x08) != 0, frameParameter_unsupported,
"reserved bits, must be zero");
if (!singleSegment) {
BYTE const wlByte = ip[pos++];
U32 const windowLog = (wlByte >> 3) + ZSTD_WINDOWLOG_ABSOLUTEMIN;
RETURN_ERROR_IF(windowLog > ZSTD_WINDOWLOG_MAX, frameParameter_windowTooLarge, "");
windowSize = (1ULL << windowLog);
windowSize += (windowSize >> 3) * (wlByte&7);
}
switch(dictIDSizeCode)
{
default:
assert(0); /* impossible */
ZSTD_FALLTHROUGH;
case 0 : break;
case 1 : dictID = ip[pos]; pos++; break;
case 2 : dictID = MEM_readLE16(ip+pos); pos+=2; break;
case 3 : dictID = MEM_readLE32(ip+pos); pos+=4; break;
}
switch(fcsID)
{
default:
assert(0); /* impossible */
ZSTD_FALLTHROUGH;
case 0 : if (singleSegment) frameContentSize = ip[pos]; break;
case 1 : frameContentSize = MEM_readLE16(ip+pos)+256; break;
case 2 : frameContentSize = MEM_readLE32(ip+pos); break;
case 3 : frameContentSize = MEM_readLE64(ip+pos); break;
}
if (singleSegment) windowSize = frameContentSize;
zfhPtr->frameType = ZSTD_frame;
zfhPtr->frameContentSize = frameContentSize;
zfhPtr->windowSize = windowSize;
zfhPtr->blockSizeMax = (unsigned) MIN(windowSize, ZSTD_BLOCKSIZE_MAX);
zfhPtr->dictID = dictID;
zfhPtr->checksumFlag = checksumFlag;
}
return 0;
}
/** ZSTD_getFrameHeader() :
* decode Frame Header, or require larger `srcSize`.
* note : this function does not consume input, it only reads it.
* @return : 0, `zfhPtr` is correctly filled,
* >0, `srcSize` is too small, value is wanted `srcSize` amount,
* or an error code, which can be tested using ZSTD_isError() */
size_t ZSTD_getFrameHeader(ZSTD_FrameHeader* zfhPtr, const void* src, size_t srcSize)
{
return ZSTD_getFrameHeader_advanced(zfhPtr, src, srcSize, ZSTD_f_zstd1);
}
/** ZSTD_getFrameContentSize() :
* compatible with legacy mode
* @return : decompressed size of the single frame pointed to be `src` if known, otherwise
* - ZSTD_CONTENTSIZE_UNKNOWN if the size cannot be determined
* - ZSTD_CONTENTSIZE_ERROR if an error occurred (e.g. invalid magic number, srcSize too small) */
unsigned long long ZSTD_getFrameContentSize(const void *src, size_t srcSize)
{
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
if (ZSTD_isLegacy(src, srcSize)) {
unsigned long long const ret = ZSTD_getDecompressedSize_legacy(src, srcSize);
return ret == 0 ? ZSTD_CONTENTSIZE_UNKNOWN : ret;
}
#endif
{ ZSTD_FrameHeader zfh;
if (ZSTD_getFrameHeader(&zfh, src, srcSize) != 0)
return ZSTD_CONTENTSIZE_ERROR;
if (zfh.frameType == ZSTD_skippableFrame) {
return 0;
} else {
return zfh.frameContentSize;
} }
}
static size_t readSkippableFrameSize(void const* src, size_t srcSize)
{
size_t const skippableHeaderSize = ZSTD_SKIPPABLEHEADERSIZE;
U32 sizeU32;
RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, "");
sizeU32 = MEM_readLE32((BYTE const*)src + ZSTD_FRAMEIDSIZE);
RETURN_ERROR_IF((U32)(sizeU32 + ZSTD_SKIPPABLEHEADERSIZE) < sizeU32,
frameParameter_unsupported, "");
{ size_t const skippableSize = skippableHeaderSize + sizeU32;
RETURN_ERROR_IF(skippableSize > srcSize, srcSize_wrong, "");
return skippableSize;
}
}
/*! ZSTD_readSkippableFrame() :
* Retrieves content of a skippable frame, and writes it to dst buffer.
*
* The parameter magicVariant will receive the magicVariant that was supplied when the frame was written,
* i.e. magicNumber - ZSTD_MAGIC_SKIPPABLE_START. This can be NULL if the caller is not interested
* in the magicVariant.
*
* Returns an error if destination buffer is not large enough, or if this is not a valid skippable frame.
*
* @return : number of bytes written or a ZSTD error.
*/
size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity,
unsigned* magicVariant, /* optional, can be NULL */
const void* src, size_t srcSize) const void* src, size_t srcSize)
{ {
RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, ""); #if ZSTD_HEAPMODE >= 1
(void)dst;
{ U32 const magicNumber = MEM_readLE32(src); (void)dstCapacity;
size_t skippableFrameSize = readSkippableFrameSize(src, srcSize); (void)src;
size_t skippableContentSize = skippableFrameSize - ZSTD_SKIPPABLEHEADERSIZE; (void)srcSize;
return ERROR(GENERIC);
/* check input validity */ #else
RETURN_ERROR_IF(!ZSTD_isSkippableFrame(src, srcSize), frameParameter_unsupported, ""); ZSTD_DCtx dctx;
RETURN_ERROR_IF(skippableFrameSize < ZSTD_SKIPPABLEHEADERSIZE || skippableFrameSize > srcSize, srcSize_wrong, ""); ZSTD_DCtx* const initialized = ZSTD_initStaticDCtx(&dctx, sizeof(dctx));
RETURN_ERROR_IF(skippableContentSize > dstCapacity, dstSize_tooSmall, ""); if (initialized == NULL) return ERROR(memory_allocation);
/* This is a stack DCtx, not a user-provided static workspace. Keep the
/* deliver payload */ * original heapmode=0 semantics so legacy decoding is permitted and no
if (skippableContentSize > 0 && dst != NULL) * static-context allocation restrictions leak into the one-shot API. */
ZSTD_memcpy(dst, (const BYTE *)src + ZSTD_SKIPPABLEHEADERSIZE, skippableContentSize); initialized->staticSize = 0;
if (magicVariant != NULL) return ZSTD_decompressDCtx(initialized, dst, dstCapacity, src, srcSize);
*magicVariant = magicNumber - ZSTD_MAGIC_SKIPPABLE_START;
return skippableContentSize;
}
}
/** ZSTD_findDecompressedSize() :
* `srcSize` must be the exact length of some number of ZSTD compressed and/or
* skippable frames
* note: compatible with legacy mode
* @return : decompressed size of the frames contained */
unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize)
{
unsigned long long totalDstSize = 0;
while (srcSize >= ZSTD_startingInputLength(ZSTD_f_zstd1)) {
U32 const magicNumber = MEM_readLE32(src);
if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
size_t const skippableSize = readSkippableFrameSize(src, srcSize);
if (ZSTD_isError(skippableSize)) return ZSTD_CONTENTSIZE_ERROR;
assert(skippableSize <= srcSize);
src = (const BYTE *)src + skippableSize;
srcSize -= skippableSize;
continue;
}
{ unsigned long long const fcs = ZSTD_getFrameContentSize(src, srcSize);
if (fcs >= ZSTD_CONTENTSIZE_ERROR) return fcs;
if (totalDstSize + fcs < totalDstSize)
return ZSTD_CONTENTSIZE_ERROR; /* check for overflow */
totalDstSize += fcs;
}
/* skip to next frame */
{ size_t const frameSrcSize = ZSTD_findFrameCompressedSize(src, srcSize);
if (ZSTD_isError(frameSrcSize)) return ZSTD_CONTENTSIZE_ERROR;
assert(frameSrcSize <= srcSize);
src = (const BYTE *)src + frameSrcSize;
srcSize -= frameSrcSize;
}
} /* while (srcSize >= ZSTD_frameHeaderSize_prefix) */
if (srcSize) return ZSTD_CONTENTSIZE_ERROR;
return totalDstSize;
}
/** ZSTD_getDecompressedSize() :
* compatible with legacy mode
* @return : decompressed size if known, 0 otherwise
note : 0 can mean any of the following :
- frame content is empty
- decompressed size field is not present in frame header
- frame header unknown / not supported
- frame header not complete (`srcSize` too small) */
unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize)
{
unsigned long long const ret = ZSTD_getFrameContentSize(src, srcSize);
ZSTD_STATIC_ASSERT(ZSTD_CONTENTSIZE_ERROR < ZSTD_CONTENTSIZE_UNKNOWN);
return (ret >= ZSTD_CONTENTSIZE_ERROR) ? 0 : ret;
}
/** ZSTD_decodeFrameHeader() :
* `headerSize` must be the size provided by ZSTD_frameHeaderSize().
* If multiple DDict references are enabled, also will choose the correct DDict to use.
* @return : 0 if success, or an error code, which can be tested using ZSTD_isError() */
static size_t ZSTD_decodeFrameHeader(ZSTD_DCtx* dctx, const void* src, size_t headerSize)
{
size_t const result = ZSTD_getFrameHeader_advanced(&(dctx->fParams), src, headerSize, dctx->format);
if (ZSTD_isError(result)) return result; /* invalid header */
RETURN_ERROR_IF(result>0, srcSize_wrong, "headerSize too small");
/* Reference DDict requested by frame if dctx references multiple ddicts */
if (dctx->refMultipleDDicts == ZSTD_rmd_refMultipleDDicts && dctx->ddictSet) {
ZSTD_DCtx_selectFrameDDict(dctx);
}
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
/* Skip the dictID check in fuzzing mode, because it makes the search
* harder.
*/
RETURN_ERROR_IF(dctx->fParams.dictID && (dctx->dictID != dctx->fParams.dictID),
dictionary_wrong, "");
#endif #endif
dctx->validateChecksum = (dctx->fParams.checksumFlag && !dctx->forceIgnoreChecksum) ? 1 : 0;
if (dctx->validateChecksum) XXH64_reset(&dctx->xxhState, 0);
dctx->processedCSize += headerSize;
return 0;
} }
static ZSTD_frameSizeInfo ZSTD_errorFrameSizeInfo(size_t ret) void* ZSTD_rust_custom_malloc(size_t size, ZSTD_customMem customMem)
{ {
ZSTD_frameSizeInfo frameSizeInfo; return ZSTD_customMalloc(size, customMem);
frameSizeInfo.compressedSize = ret;
frameSizeInfo.decompressedBound = ZSTD_CONTENTSIZE_ERROR;
return frameSizeInfo;
} }
static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize, ZSTD_format_e format) void* ZSTD_rust_custom_calloc(size_t size, ZSTD_customMem customMem)
{ {
ZSTD_frameSizeInfo frameSizeInfo; return ZSTD_customCalloc(size, customMem);
ZSTD_memset(&frameSizeInfo, 0, sizeof(ZSTD_frameSizeInfo)); }
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) void ZSTD_rust_custom_free(void* allocation, ZSTD_customMem customMem)
if (format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) {
return ZSTD_findFrameSizeInfoLegacy(src, srcSize); ZSTD_customFree(allocation, customMem);
}
ZSTD_DDict* ZSTD_rust_create_ddict(const void* dict, size_t dictSize,
ZSTD_dictLoadMethod_e dictLoadMethod,
ZSTD_dictContentType_e dictContentType,
ZSTD_customMem customMem)
{
return ZSTD_createDDict_advanced(dict, dictSize, dictLoadMethod,
dictContentType, customMem);
}
void ZSTD_rust_dctx_copy_prefix(ZSTD_DCtx* dstDCtx, const ZSTD_DCtx* srcDCtx)
{
size_t const toCopy = (size_t)((const char*)(&dstDCtx->inBuff) - (const char*)dstDCtx);
ZSTD_memcpy(dstDCtx, srcDCtx, toCopy);
}
void ZSTD_rust_dctx_trace_begin(ZSTD_DCtx* dctx)
{
#if ZSTD_TRACE
dctx->traceCtx = (ZSTD_trace_decompress_begin != NULL)
? ZSTD_trace_decompress_begin(dctx) : 0;
#else
(void)dctx;
#endif #endif
if (format == ZSTD_f_zstd1 && (srcSize >= ZSTD_SKIPPABLEHEADERSIZE)
&& (MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
frameSizeInfo.compressedSize = readSkippableFrameSize(src, srcSize);
assert(ZSTD_isError(frameSizeInfo.compressedSize) ||
frameSizeInfo.compressedSize <= srcSize);
return frameSizeInfo;
} else {
const BYTE* ip = (const BYTE*)src;
const BYTE* const ipstart = ip;
size_t remainingSize = srcSize;
size_t nbBlocks = 0;
ZSTD_FrameHeader zfh;
/* Extract Frame Header */
{ size_t const ret = ZSTD_getFrameHeader_advanced(&zfh, src, srcSize, format);
if (ZSTD_isError(ret))
return ZSTD_errorFrameSizeInfo(ret);
if (ret > 0)
return ZSTD_errorFrameSizeInfo(ERROR(srcSize_wrong));
} }
ip += zfh.headerSize; void ZSTD_rust_dctx_trace_end(ZSTD_DCtx* dctx, U64 uncompressedSize,
remainingSize -= zfh.headerSize; U64 compressedSize, int streaming)
/* Iterate over each block */
while (1) {
blockProperties_t blockProperties;
size_t const cBlockSize = ZSTD_getcBlockSize(ip, remainingSize, &blockProperties);
if (ZSTD_isError(cBlockSize))
return ZSTD_errorFrameSizeInfo(cBlockSize);
if (ZSTD_blockHeaderSize + cBlockSize > remainingSize)
return ZSTD_errorFrameSizeInfo(ERROR(srcSize_wrong));
ip += ZSTD_blockHeaderSize + cBlockSize;
remainingSize -= ZSTD_blockHeaderSize + cBlockSize;
nbBlocks++;
if (blockProperties.lastBlock) break;
}
/* Final frame content checksum */
if (zfh.checksumFlag) {
if (remainingSize < 4)
return ZSTD_errorFrameSizeInfo(ERROR(srcSize_wrong));
ip += 4;
}
frameSizeInfo.nbBlocks = nbBlocks;
frameSizeInfo.compressedSize = (size_t)(ip - ipstart);
frameSizeInfo.decompressedBound = (zfh.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN)
? zfh.frameContentSize
: (unsigned long long)nbBlocks * zfh.blockSizeMax;
return frameSizeInfo;
}
}
static size_t ZSTD_findFrameCompressedSize_advanced(const void *src, size_t srcSize, ZSTD_format_e format) {
ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, format);
return frameSizeInfo.compressedSize;
}
/** ZSTD_findFrameCompressedSize() :
* See docs in zstd.h
* Note: compatible with legacy mode */
size_t ZSTD_findFrameCompressedSize(const void *src, size_t srcSize)
{
return ZSTD_findFrameCompressedSize_advanced(src, srcSize, ZSTD_f_zstd1);
}
/** ZSTD_decompressBound() :
* compatible with legacy mode
* `src` must point to the start of a ZSTD frame or a skippable frame
* `srcSize` must be at least as large as the frame contained
* @return : the maximum decompressed size of the compressed source
*/
unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize)
{
unsigned long long bound = 0;
/* Iterate over each frame */
while (srcSize > 0) {
ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, ZSTD_f_zstd1);
size_t const compressedSize = frameSizeInfo.compressedSize;
unsigned long long const decompressedBound = frameSizeInfo.decompressedBound;
if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR)
return ZSTD_CONTENTSIZE_ERROR;
assert(srcSize >= compressedSize);
src = (const BYTE*)src + compressedSize;
srcSize -= compressedSize;
bound += decompressedBound;
}
return bound;
}
size_t ZSTD_decompressionMargin(void const* src, size_t srcSize)
{
size_t margin = 0;
unsigned maxBlockSize = 0;
/* Iterate over each frame */
while (srcSize > 0) {
ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, ZSTD_f_zstd1);
size_t const compressedSize = frameSizeInfo.compressedSize;
unsigned long long const decompressedBound = frameSizeInfo.decompressedBound;
ZSTD_FrameHeader zfh;
FORWARD_IF_ERROR(ZSTD_getFrameHeader(&zfh, src, srcSize), "");
if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR)
return ERROR(corruption_detected);
if (zfh.frameType == ZSTD_frame) {
/* Add the frame header to our margin */
margin += zfh.headerSize;
/* Add the checksum to our margin */
margin += zfh.checksumFlag ? 4 : 0;
/* Add 3 bytes per block */
margin += 3 * frameSizeInfo.nbBlocks;
/* Compute the max block size */
maxBlockSize = MAX(maxBlockSize, zfh.blockSizeMax);
} else {
assert(zfh.frameType == ZSTD_skippableFrame);
/* Add the entire skippable frame size to our margin. */
margin += compressedSize;
}
assert(srcSize >= compressedSize);
src = (const BYTE*)src + compressedSize;
srcSize -= compressedSize;
}
/* Add the max block size back to the margin. */
margin += maxBlockSize;
return margin;
}
/*-*************************************************************
* Frame decoding
***************************************************************/
/** ZSTD_insertBlock() :
* insert `src` block into `dctx` history. Useful to track uncompressed blocks. */
size_t ZSTD_insertBlock(ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize)
{
DEBUGLOG(5, "ZSTD_insertBlock: %u bytes", (unsigned)blockSize);
ZSTD_checkContinuity(dctx, blockStart, blockSize);
dctx->previousDstEnd = (const char*)blockStart + blockSize;
return blockSize;
}
static size_t ZSTD_copyRawBlock(void* dst, size_t dstCapacity,
const void* src, size_t srcSize)
{
DEBUGLOG(5, "ZSTD_copyRawBlock");
RETURN_ERROR_IF(srcSize > dstCapacity, dstSize_tooSmall, "");
if (dst == NULL) {
if (srcSize == 0) return 0;
RETURN_ERROR(dstBuffer_null, "");
}
ZSTD_memmove(dst, src, srcSize);
return srcSize;
}
static size_t ZSTD_setRleBlock(void* dst, size_t dstCapacity,
BYTE b,
size_t regenSize)
{
RETURN_ERROR_IF(regenSize > dstCapacity, dstSize_tooSmall, "");
if (dst == NULL) {
if (regenSize == 0) return 0;
RETURN_ERROR(dstBuffer_null, "");
}
ZSTD_memset(dst, b, regenSize);
return regenSize;
}
static void ZSTD_DCtx_trace_end(ZSTD_DCtx const* dctx, U64 uncompressedSize, U64 compressedSize, int streaming)
{ {
#if ZSTD_TRACE #if ZSTD_TRACE
if (dctx->traceCtx && ZSTD_trace_decompress_end != NULL) { if (dctx->traceCtx && ZSTD_trace_decompress_end != NULL) {
@@ -945,1466 +356,127 @@ static void ZSTD_DCtx_trace_end(ZSTD_DCtx const* dctx, U64 uncompressedSize, U64
#endif #endif
} }
unsigned ZSTD_rust_legacy_is(const void* src, size_t srcSize)
/*! ZSTD_decompressFrame() :
* @dctx must be properly initialized
* will update *srcPtr and *srcSizePtr,
* to make *srcPtr progress by one frame. */
static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx,
void* dst, size_t dstCapacity,
const void** srcPtr, size_t *srcSizePtr)
{ {
const BYTE* const istart = (const BYTE*)(*srcPtr);
const BYTE* ip = istart;
BYTE* const ostart = (BYTE*)dst;
BYTE* const oend = dstCapacity != 0 ? ostart + dstCapacity : ostart;
BYTE* op = ostart;
size_t remainingSrcSize = *srcSizePtr;
DEBUGLOG(4, "ZSTD_decompressFrame (srcSize:%i)", (int)*srcSizePtr);
/* check */
RETURN_ERROR_IF(
remainingSrcSize < ZSTD_FRAMEHEADERSIZE_MIN(dctx->format)+ZSTD_blockHeaderSize,
srcSize_wrong, "");
/* Frame Header */
{ size_t const frameHeaderSize = ZSTD_frameHeaderSize_internal(
ip, ZSTD_FRAMEHEADERSIZE_PREFIX(dctx->format), dctx->format);
if (ZSTD_isError(frameHeaderSize)) return frameHeaderSize;
RETURN_ERROR_IF(remainingSrcSize < frameHeaderSize+ZSTD_blockHeaderSize,
srcSize_wrong, "");
FORWARD_IF_ERROR( ZSTD_decodeFrameHeader(dctx, ip, frameHeaderSize) , "");
ip += frameHeaderSize; remainingSrcSize -= frameHeaderSize;
}
/* Shrink the blockSizeMax if enabled */
if (dctx->maxBlockSizeParam != 0)
dctx->fParams.blockSizeMax = MIN(dctx->fParams.blockSizeMax, (unsigned)dctx->maxBlockSizeParam);
/* Loop on each block */
while (1) {
BYTE* oBlockEnd = oend;
size_t decodedSize;
blockProperties_t blockProperties;
size_t const cBlockSize = ZSTD_getcBlockSize(ip, remainingSrcSize, &blockProperties);
if (ZSTD_isError(cBlockSize)) return cBlockSize;
ip += ZSTD_blockHeaderSize;
remainingSrcSize -= ZSTD_blockHeaderSize;
RETURN_ERROR_IF(cBlockSize > remainingSrcSize, srcSize_wrong, "");
if (ip >= op && ip < oBlockEnd) {
/* We are decompressing in-place. Limit the output pointer so that we
* don't overwrite the block that we are currently reading. This will
* fail decompression if the input & output pointers aren't spaced
* far enough apart.
*
* This is important to set, even when the pointers are far enough
* apart, because ZSTD_decompressBlock_internal() can decide to store
* literals in the output buffer, after the block it is decompressing.
* Since we don't want anything to overwrite our input, we have to tell
* ZSTD_decompressBlock_internal to never write past ip.
*
* See ZSTD_allocateLiteralsBuffer() for reference.
*/
oBlockEnd = op + (ip - op);
}
switch(blockProperties.blockType)
{
case bt_compressed:
assert(dctx->isFrameDecompression == 1);
decodedSize = ZSTD_decompressBlock_internal(dctx, op, (size_t)(oBlockEnd-op), ip, cBlockSize, not_streaming);
break;
case bt_raw :
/* Use oend instead of oBlockEnd because this function is safe to overlap. It uses memmove. */
decodedSize = ZSTD_copyRawBlock(op, (size_t)(oend-op), ip, cBlockSize);
break;
case bt_rle :
decodedSize = ZSTD_setRleBlock(op, (size_t)(oBlockEnd-op), *ip, blockProperties.origSize);
break;
case bt_reserved :
default:
RETURN_ERROR(corruption_detected, "invalid block type");
}
FORWARD_IF_ERROR(decodedSize, "Block decompression failure");
DEBUGLOG(5, "Decompressed block of dSize = %u", (unsigned)decodedSize);
if (dctx->validateChecksum) {
XXH64_update(&dctx->xxhState, op, decodedSize);
}
if (decodedSize) /* support dst = NULL,0 */ {
op += decodedSize;
}
assert(ip != NULL);
ip += cBlockSize;
remainingSrcSize -= cBlockSize;
if (blockProperties.lastBlock) break;
}
if (dctx->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN) {
RETURN_ERROR_IF((U64)(op-ostart) != dctx->fParams.frameContentSize,
corruption_detected, "");
}
if (dctx->fParams.checksumFlag) { /* Frame content checksum verification */
RETURN_ERROR_IF(remainingSrcSize<4, checksum_wrong, "");
if (!dctx->forceIgnoreChecksum) {
U32 const checkCalc = (U32)XXH64_digest(&dctx->xxhState);
U32 checkRead;
checkRead = MEM_readLE32(ip);
RETURN_ERROR_IF(checkRead != checkCalc, checksum_wrong, "");
}
ip += 4;
remainingSrcSize -= 4;
}
ZSTD_DCtx_trace_end(dctx, (U64)(op-ostart), (U64)(ip-istart), /* streaming */ 0);
/* Allow caller to get size read */
DEBUGLOG(4, "ZSTD_decompressFrame: decompressed frame of size %i, consuming %i bytes of input", (int)(op-ostart), (int)(ip - (const BYTE*)*srcPtr));
*srcPtr = ip;
*srcSizePtr = remainingSrcSize;
return (size_t)(op-ostart);
}
static
ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx,
void* dst, size_t dstCapacity,
const void* src, size_t srcSize,
const void* dict, size_t dictSize,
const ZSTD_DDict* ddict)
{
void* const dststart = dst;
int moreThan1Frame = 0;
DEBUGLOG(5, "ZSTD_decompressMultiFrame");
assert(dict==NULL || ddict==NULL); /* either dict or ddict set, not both */
if (ddict) {
dict = ZSTD_DDict_dictContent(ddict);
dictSize = ZSTD_DDict_dictSize(ddict);
}
while (srcSize >= ZSTD_startingInputLength(dctx->format)) {
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
if (dctx->format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) { return ZSTD_isLegacy(src, srcSize);
size_t decodedSize; #else
size_t const frameSize = ZSTD_findFrameCompressedSizeLegacy(src, srcSize); (void)src;
if (ZSTD_isError(frameSize)) return frameSize; (void)srcSize;
RETURN_ERROR_IF(dctx->staticSize, memory_allocation, return 0;
"legacy support is not compatible with static dctx");
decodedSize = ZSTD_decompressLegacy(dst, dstCapacity, src, frameSize, dict, dictSize);
if (ZSTD_isError(decodedSize)) return decodedSize;
{
unsigned long long const expectedSize = ZSTD_getFrameContentSize(src, srcSize);
RETURN_ERROR_IF(expectedSize == ZSTD_CONTENTSIZE_ERROR, corruption_detected, "Corrupted frame header!");
if (expectedSize != ZSTD_CONTENTSIZE_UNKNOWN) {
RETURN_ERROR_IF(expectedSize != decodedSize, corruption_detected,
"Frame header size does not match decoded size!");
}
}
assert(decodedSize <= dstCapacity);
dst = (BYTE*)dst + decodedSize;
dstCapacity -= decodedSize;
src = (const BYTE*)src + frameSize;
srcSize -= frameSize;
continue;
}
#endif #endif
if (dctx->format == ZSTD_f_zstd1 && srcSize >= 4) {
U32 const magicNumber = MEM_readLE32(src);
DEBUGLOG(5, "reading magic number %08X", (unsigned)magicNumber);
if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
/* skippable frame detected : skip it */
size_t const skippableSize = readSkippableFrameSize(src, srcSize);
FORWARD_IF_ERROR(skippableSize, "invalid skippable frame");
assert(skippableSize <= srcSize);
src = (const BYTE *)src + skippableSize;
srcSize -= skippableSize;
continue; /* check next frame */
} }
if (ddict) {
/* we were called from ZSTD_decompress_usingDDict */
FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDDict(dctx, ddict), "");
} else {
/* this will initialize correctly with no dict if dict == NULL, so
* use this in all cases but ddict */
FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDict(dctx, dict, dictSize), "");
}
ZSTD_checkContinuity(dctx, dst, dstCapacity);
{ const size_t res = ZSTD_decompressFrame(dctx, dst, dstCapacity,
&src, &srcSize);
RETURN_ERROR_IF(
(ZSTD_getErrorCode(res) == ZSTD_error_prefix_unknown)
&& (moreThan1Frame==1),
srcSize_wrong,
"At least one frame successfully completed, "
"but following bytes are garbage: "
"it's more likely to be a srcSize error, "
"specifying more input bytes than size of frame(s). "
"Note: one could be unlucky, it might be a corruption error instead, "
"happening right at the place where we expect zstd magic bytes. "
"But this is _much_ less likely than a srcSize field error.");
if (ZSTD_isError(res)) return res;
assert(res <= dstCapacity);
if (res != 0)
dst = (BYTE*)dst + res;
dstCapacity -= res;
}
moreThan1Frame = 1;
} /* while (srcSize >= ZSTD_frameHeaderSize_prefix) */
RETURN_ERROR_IF(srcSize, srcSize_wrong, "input not entirely consumed");
return (size_t)((BYTE*)dst - (BYTE*)dststart);
} }
size_t ZSTD_decompress_usingDict(ZSTD_DCtx* dctx, unsigned long long ZSTD_rust_legacy_get_decompressed_size(const void* src, size_t srcSize)
void* dst, size_t dstCapacity, {
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
return ZSTD_getDecompressedSize_legacy(src, srcSize);
#else
(void)src;
(void)srcSize;
return 0;
#endif
}
size_t ZSTD_rust_legacy_find_compressed_size(const void* src, size_t srcSize)
{
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
return ZSTD_findFrameCompressedSizeLegacy(src, srcSize);
#else
(void)src;
(void)srcSize;
return ERROR(prefix_unknown);
#endif
}
size_t ZSTD_rust_legacy_frame_size_info(const void* src, size_t srcSize,
size_t* compressedSize,
unsigned long long* decompressedBound,
size_t* nbBlocks)
{
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
ZSTD_frameSizeInfo const info = ZSTD_findFrameSizeInfoLegacy(src, srcSize);
*compressedSize = info.compressedSize;
*decompressedBound = info.decompressedBound;
*nbBlocks = info.decompressedBound == ZSTD_CONTENTSIZE_ERROR
? 0 : (size_t)(info.decompressedBound / ZSTD_BLOCKSIZE_MAX);
return ZSTD_isError(info.compressedSize) ? info.compressedSize : 0;
#else
(void)src;
(void)srcSize;
*compressedSize = ERROR(prefix_unknown);
*decompressedBound = ZSTD_CONTENTSIZE_ERROR;
*nbBlocks = 0;
return ERROR(prefix_unknown);
#endif
}
size_t ZSTD_rust_legacy_decompress(void* dst, size_t dstCapacity,
const void* src, size_t srcSize, const void* src, size_t srcSize,
const void* dict, size_t dictSize) const void* dict, size_t dictSize)
{ {
return ZSTD_decompressMultiFrame(dctx, dst, dstCapacity, src, srcSize, dict, dictSize, NULL); #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
} return ZSTD_decompressLegacy(dst, dstCapacity, src, srcSize, dict, dictSize);
static ZSTD_DDict const* ZSTD_getDDict(ZSTD_DCtx* dctx)
{
switch (dctx->dictUses) {
default:
assert(0 /* Impossible */);
ZSTD_FALLTHROUGH;
case ZSTD_dont_use:
ZSTD_clearDict(dctx);
return NULL;
case ZSTD_use_indefinitely:
return dctx->ddict;
case ZSTD_use_once:
dctx->dictUses = ZSTD_dont_use;
return dctx->ddict;
}
}
size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize)
{
return ZSTD_decompress_usingDDict(dctx, dst, dstCapacity, src, srcSize, ZSTD_getDDict(dctx));
}
size_t ZSTD_decompress(void* dst, size_t dstCapacity, const void* src, size_t srcSize)
{
#if defined(ZSTD_HEAPMODE) && (ZSTD_HEAPMODE>=1)
size_t regenSize;
ZSTD_DCtx* const dctx = ZSTD_createDCtx_internal(ZSTD_defaultCMem);
RETURN_ERROR_IF(dctx==NULL, memory_allocation, "NULL pointer!");
regenSize = ZSTD_decompressDCtx(dctx, dst, dstCapacity, src, srcSize);
ZSTD_freeDCtx(dctx);
return regenSize;
#else /* stack mode */
ZSTD_DCtx dctx;
ZSTD_initDCtx_internal(&dctx);
return ZSTD_decompressDCtx(&dctx, dst, dstCapacity, src, srcSize);
#endif
}
/*-**************************************
* Advanced Streaming Decompression API
* Bufferless and synchronous
****************************************/
size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx->expected; }
/**
* Similar to ZSTD_nextSrcSizeToDecompress(), but when a block input can be streamed, we
* allow taking a partial block as the input. Currently only raw uncompressed blocks can
* be streamed.
*
* For blocks that can be streamed, this allows us to reduce the latency until we produce
* output, and avoid copying the input.
*
* @param inputSize - The total amount of input that the caller currently has.
*/
static size_t ZSTD_nextSrcSizeToDecompressWithInputSize(ZSTD_DCtx* dctx, size_t inputSize) {
if (!(dctx->stage == ZSTDds_decompressBlock || dctx->stage == ZSTDds_decompressLastBlock))
return dctx->expected;
if (dctx->bType != bt_raw)
return dctx->expected;
return BOUNDED(1, inputSize, dctx->expected);
}
ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx) {
switch(dctx->stage)
{
default: /* should not happen */
assert(0);
ZSTD_FALLTHROUGH;
case ZSTDds_getFrameHeaderSize:
ZSTD_FALLTHROUGH;
case ZSTDds_decodeFrameHeader:
return ZSTDnit_frameHeader;
case ZSTDds_decodeBlockHeader:
return ZSTDnit_blockHeader;
case ZSTDds_decompressBlock:
return ZSTDnit_block;
case ZSTDds_decompressLastBlock:
return ZSTDnit_lastBlock;
case ZSTDds_checkChecksum:
return ZSTDnit_checksum;
case ZSTDds_decodeSkippableHeader:
ZSTD_FALLTHROUGH;
case ZSTDds_skipFrame:
return ZSTDnit_skippableFrame;
}
}
static int ZSTD_isSkipFrame(ZSTD_DCtx* dctx) { return dctx->stage == ZSTDds_skipFrame; }
/** ZSTD_decompressContinue() :
* srcSize : must be the exact nb of bytes expected (see ZSTD_nextSrcSizeToDecompress())
* @return : nb of bytes generated into `dst` (necessarily <= `dstCapacity)
* or an error code, which can be tested using ZSTD_isError() */
size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize)
{
DEBUGLOG(5, "ZSTD_decompressContinue (srcSize:%u)", (unsigned)srcSize);
/* Sanity check */
RETURN_ERROR_IF(srcSize != ZSTD_nextSrcSizeToDecompressWithInputSize(dctx, srcSize), srcSize_wrong, "not allowed");
ZSTD_checkContinuity(dctx, dst, dstCapacity);
dctx->processedCSize += srcSize;
switch (dctx->stage)
{
case ZSTDds_getFrameHeaderSize :
assert(src != NULL);
if (dctx->format == ZSTD_f_zstd1) { /* allows header */
assert(srcSize >= ZSTD_FRAMEIDSIZE); /* to read skippable magic number */
if ((MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { /* skippable frame */
ZSTD_memcpy(dctx->headerBuffer, src, srcSize);
dctx->expected = ZSTD_SKIPPABLEHEADERSIZE - srcSize; /* remaining to load to get full skippable frame header */
dctx->stage = ZSTDds_decodeSkippableHeader;
return 0;
} }
dctx->headerSize = ZSTD_frameHeaderSize_internal(src, srcSize, dctx->format);
if (ZSTD_isError(dctx->headerSize)) return dctx->headerSize;
ZSTD_memcpy(dctx->headerBuffer, src, srcSize);
dctx->expected = dctx->headerSize - srcSize;
dctx->stage = ZSTDds_decodeFrameHeader;
return 0;
case ZSTDds_decodeFrameHeader:
assert(src != NULL);
ZSTD_memcpy(dctx->headerBuffer + (dctx->headerSize - srcSize), src, srcSize);
FORWARD_IF_ERROR(ZSTD_decodeFrameHeader(dctx, dctx->headerBuffer, dctx->headerSize), "");
dctx->expected = ZSTD_blockHeaderSize;
dctx->stage = ZSTDds_decodeBlockHeader;
return 0;
case ZSTDds_decodeBlockHeader:
{ blockProperties_t bp;
size_t const cBlockSize = ZSTD_getcBlockSize(src, ZSTD_blockHeaderSize, &bp);
if (ZSTD_isError(cBlockSize)) return cBlockSize;
RETURN_ERROR_IF(cBlockSize > dctx->fParams.blockSizeMax, corruption_detected, "Block Size Exceeds Maximum");
dctx->expected = cBlockSize;
dctx->bType = bp.blockType;
dctx->rleSize = bp.origSize;
if (cBlockSize) {
dctx->stage = bp.lastBlock ? ZSTDds_decompressLastBlock : ZSTDds_decompressBlock;
return 0;
}
/* empty block */
if (bp.lastBlock) {
if (dctx->fParams.checksumFlag) {
dctx->expected = 4;
dctx->stage = ZSTDds_checkChecksum;
} else {
dctx->expected = 0; /* end of frame */
dctx->stage = ZSTDds_getFrameHeaderSize;
}
} else {
dctx->expected = ZSTD_blockHeaderSize; /* jump to next header */
dctx->stage = ZSTDds_decodeBlockHeader;
}
return 0;
}
case ZSTDds_decompressLastBlock:
case ZSTDds_decompressBlock:
DEBUGLOG(5, "ZSTD_decompressContinue: case ZSTDds_decompressBlock");
{ size_t rSize;
switch(dctx->bType)
{
case bt_compressed:
DEBUGLOG(5, "ZSTD_decompressContinue: case bt_compressed");
assert(dctx->isFrameDecompression == 1);
rSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, is_streaming);
dctx->expected = 0; /* Streaming not supported */
break;
case bt_raw :
assert(srcSize <= dctx->expected);
rSize = ZSTD_copyRawBlock(dst, dstCapacity, src, srcSize);
FORWARD_IF_ERROR(rSize, "ZSTD_copyRawBlock failed");
assert(rSize == srcSize);
dctx->expected -= rSize;
break;
case bt_rle :
rSize = ZSTD_setRleBlock(dst, dstCapacity, *(const BYTE*)src, dctx->rleSize);
dctx->expected = 0; /* Streaming not supported */
break;
case bt_reserved : /* should never happen */
default:
RETURN_ERROR(corruption_detected, "invalid block type");
}
FORWARD_IF_ERROR(rSize, "");
RETURN_ERROR_IF(rSize > dctx->fParams.blockSizeMax, corruption_detected, "Decompressed Block Size Exceeds Maximum");
DEBUGLOG(5, "ZSTD_decompressContinue: decoded size from block : %u", (unsigned)rSize);
dctx->decodedSize += rSize;
if (dctx->validateChecksum) XXH64_update(&dctx->xxhState, dst, rSize);
dctx->previousDstEnd = (char*)dst + rSize;
/* Stay on the same stage until we are finished streaming the block. */
if (dctx->expected > 0) {
return rSize;
}
if (dctx->stage == ZSTDds_decompressLastBlock) { /* end of frame */
DEBUGLOG(4, "ZSTD_decompressContinue: decoded size from frame : %u", (unsigned)dctx->decodedSize);
RETURN_ERROR_IF(
dctx->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
&& dctx->decodedSize != dctx->fParams.frameContentSize,
corruption_detected, "");
if (dctx->fParams.checksumFlag) { /* another round for frame checksum */
dctx->expected = 4;
dctx->stage = ZSTDds_checkChecksum;
} else {
ZSTD_DCtx_trace_end(dctx, dctx->decodedSize, dctx->processedCSize, /* streaming */ 1);
dctx->expected = 0; /* ends here */
dctx->stage = ZSTDds_getFrameHeaderSize;
}
} else {
dctx->stage = ZSTDds_decodeBlockHeader;
dctx->expected = ZSTD_blockHeaderSize;
}
return rSize;
}
case ZSTDds_checkChecksum:
assert(srcSize == 4); /* guaranteed by dctx->expected */
{
if (dctx->validateChecksum) {
U32 const h32 = (U32)XXH64_digest(&dctx->xxhState);
U32 const check32 = MEM_readLE32(src);
DEBUGLOG(4, "ZSTD_decompressContinue: checksum : calculated %08X :: %08X read", (unsigned)h32, (unsigned)check32);
RETURN_ERROR_IF(check32 != h32, checksum_wrong, "");
}
ZSTD_DCtx_trace_end(dctx, dctx->decodedSize, dctx->processedCSize, /* streaming */ 1);
dctx->expected = 0;
dctx->stage = ZSTDds_getFrameHeaderSize;
return 0;
}
case ZSTDds_decodeSkippableHeader:
assert(src != NULL);
assert(srcSize <= ZSTD_SKIPPABLEHEADERSIZE);
assert(dctx->format != ZSTD_f_zstd1_magicless);
ZSTD_memcpy(dctx->headerBuffer + (ZSTD_SKIPPABLEHEADERSIZE - srcSize), src, srcSize); /* complete skippable header */
dctx->expected = MEM_readLE32(dctx->headerBuffer + ZSTD_FRAMEIDSIZE); /* note : dctx->expected can grow seriously large, beyond local buffer size */
dctx->stage = ZSTDds_skipFrame;
return 0;
case ZSTDds_skipFrame:
dctx->expected = 0;
dctx->stage = ZSTDds_getFrameHeaderSize;
return 0;
default:
assert(0); /* impossible */
RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */
}
}
static size_t ZSTD_refDictContent(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
{
dctx->dictEnd = dctx->previousDstEnd;
dctx->virtualStart = (const char*)dict - ((const char*)(dctx->previousDstEnd) - (const char*)(dctx->prefixStart));
dctx->prefixStart = dict;
dctx->previousDstEnd = (const char*)dict + dictSize;
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
dctx->dictContentBeginForFuzzing = dctx->prefixStart;
dctx->dictContentEndForFuzzing = dctx->previousDstEnd;
#endif
return 0;
}
/*! ZSTD_loadDEntropy() :
* dict : must point at beginning of a valid zstd dictionary.
* @return : size of entropy tables read */
size_t
ZSTD_loadDEntropy(ZSTD_entropyDTables_t* entropy,
const void* const dict, size_t const dictSize)
{
const BYTE* dictPtr = (const BYTE*)dict;
const BYTE* const dictEnd = dictPtr + dictSize;
RETURN_ERROR_IF(dictSize <= 8, dictionary_corrupted, "dict is too small");
assert(MEM_readLE32(dict) == ZSTD_MAGIC_DICTIONARY); /* dict must be valid */
dictPtr += 8; /* skip header = magic + dictID */
ZSTD_STATIC_ASSERT(offsetof(ZSTD_entropyDTables_t, OFTable) == offsetof(ZSTD_entropyDTables_t, LLTable) + sizeof(entropy->LLTable));
ZSTD_STATIC_ASSERT(offsetof(ZSTD_entropyDTables_t, MLTable) == offsetof(ZSTD_entropyDTables_t, OFTable) + sizeof(entropy->OFTable));
ZSTD_STATIC_ASSERT(sizeof(entropy->LLTable) + sizeof(entropy->OFTable) + sizeof(entropy->MLTable) >= HUF_DECOMPRESS_WORKSPACE_SIZE);
{ void* const workspace = &entropy->LLTable; /* use fse tables as temporary workspace; implies fse tables are grouped together */
size_t const workspaceSize = sizeof(entropy->LLTable) + sizeof(entropy->OFTable) + sizeof(entropy->MLTable);
#ifdef HUF_FORCE_DECOMPRESS_X1
/* in minimal huffman, we always use X1 variants */
size_t const hSize = HUF_readDTableX1_wksp(entropy->hufTable,
dictPtr, dictEnd - dictPtr,
workspace, workspaceSize, /* flags */ 0);
#else #else
size_t const hSize = HUF_readDTableX2_wksp(entropy->hufTable, (void)dst;
dictPtr, (size_t)(dictEnd - dictPtr), (void)dstCapacity;
workspace, workspaceSize, /* flags */ 0); (void)src;
(void)srcSize;
(void)dict;
(void)dictSize;
return ERROR(prefix_unknown);
#endif #endif
RETURN_ERROR_IF(HUF_isError(hSize), dictionary_corrupted, "");
dictPtr += hSize;
} }
{ short offcodeNCount[MaxOff+1]; size_t ZSTD_rust_legacy_decompress_stream(ZSTD_DCtx* dctx,
unsigned offcodeMaxValue = MaxOff, offcodeLog; ZSTD_outBuffer* output,
size_t const offcodeHeaderSize = FSE_readNCount(offcodeNCount, &offcodeMaxValue, &offcodeLog, dictPtr, (size_t)(dictEnd-dictPtr)); ZSTD_inBuffer* input,
RETURN_ERROR_IF(FSE_isError(offcodeHeaderSize), dictionary_corrupted, ""); const void* dict, size_t dictSize)
RETURN_ERROR_IF(offcodeMaxValue > MaxOff, dictionary_corrupted, "");
RETURN_ERROR_IF(offcodeLog > OffFSELog, dictionary_corrupted, "");
ZSTD_buildFSETable( entropy->OFTable,
offcodeNCount, offcodeMaxValue,
OF_base, OF_bits,
offcodeLog,
entropy->workspace, sizeof(entropy->workspace),
/* bmi2 */0);
dictPtr += offcodeHeaderSize;
}
{ short matchlengthNCount[MaxML+1];
unsigned matchlengthMaxValue = MaxML, matchlengthLog;
size_t const matchlengthHeaderSize = FSE_readNCount(matchlengthNCount, &matchlengthMaxValue, &matchlengthLog, dictPtr, (size_t)(dictEnd-dictPtr));
RETURN_ERROR_IF(FSE_isError(matchlengthHeaderSize), dictionary_corrupted, "");
RETURN_ERROR_IF(matchlengthMaxValue > MaxML, dictionary_corrupted, "");
RETURN_ERROR_IF(matchlengthLog > MLFSELog, dictionary_corrupted, "");
ZSTD_buildFSETable( entropy->MLTable,
matchlengthNCount, matchlengthMaxValue,
ML_base, ML_bits,
matchlengthLog,
entropy->workspace, sizeof(entropy->workspace),
/* bmi2 */ 0);
dictPtr += matchlengthHeaderSize;
}
{ short litlengthNCount[MaxLL+1];
unsigned litlengthMaxValue = MaxLL, litlengthLog;
size_t const litlengthHeaderSize = FSE_readNCount(litlengthNCount, &litlengthMaxValue, &litlengthLog, dictPtr, (size_t)(dictEnd-dictPtr));
RETURN_ERROR_IF(FSE_isError(litlengthHeaderSize), dictionary_corrupted, "");
RETURN_ERROR_IF(litlengthMaxValue > MaxLL, dictionary_corrupted, "");
RETURN_ERROR_IF(litlengthLog > LLFSELog, dictionary_corrupted, "");
ZSTD_buildFSETable( entropy->LLTable,
litlengthNCount, litlengthMaxValue,
LL_base, LL_bits,
litlengthLog,
entropy->workspace, sizeof(entropy->workspace),
/* bmi2 */ 0);
dictPtr += litlengthHeaderSize;
}
RETURN_ERROR_IF(dictPtr+12 > dictEnd, dictionary_corrupted, "");
{ int i;
size_t const dictContentSize = (size_t)(dictEnd - (dictPtr+12));
for (i=0; i<3; i++) {
U32 const rep = MEM_readLE32(dictPtr); dictPtr += 4;
RETURN_ERROR_IF(rep==0 || rep > dictContentSize,
dictionary_corrupted, "");
entropy->rep[i] = rep;
} }
return (size_t)(dictPtr - (const BYTE*)dict);
}
static size_t ZSTD_decompress_insertDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
{ {
if (dictSize < 8) return ZSTD_refDictContent(dctx, dict, dictSize);
{ U32 const magic = MEM_readLE32(dict);
if (magic != ZSTD_MAGIC_DICTIONARY) {
return ZSTD_refDictContent(dctx, dict, dictSize); /* pure content mode */
} }
dctx->dictID = MEM_readLE32((const char*)dict + ZSTD_FRAMEIDSIZE);
/* load entropy tables */
{ size_t const eSize = ZSTD_loadDEntropy(&dctx->entropy, dict, dictSize);
RETURN_ERROR_IF(ZSTD_isError(eSize), dictionary_corrupted, "");
dict = (const char*)dict + eSize;
dictSize -= eSize;
}
dctx->litEntropy = dctx->fseEntropy = 1;
/* reference dictionary content */
return ZSTD_refDictContent(dctx, dict, dictSize);
}
size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx)
{
assert(dctx != NULL);
#if ZSTD_TRACE
dctx->traceCtx = (ZSTD_trace_decompress_begin != NULL) ? ZSTD_trace_decompress_begin(dctx) : 0;
#endif
dctx->expected = ZSTD_startingInputLength(dctx->format); /* dctx->format must be properly set */
dctx->stage = ZSTDds_getFrameHeaderSize;
dctx->processedCSize = 0;
dctx->decodedSize = 0;
dctx->previousDstEnd = NULL;
dctx->prefixStart = NULL;
dctx->virtualStart = NULL;
dctx->dictEnd = NULL;
dctx->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */
dctx->litEntropy = dctx->fseEntropy = 0;
dctx->dictID = 0;
dctx->bType = bt_reserved;
dctx->isFrameDecompression = 1;
ZSTD_STATIC_ASSERT(sizeof(dctx->entropy.rep) == sizeof(repStartValue));
ZSTD_memcpy(dctx->entropy.rep, repStartValue, sizeof(repStartValue)); /* initial repcodes */
dctx->LLTptr = dctx->entropy.LLTable;
dctx->MLTptr = dctx->entropy.MLTable;
dctx->OFTptr = dctx->entropy.OFTable;
dctx->HUFptr = dctx->entropy.hufTable;
return 0;
}
size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
{
FORWARD_IF_ERROR( ZSTD_decompressBegin(dctx) , "");
if (dict && dictSize)
RETURN_ERROR_IF(
ZSTD_isError(ZSTD_decompress_insertDictionary(dctx, dict, dictSize)),
dictionary_corrupted, "");
return 0;
}
/* ====== ZSTD_DDict ====== */
size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict)
{
DEBUGLOG(4, "ZSTD_decompressBegin_usingDDict");
assert(dctx != NULL);
if (ddict) {
const char* const dictStart = (const char*)ZSTD_DDict_dictContent(ddict);
size_t const dictSize = ZSTD_DDict_dictSize(ddict);
const void* const dictEnd = dictStart + dictSize;
dctx->ddictIsCold = (dctx->dictEnd != dictEnd);
DEBUGLOG(4, "DDict is %s",
dctx->ddictIsCold ? "~cold~" : "hot!");
}
FORWARD_IF_ERROR( ZSTD_decompressBegin(dctx) , "");
if (ddict) { /* NULL ddict is equivalent to no dictionary */
ZSTD_copyDDictParameters(dctx, ddict);
}
return 0;
}
/*! ZSTD_getDictID_fromDict() :
* Provides the dictID stored within dictionary.
* if @return == 0, the dictionary is not conformant with Zstandard specification.
* It can still be loaded, but as a content-only dictionary. */
unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize)
{
if (dictSize < 8) return 0;
if (MEM_readLE32(dict) != ZSTD_MAGIC_DICTIONARY) return 0;
return MEM_readLE32((const char*)dict + ZSTD_FRAMEIDSIZE);
}
/*! ZSTD_getDictID_fromFrame() :
* Provides the dictID required to decompress frame stored within `src`.
* If @return == 0, the dictID could not be decoded.
* This could for one of the following reasons :
* - The frame does not require a dictionary (most common case).
* - The frame was built with dictID intentionally removed.
* Needed dictionary is a hidden piece of information.
* Note : this use case also happens when using a non-conformant dictionary.
* - `srcSize` is too small, and as a result, frame header could not be decoded.
* Note : possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`.
* - This is not a Zstandard frame.
* When identifying the exact failure cause, it's possible to use
* ZSTD_getFrameHeader(), which will provide a more precise error code. */
unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize)
{
ZSTD_FrameHeader zfp = { 0, 0, 0, ZSTD_frame, 0, 0, 0, 0, 0 };
size_t const hError = ZSTD_getFrameHeader(&zfp, src, srcSize);
if (ZSTD_isError(hError)) return 0;
return zfp.dictID;
}
/*! ZSTD_decompress_usingDDict() :
* Decompression using a pre-digested Dictionary
* Use dictionary without significant overhead. */
size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx,
void* dst, size_t dstCapacity,
const void* src, size_t srcSize,
const ZSTD_DDict* ddict)
{
/* pass content and size in case legacy frames are encountered */
return ZSTD_decompressMultiFrame(dctx, dst, dstCapacity, src, srcSize,
NULL, 0,
ddict);
}
/*=====================================
* Streaming decompression
*====================================*/
ZSTD_DStream* ZSTD_createDStream(void)
{
DEBUGLOG(3, "ZSTD_createDStream");
return ZSTD_createDCtx_internal(ZSTD_defaultCMem);
}
ZSTD_DStream* ZSTD_initStaticDStream(void *workspace, size_t workspaceSize)
{
return ZSTD_initStaticDCtx(workspace, workspaceSize);
}
ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem)
{
return ZSTD_createDCtx_internal(customMem);
}
size_t ZSTD_freeDStream(ZSTD_DStream* zds)
{
return ZSTD_freeDCtx(zds);
}
/* *** Initialization *** */
size_t ZSTD_DStreamInSize(void) { return ZSTD_BLOCKSIZE_MAX + ZSTD_blockHeaderSize; }
size_t ZSTD_DStreamOutSize(void) { return ZSTD_BLOCKSIZE_MAX; }
size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx,
const void* dict, size_t dictSize,
ZSTD_dictLoadMethod_e dictLoadMethod,
ZSTD_dictContentType_e dictContentType)
{
RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
ZSTD_clearDict(dctx);
if (dict && dictSize != 0) {
dctx->ddictLocal = ZSTD_createDDict_advanced(dict, dictSize, dictLoadMethod, dictContentType, dctx->customMem);
RETURN_ERROR_IF(dctx->ddictLocal == NULL, memory_allocation, "NULL pointer!");
dctx->ddict = dctx->ddictLocal;
dctx->dictUses = ZSTD_use_indefinitely;
}
return 0;
}
size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
{
return ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dictSize, ZSTD_dlm_byRef, ZSTD_dct_auto);
}
size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
{
return ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dictSize, ZSTD_dlm_byCopy, ZSTD_dct_auto);
}
size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType)
{
FORWARD_IF_ERROR(ZSTD_DCtx_loadDictionary_advanced(dctx, prefix, prefixSize, ZSTD_dlm_byRef, dictContentType), "");
dctx->dictUses = ZSTD_use_once;
return 0;
}
size_t ZSTD_DCtx_refPrefix(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize)
{
return ZSTD_DCtx_refPrefix_advanced(dctx, prefix, prefixSize, ZSTD_dct_rawContent);
}
/* ZSTD_initDStream_usingDict() :
* return : expected size, aka ZSTD_startingInputLength().
* this function cannot fail */
size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize)
{
DEBUGLOG(4, "ZSTD_initDStream_usingDict");
FORWARD_IF_ERROR( ZSTD_DCtx_reset(zds, ZSTD_reset_session_only) , "");
FORWARD_IF_ERROR( ZSTD_DCtx_loadDictionary(zds, dict, dictSize) , "");
return ZSTD_startingInputLength(zds->format);
}
/* note : this variant can't fail */
size_t ZSTD_initDStream(ZSTD_DStream* zds)
{
DEBUGLOG(4, "ZSTD_initDStream");
FORWARD_IF_ERROR(ZSTD_DCtx_reset(zds, ZSTD_reset_session_only), "");
FORWARD_IF_ERROR(ZSTD_DCtx_refDDict(zds, NULL), "");
return ZSTD_startingInputLength(zds->format);
}
/* ZSTD_initDStream_usingDDict() :
* ddict will just be referenced, and must outlive decompression session
* this function cannot fail */
size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* dctx, const ZSTD_DDict* ddict)
{
DEBUGLOG(4, "ZSTD_initDStream_usingDDict");
FORWARD_IF_ERROR( ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only) , "");
FORWARD_IF_ERROR( ZSTD_DCtx_refDDict(dctx, ddict) , "");
return ZSTD_startingInputLength(dctx->format);
}
/* ZSTD_resetDStream() :
* return : expected size, aka ZSTD_startingInputLength().
* this function cannot fail */
size_t ZSTD_resetDStream(ZSTD_DStream* dctx)
{
DEBUGLOG(4, "ZSTD_resetDStream");
FORWARD_IF_ERROR(ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only), "");
return ZSTD_startingInputLength(dctx->format);
}
size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict)
{
RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
ZSTD_clearDict(dctx);
if (ddict) {
dctx->ddict = ddict;
dctx->dictUses = ZSTD_use_indefinitely;
if (dctx->refMultipleDDicts == ZSTD_rmd_refMultipleDDicts) {
if (dctx->ddictSet == NULL) {
dctx->ddictSet = ZSTD_createDDictHashSet(dctx->customMem);
if (!dctx->ddictSet) {
RETURN_ERROR(memory_allocation, "Failed to allocate memory for hash set!");
}
}
assert(!dctx->staticSize); /* Impossible: ddictSet cannot have been allocated if static dctx */
FORWARD_IF_ERROR(ZSTD_DDictHashSet_addDDict(dctx->ddictSet, ddict, dctx->customMem), "");
}
}
return 0;
}
/* ZSTD_DCtx_setMaxWindowSize() :
* note : no direct equivalence in ZSTD_DCtx_setParameter,
* since this version sets windowSize, and the other sets windowLog */
size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize)
{
ZSTD_bounds const bounds = ZSTD_dParam_getBounds(ZSTD_d_windowLogMax);
size_t const min = (size_t)1 << bounds.lowerBound;
size_t const max = (size_t)1 << bounds.upperBound;
RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
RETURN_ERROR_IF(maxWindowSize < min, parameter_outOfBound, "");
RETURN_ERROR_IF(maxWindowSize > max, parameter_outOfBound, "");
dctx->maxWindowSize = maxWindowSize;
return 0;
}
size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format)
{
return ZSTD_DCtx_setParameter(dctx, ZSTD_d_format, (int)format);
}
ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam)
{
ZSTD_bounds bounds = { 0, 0, 0 };
switch(dParam) {
case ZSTD_d_windowLogMax:
bounds.lowerBound = ZSTD_WINDOWLOG_ABSOLUTEMIN;
bounds.upperBound = ZSTD_WINDOWLOG_MAX;
return bounds;
case ZSTD_d_format:
bounds.lowerBound = (int)ZSTD_f_zstd1;
bounds.upperBound = (int)ZSTD_f_zstd1_magicless;
ZSTD_STATIC_ASSERT(ZSTD_f_zstd1 < ZSTD_f_zstd1_magicless);
return bounds;
case ZSTD_d_stableOutBuffer:
bounds.lowerBound = (int)ZSTD_bm_buffered;
bounds.upperBound = (int)ZSTD_bm_stable;
return bounds;
case ZSTD_d_forceIgnoreChecksum:
bounds.lowerBound = (int)ZSTD_d_validateChecksum;
bounds.upperBound = (int)ZSTD_d_ignoreChecksum;
return bounds;
case ZSTD_d_refMultipleDDicts:
bounds.lowerBound = (int)ZSTD_rmd_refSingleDDict;
bounds.upperBound = (int)ZSTD_rmd_refMultipleDDicts;
return bounds;
case ZSTD_d_disableHuffmanAssembly:
bounds.lowerBound = 0;
bounds.upperBound = 1;
return bounds;
case ZSTD_d_maxBlockSize:
bounds.lowerBound = ZSTD_BLOCKSIZE_MAX_MIN;
bounds.upperBound = ZSTD_BLOCKSIZE_MAX;
return bounds;
default:;
}
bounds.error = ERROR(parameter_unsupported);
return bounds;
}
/* ZSTD_dParam_withinBounds:
* @return 1 if value is within dParam bounds,
* 0 otherwise */
static int ZSTD_dParam_withinBounds(ZSTD_dParameter dParam, int value)
{
ZSTD_bounds const bounds = ZSTD_dParam_getBounds(dParam);
if (ZSTD_isError(bounds.error)) return 0;
if (value < bounds.lowerBound) return 0;
if (value > bounds.upperBound) return 0;
return 1;
}
#define CHECK_DBOUNDS(p,v) { \
RETURN_ERROR_IF(!ZSTD_dParam_withinBounds(p, v), parameter_outOfBound, ""); \
}
size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value)
{
switch (param) {
case ZSTD_d_windowLogMax:
*value = (int)ZSTD_highbit32((U32)dctx->maxWindowSize);
return 0;
case ZSTD_d_format:
*value = (int)dctx->format;
return 0;
case ZSTD_d_stableOutBuffer:
*value = (int)dctx->outBufferMode;
return 0;
case ZSTD_d_forceIgnoreChecksum:
*value = (int)dctx->forceIgnoreChecksum;
return 0;
case ZSTD_d_refMultipleDDicts:
*value = (int)dctx->refMultipleDDicts;
return 0;
case ZSTD_d_disableHuffmanAssembly:
*value = (int)dctx->disableHufAsm;
return 0;
case ZSTD_d_maxBlockSize:
*value = dctx->maxBlockSizeParam;
return 0;
default:;
}
RETURN_ERROR(parameter_unsupported, "");
}
size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value)
{
RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
switch(dParam) {
case ZSTD_d_windowLogMax:
if (value == 0) value = ZSTD_WINDOWLOG_LIMIT_DEFAULT;
CHECK_DBOUNDS(ZSTD_d_windowLogMax, value);
dctx->maxWindowSize = ((size_t)1) << value;
return 0;
case ZSTD_d_format:
CHECK_DBOUNDS(ZSTD_d_format, value);
dctx->format = (ZSTD_format_e)value;
return 0;
case ZSTD_d_stableOutBuffer:
CHECK_DBOUNDS(ZSTD_d_stableOutBuffer, value);
dctx->outBufferMode = (ZSTD_bufferMode_e)value;
return 0;
case ZSTD_d_forceIgnoreChecksum:
CHECK_DBOUNDS(ZSTD_d_forceIgnoreChecksum, value);
dctx->forceIgnoreChecksum = (ZSTD_forceIgnoreChecksum_e)value;
return 0;
case ZSTD_d_refMultipleDDicts:
CHECK_DBOUNDS(ZSTD_d_refMultipleDDicts, value);
if (dctx->staticSize != 0) {
RETURN_ERROR(parameter_unsupported, "Static dctx does not support multiple DDicts!");
}
dctx->refMultipleDDicts = (ZSTD_refMultipleDDicts_e)value;
return 0;
case ZSTD_d_disableHuffmanAssembly:
CHECK_DBOUNDS(ZSTD_d_disableHuffmanAssembly, value);
dctx->disableHufAsm = value != 0;
return 0;
case ZSTD_d_maxBlockSize:
if (value != 0) CHECK_DBOUNDS(ZSTD_d_maxBlockSize, value);
dctx->maxBlockSizeParam = value;
return 0;
default:;
}
RETURN_ERROR(parameter_unsupported, "");
}
size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset)
{
if ( (reset == ZSTD_reset_session_only)
|| (reset == ZSTD_reset_session_and_parameters) ) {
dctx->streamStage = zdss_init;
dctx->noForwardProgress = 0;
dctx->isFrameDecompression = 1;
}
if ( (reset == ZSTD_reset_parameters)
|| (reset == ZSTD_reset_session_and_parameters) ) {
RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
ZSTD_clearDict(dctx);
ZSTD_DCtx_resetParameters(dctx);
}
return 0;
}
size_t ZSTD_sizeof_DStream(const ZSTD_DStream* dctx)
{
return ZSTD_sizeof_DCtx(dctx);
}
static size_t ZSTD_decodingBufferSize_internal(unsigned long long windowSize, unsigned long long frameContentSize, size_t blockSizeMax)
{
size_t const blockSize = MIN((size_t)MIN(windowSize, ZSTD_BLOCKSIZE_MAX), blockSizeMax);
/* We need blockSize + WILDCOPY_OVERLENGTH worth of buffer so that if a block
* ends at windowSize + WILDCOPY_OVERLENGTH + 1 bytes, we can start writing
* the block at the beginning of the output buffer, and maintain a full window.
*
* We need another blockSize worth of buffer so that we can store split
* literals at the end of the block without overwriting the extDict window.
*/
unsigned long long const neededRBSize = windowSize + (blockSize * 2) + (WILDCOPY_OVERLENGTH * 2);
unsigned long long const neededSize = MIN(frameContentSize, neededRBSize);
size_t const minRBSize = (size_t) neededSize;
RETURN_ERROR_IF((unsigned long long)minRBSize != neededSize,
frameParameter_windowTooLarge, "");
return minRBSize;
}
size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize)
{
return ZSTD_decodingBufferSize_internal(windowSize, frameContentSize, ZSTD_BLOCKSIZE_MAX);
}
size_t ZSTD_estimateDStreamSize(size_t windowSize)
{
size_t const blockSize = MIN(windowSize, ZSTD_BLOCKSIZE_MAX);
size_t const inBuffSize = blockSize; /* no block can be larger */
size_t const outBuffSize = ZSTD_decodingBufferSize_min(windowSize, ZSTD_CONTENTSIZE_UNKNOWN);
return ZSTD_estimateDCtxSize() + inBuffSize + outBuffSize;
}
size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize)
{
U32 const windowSizeMax = 1U << ZSTD_WINDOWLOG_MAX; /* note : should be user-selectable, but requires an additional parameter (or a dctx) */
ZSTD_FrameHeader zfh;
size_t const err = ZSTD_getFrameHeader(&zfh, src, srcSize);
if (ZSTD_isError(err)) return err;
RETURN_ERROR_IF(err>0, srcSize_wrong, "");
RETURN_ERROR_IF(zfh.windowSize > windowSizeMax,
frameParameter_windowTooLarge, "");
return ZSTD_estimateDStreamSize((size_t)zfh.windowSize);
}
/* ***** Decompression ***** */
static int ZSTD_DCtx_isOverflow(ZSTD_DStream* zds, size_t const neededInBuffSize, size_t const neededOutBuffSize)
{
return (zds->inBuffSize + zds->outBuffSize) >= (neededInBuffSize + neededOutBuffSize) * ZSTD_WORKSPACETOOLARGE_FACTOR;
}
static void ZSTD_DCtx_updateOversizedDuration(ZSTD_DStream* zds, size_t const neededInBuffSize, size_t const neededOutBuffSize)
{
if (ZSTD_DCtx_isOverflow(zds, neededInBuffSize, neededOutBuffSize))
zds->oversizedDuration++;
else
zds->oversizedDuration = 0;
}
static int ZSTD_DCtx_isOversizedTooLong(ZSTD_DStream* zds)
{
return zds->oversizedDuration >= ZSTD_WORKSPACETOOLARGE_MAXDURATION;
}
/* Checks that the output buffer hasn't changed if ZSTD_obm_stable is used. */
static size_t ZSTD_checkOutBuffer(ZSTD_DStream const* zds, ZSTD_outBuffer const* output)
{
ZSTD_outBuffer const expect = zds->expectedOutBuffer;
/* No requirement when ZSTD_obm_stable is not enabled. */
if (zds->outBufferMode != ZSTD_bm_stable)
return 0;
/* Any buffer is allowed in zdss_init, this must be the same for every other call until
* the context is reset.
*/
if (zds->streamStage == zdss_init)
return 0;
/* The buffer must match our expectation exactly. */
if (expect.dst == output->dst && expect.pos == output->pos && expect.size == output->size)
return 0;
RETURN_ERROR(dstBuffer_wrong, "ZSTD_d_stableOutBuffer enabled but output differs!");
}
/* Calls ZSTD_decompressContinue() with the right parameters for ZSTD_decompressStream()
* and updates the stage and the output buffer state. This call is extracted so it can be
* used both when reading directly from the ZSTD_inBuffer, and in buffered input mode.
* NOTE: You must break after calling this function since the streamStage is modified.
*/
static size_t ZSTD_decompressContinueStream(
ZSTD_DStream* zds, char** op, char* oend,
void const* src, size_t srcSize) {
int const isSkipFrame = ZSTD_isSkipFrame(zds);
if (zds->outBufferMode == ZSTD_bm_buffered) {
size_t const dstSize = isSkipFrame ? 0 : zds->outBuffSize - zds->outStart;
size_t const decodedSize = ZSTD_decompressContinue(zds,
zds->outBuff + zds->outStart, dstSize, src, srcSize);
FORWARD_IF_ERROR(decodedSize, "");
if (!decodedSize && !isSkipFrame) {
zds->streamStage = zdss_read;
} else {
zds->outEnd = zds->outStart + decodedSize;
zds->streamStage = zdss_flush;
}
} else {
/* Write directly into the output buffer */
size_t const dstSize = isSkipFrame ? 0 : (size_t)(oend - *op);
size_t const decodedSize = ZSTD_decompressContinue(zds, *op, dstSize, src, srcSize);
FORWARD_IF_ERROR(decodedSize, "");
*op += decodedSize;
/* Flushing is not needed. */
zds->streamStage = zdss_read;
assert(*op <= oend);
assert(zds->outBufferMode == ZSTD_bm_stable);
}
return 0;
}
size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input)
{
const char* const src = (const char*)input->src;
const char* const istart = input->pos != 0 ? src + input->pos : src;
const char* const iend = input->size != 0 ? src + input->size : src;
const char* ip = istart;
char* const dst = (char*)output->dst;
char* const ostart = output->pos != 0 ? dst + output->pos : dst;
char* const oend = output->size != 0 ? dst + output->size : dst;
char* op = ostart;
U32 someMoreWork = 1;
DEBUGLOG(5, "ZSTD_decompressStream");
assert(zds != NULL);
RETURN_ERROR_IF(
input->pos > input->size,
srcSize_wrong,
"forbidden. in: pos: %u vs size: %u",
(U32)input->pos, (U32)input->size);
RETURN_ERROR_IF(
output->pos > output->size,
dstSize_tooSmall,
"forbidden. out: pos: %u vs size: %u",
(U32)output->pos, (U32)output->size);
DEBUGLOG(5, "input size : %u", (U32)(input->size - input->pos));
FORWARD_IF_ERROR(ZSTD_checkOutBuffer(zds, output), "");
while (someMoreWork) {
switch(zds->streamStage)
{
case zdss_init :
DEBUGLOG(5, "stage zdss_init => transparent reset ");
zds->streamStage = zdss_loadHeader;
zds->lhSize = zds->inPos = zds->outStart = zds->outEnd = 0;
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
zds->legacyVersion = 0; size_t hint;
#endif if (dctx->legacyVersion) {
zds->hostageByte = 0; hint = ZSTD_decompressLegacyStream(dctx->legacyContext,
zds->expectedOutBuffer = *output; dctx->legacyVersion, output, input);
ZSTD_FALLTHROUGH; if (hint == 0) dctx->streamStage = zdss_init;
case zdss_loadHeader :
DEBUGLOG(5, "stage zdss_loadHeader (srcSize : %u)", (U32)(iend - ip));
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
if (zds->legacyVersion) {
RETURN_ERROR_IF(zds->staticSize, memory_allocation,
"legacy support is incompatible with static dctx");
{ size_t const hint = ZSTD_decompressLegacyStream(zds->legacyContext, zds->legacyVersion, output, input);
if (hint==0) zds->streamStage = zdss_init;
return hint; return hint;
} }
#endif
{ size_t const hSize = ZSTD_getFrameHeader_advanced(&zds->fParams, zds->headerBuffer, zds->lhSize, zds->format);
if (zds->refMultipleDDicts && zds->ddictSet) {
ZSTD_DCtx_selectFrameDDict(zds);
} }
if (ZSTD_isError(hSize)) { {
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) const char* const istart = input->pos != 0
U32 const legacyVersion = ZSTD_isLegacy(istart, iend-istart); ? (const char*)input->src + input->pos
if (legacyVersion) { : (const char*)input->src;
ZSTD_DDict const* const ddict = ZSTD_getDDict(zds); size_t const inputSize = input->size - input->pos;
const void* const dict = ddict ? ZSTD_DDict_dictContent(ddict) : NULL; U32 const legacyVersion = ZSTD_isLegacy(istart, inputSize);
size_t const dictSize = ddict ? ZSTD_DDict_dictSize(ddict) : 0; if (!legacyVersion) return ERROR(prefix_unknown);
DEBUGLOG(5, "ZSTD_decompressStream: detected legacy version v0.%u", legacyVersion); if (dctx->staticSize) return ERROR(memory_allocation);
RETURN_ERROR_IF(zds->staticSize, memory_allocation, FORWARD_IF_ERROR(ZSTD_initLegacyStream(&dctx->legacyContext,
"legacy support is incompatible with static dctx"); dctx->previousLegacyVersion,
FORWARD_IF_ERROR(ZSTD_initLegacyStream(&zds->legacyContext, legacyVersion, dict, dictSize), "");
zds->previousLegacyVersion, legacyVersion, dctx->legacyVersion = dctx->previousLegacyVersion = legacyVersion;
dict, dictSize), ""); hint = ZSTD_decompressLegacyStream(dctx->legacyContext, legacyVersion,
zds->legacyVersion = zds->previousLegacyVersion = legacyVersion; output, input);
{ size_t const hint = ZSTD_decompressLegacyStream(zds->legacyContext, legacyVersion, output, input); if (hint == 0) dctx->streamStage = zdss_init;
if (hint==0) zds->streamStage = zdss_init; /* or stay in stage zdss_loadHeader */
return hint; return hint;
} } }
#else
(void)dctx;
(void)output;
(void)input;
(void)dict;
(void)dictSize;
return ERROR(prefix_unknown);
#endif #endif
return hSize; /* error */
}
if (hSize != 0) { /* need more input */
size_t const toLoad = hSize - zds->lhSize; /* if hSize!=0, hSize > zds->lhSize */
size_t const remainingInput = (size_t)(iend-ip);
assert(iend >= ip);
if (toLoad > remainingInput) { /* not enough input to load full header */
if (remainingInput > 0) {
ZSTD_memcpy(zds->headerBuffer + zds->lhSize, ip, remainingInput);
zds->lhSize += remainingInput;
}
input->pos = input->size;
/* check first few bytes */
FORWARD_IF_ERROR(
ZSTD_getFrameHeader_advanced(&zds->fParams, zds->headerBuffer, zds->lhSize, zds->format),
"First few bytes detected incorrect" );
/* return hint input size */
return (MAX((size_t)ZSTD_FRAMEHEADERSIZE_MIN(zds->format), hSize) - zds->lhSize) + ZSTD_blockHeaderSize; /* remaining header bytes + next block header */
}
assert(ip != NULL);
ZSTD_memcpy(zds->headerBuffer + zds->lhSize, ip, toLoad); zds->lhSize = hSize; ip += toLoad;
break;
} }
/* check for single-pass mode opportunity */
if (zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
&& zds->fParams.frameType != ZSTD_skippableFrame
&& (U64)(size_t)(oend-op) >= zds->fParams.frameContentSize) {
size_t const cSize = ZSTD_findFrameCompressedSize_advanced(istart, (size_t)(iend-istart), zds->format);
if (cSize <= (size_t)(iend-istart)) {
/* shortcut : using single-pass mode */
size_t const decompressedSize = ZSTD_decompress_usingDDict(zds, op, (size_t)(oend-op), istart, cSize, ZSTD_getDDict(zds));
if (ZSTD_isError(decompressedSize)) return decompressedSize;
DEBUGLOG(4, "shortcut to single-pass ZSTD_decompress_usingDDict()");
assert(istart != NULL);
ip = istart + cSize;
op = op ? op + decompressedSize : op; /* can occur if frameContentSize = 0 (empty frame) */
zds->expected = 0;
zds->streamStage = zdss_init;
someMoreWork = 0;
break;
} }
/* Check output buffer is large enough for ZSTD_odm_stable. */
if (zds->outBufferMode == ZSTD_bm_stable
&& zds->fParams.frameType != ZSTD_skippableFrame
&& zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
&& (U64)(size_t)(oend-op) < zds->fParams.frameContentSize) {
RETURN_ERROR(dstSize_tooSmall, "ZSTD_obm_stable passed but ZSTD_outBuffer is too small");
} }
/* Consume header (see ZSTDds_decodeFrameHeader) */ void ZSTD_rust_legacy_free_stream(ZSTD_DCtx* dctx)
DEBUGLOG(4, "Consume header");
FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDDict(zds, ZSTD_getDDict(zds)), "");
if (zds->format == ZSTD_f_zstd1
&& (MEM_readLE32(zds->headerBuffer) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { /* skippable frame */
zds->expected = MEM_readLE32(zds->headerBuffer + ZSTD_FRAMEIDSIZE);
zds->stage = ZSTDds_skipFrame;
} else {
FORWARD_IF_ERROR(ZSTD_decodeFrameHeader(zds, zds->headerBuffer, zds->lhSize), "");
zds->expected = ZSTD_blockHeaderSize;
zds->stage = ZSTDds_decodeBlockHeader;
}
/* control buffer memory usage */
DEBUGLOG(4, "Control max memory usage (%u KB <= max %u KB)",
(U32)(zds->fParams.windowSize >>10),
(U32)(zds->maxWindowSize >> 10) );
zds->fParams.windowSize = MAX(zds->fParams.windowSize, 1U << ZSTD_WINDOWLOG_ABSOLUTEMIN);
RETURN_ERROR_IF(zds->fParams.windowSize > zds->maxWindowSize,
frameParameter_windowTooLarge, "");
if (zds->maxBlockSizeParam != 0)
zds->fParams.blockSizeMax = MIN(zds->fParams.blockSizeMax, (unsigned)zds->maxBlockSizeParam);
/* Adapt buffer sizes to frame header instructions */
{ size_t const neededInBuffSize = MAX(zds->fParams.blockSizeMax, 4 /* frame checksum */);
size_t const neededOutBuffSize = zds->outBufferMode == ZSTD_bm_buffered
? ZSTD_decodingBufferSize_internal(zds->fParams.windowSize, zds->fParams.frameContentSize, zds->fParams.blockSizeMax)
: 0;
ZSTD_DCtx_updateOversizedDuration(zds, neededInBuffSize, neededOutBuffSize);
{ int const tooSmall = (zds->inBuffSize < neededInBuffSize) || (zds->outBuffSize < neededOutBuffSize);
int const tooLarge = ZSTD_DCtx_isOversizedTooLong(zds);
if (tooSmall || tooLarge) {
size_t const bufferSize = neededInBuffSize + neededOutBuffSize;
DEBUGLOG(4, "inBuff : from %u to %u",
(U32)zds->inBuffSize, (U32)neededInBuffSize);
DEBUGLOG(4, "outBuff : from %u to %u",
(U32)zds->outBuffSize, (U32)neededOutBuffSize);
if (zds->staticSize) { /* static DCtx */
DEBUGLOG(4, "staticSize : %u", (U32)zds->staticSize);
assert(zds->staticSize >= sizeof(ZSTD_DCtx)); /* controlled at init */
RETURN_ERROR_IF(
bufferSize > zds->staticSize - sizeof(ZSTD_DCtx),
memory_allocation, "");
} else {
ZSTD_customFree(zds->inBuff, zds->customMem);
zds->inBuffSize = 0;
zds->outBuffSize = 0;
zds->inBuff = (char*)ZSTD_customMalloc(bufferSize, zds->customMem);
RETURN_ERROR_IF(zds->inBuff == NULL, memory_allocation, "");
}
zds->inBuffSize = neededInBuffSize;
zds->outBuff = zds->inBuff + zds->inBuffSize;
zds->outBuffSize = neededOutBuffSize;
} } }
zds->streamStage = zdss_read;
ZSTD_FALLTHROUGH;
case zdss_read:
DEBUGLOG(5, "stage zdss_read");
{ size_t const neededInSize = ZSTD_nextSrcSizeToDecompressWithInputSize(zds, (size_t)(iend - ip));
DEBUGLOG(5, "neededInSize = %u", (U32)neededInSize);
if (neededInSize==0) { /* end of frame */
zds->streamStage = zdss_init;
someMoreWork = 0;
break;
}
if ((size_t)(iend-ip) >= neededInSize) { /* decode directly from src */
FORWARD_IF_ERROR(ZSTD_decompressContinueStream(zds, &op, oend, ip, neededInSize), "");
assert(ip != NULL);
ip += neededInSize;
/* Function modifies the stage so we must break */
break;
} }
if (ip==iend) { someMoreWork = 0; break; } /* no more input */
zds->streamStage = zdss_load;
ZSTD_FALLTHROUGH;
case zdss_load:
{ size_t const neededInSize = ZSTD_nextSrcSizeToDecompress(zds);
size_t const toLoad = neededInSize - zds->inPos;
int const isSkipFrame = ZSTD_isSkipFrame(zds);
size_t loadedSize;
/* At this point we shouldn't be decompressing a block that we can stream. */
assert(neededInSize == ZSTD_nextSrcSizeToDecompressWithInputSize(zds, (size_t)(iend - ip)));
if (isSkipFrame) {
loadedSize = MIN(toLoad, (size_t)(iend-ip));
} else {
RETURN_ERROR_IF(toLoad > zds->inBuffSize - zds->inPos,
corruption_detected,
"should never happen");
loadedSize = ZSTD_limitCopy(zds->inBuff + zds->inPos, toLoad, ip, (size_t)(iend-ip));
}
if (loadedSize != 0) {
/* ip may be NULL */
ip += loadedSize;
zds->inPos += loadedSize;
}
if (loadedSize < toLoad) { someMoreWork = 0; break; } /* not enough input, wait for more */
/* decode loaded input */
zds->inPos = 0; /* input is consumed */
FORWARD_IF_ERROR(ZSTD_decompressContinueStream(zds, &op, oend, zds->inBuff, neededInSize), "");
/* Function modifies the stage so we must break */
break;
}
case zdss_flush:
{ {
size_t const toFlushSize = zds->outEnd - zds->outStart; #if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
size_t const flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize); if (dctx->legacyContext) {
ZSTD_freeLegacyStreamContext(dctx->legacyContext,
op = op ? op + flushedSize : op; dctx->previousLegacyVersion);
dctx->legacyContext = NULL;
zds->outStart += flushedSize;
if (flushedSize == toFlushSize) { /* flush completed */
zds->streamStage = zdss_read;
if ( (zds->outBuffSize < zds->fParams.frameContentSize)
&& (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) {
DEBUGLOG(5, "restart filling outBuff from beginning (left:%i, needed:%u)",
(int)(zds->outBuffSize - zds->outStart),
(U32)zds->fParams.blockSizeMax);
zds->outStart = zds->outEnd = 0;
}
break;
} }
/* cannot complete flush */
someMoreWork = 0;
break;
default:
assert(0); /* impossible */
RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */
} }
/* result */
input->pos = (size_t)(ip - (const char*)(input->src));
output->pos = (size_t)(op - (char*)(output->dst));
/* Update the expected output buffer for ZSTD_obm_stable. */
zds->expectedOutBuffer = *output;
if ((ip==istart) && (op==ostart)) { /* no forward progress */
zds->noForwardProgress ++;
if (zds->noForwardProgress >= ZSTD_NO_FORWARD_PROGRESS_MAX) {
RETURN_ERROR_IF(op==oend, noForwardProgress_destFull, "");
RETURN_ERROR_IF(ip==iend, noForwardProgress_inputEmpty, "");
assert(0);
}
} else {
zds->noForwardProgress = 0;
}
{ size_t nextSrcSizeHint = ZSTD_nextSrcSizeToDecompress(zds);
if (!nextSrcSizeHint) { /* frame fully decoded */
if (zds->outEnd == zds->outStart) { /* output fully flushed */
if (zds->hostageByte) {
if (input->pos >= input->size) {
/* can't release hostage (not present) */
zds->streamStage = zdss_read;
return 1;
}
input->pos++; /* release hostage */
} /* zds->hostageByte */
return 0;
} /* zds->outEnd == zds->outStart */
if (!zds->hostageByte) { /* output not fully flushed; keep last byte as hostage; will be released when all output is flushed */
input->pos--; /* note : pos > 0, otherwise, impossible to finish reading last block */
zds->hostageByte=1;
}
return 1;
} /* nextSrcSizeHint==0 */
nextSrcSizeHint += ZSTD_blockHeaderSize * (ZSTD_nextInputType(zds) == ZSTDnit_block); /* preload header of next block */
assert(zds->inPos <= nextSrcSizeHint);
nextSrcSizeHint -= zds->inPos; /* part already loaded*/
return nextSrcSizeHint;
}
}
size_t ZSTD_decompressStream_simpleArgs (
ZSTD_DCtx* dctx,
void* dst, size_t dstCapacity, size_t* dstPos,
const void* src, size_t srcSize, size_t* srcPos)
{
ZSTD_outBuffer output;
ZSTD_inBuffer input;
output.dst = dst;
output.size = dstCapacity;
output.pos = *dstPos;
input.src = src;
input.size = srcSize;
input.pos = *srcPos;
{ size_t const cErr = ZSTD_decompressStream(dctx, &output, &input);
*dstPos = output.pos;
*srcPos = input.pos;
return cErr;
} }
#else
(void)dctx;
#endif
} }
+76 -8
View File
@@ -27,8 +27,12 @@ include $(LIBZSTD_MK_DIR)/libzstd.mk
CARGO ?= cargo CARGO ?= cargo
RUST_DIR := ../rust RUST_DIR := ../rust
RUST_MANIFEST := $(RUST_DIR)/Cargo.toml RUST_MANIFEST := $(RUST_DIR)/Cargo.toml
RUST_CLI_DIR := $(RUST_DIR)/cli
RUST_CLI_MANIFEST := $(RUST_CLI_DIR)/Cargo.toml
RUST_SOURCES := $(RUST_MANIFEST) $(RUST_DIR)/Cargo.lock \ RUST_SOURCES := $(RUST_MANIFEST) $(RUST_DIR)/Cargo.lock \
$(shell find $(RUST_DIR)/src -type f -name '*.rs' -print) $(shell find $(RUST_DIR)/src -type f -name '*.rs' -print)
RUST_CLI_SOURCES := $(RUST_CLI_MANIFEST) $(RUST_CLI_DIR)/Cargo.lock \
$(RUST_CLI_DIR)/src/lib.rs $(RUST_DIR)/src/zstd_cli.rs
# Keep Rust's HUF implementation in lockstep with libzstd.mk's C selection. # Keep Rust's HUF implementation in lockstep with libzstd.mk's C selection.
# Forced modes may arrive as libzstd.mk variables or as direct -D flags in # Forced modes may arrive as libzstd.mk variables or as direct -D flags in
@@ -66,7 +70,8 @@ RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a
RUST_TARGET_32 ?= i686-unknown-linux-gnu RUST_TARGET_32 ?= i686-unknown-linux-gnu
RUST_STATICLIB_32 := $(RUST_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_rs.a RUST_STATICLIB_32 := $(RUST_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_rs.a
RUST_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \ RUST_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
--target-dir $(RUST_TARGET_DIR) --target-dir $(RUST_TARGET_DIR) --no-default-features
RUST_CARGO_FLAGS += --features compression,decompression
ifneq ($(RUST_HUF_FEATURE),) ifneq ($(RUST_HUF_FEATURE),)
RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE) RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE)
endif endif
@@ -77,6 +82,63 @@ $(RUST_STATICLIB): $(RUST_SOURCES)
$(RUST_STATICLIB_32): $(RUST_SOURCES) $(RUST_STATICLIB_32): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS) --target $(RUST_TARGET_32) $(CARGO) build $(RUST_CARGO_FLAGS) --target $(RUST_TARGET_32)
RUST_CLI_BUILD_CONFIG := cli-c1-d1-$(RUST_BUILD_CONFIG)
RUST_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_CLI_BUILD_CONFIG)
RUST_CLI_STATICLIB := $(RUST_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_CLI_STATICLIB_32 := $(RUST_CLI_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a
RUST_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
--target-dir $(RUST_CLI_TARGET_DIR) \
--no-default-features --features compression,decompression
$(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_CLI_CARGO_FLAGS)
$(RUST_CLI_STATICLIB_32): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_CLI_CARGO_FLAGS) --target $(RUST_TARGET_32)
RUST_DECOMPRESS_BUILD_CONFIG := lib-c0-d1-$(RUST_BUILD_CONFIG)
RUST_DECOMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_BUILD_CONFIG)
RUST_DECOMPRESS_STATICLIB := $(RUST_DECOMPRESS_TARGET_DIR)/release/libzstd_rs.a
RUST_DECOMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
--target-dir $(RUST_DECOMPRESS_TARGET_DIR) \
--no-default-features --features decompression
ifneq ($(RUST_HUF_FEATURE),)
RUST_DECOMPRESS_CARGO_FLAGS += --features $(RUST_HUF_FEATURE)
endif
$(RUST_DECOMPRESS_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_DECOMPRESS_CARGO_FLAGS)
RUST_DECOMPRESS_CLI_BUILD_CONFIG := cli-c0-d1-$(RUST_BUILD_CONFIG)
RUST_DECOMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_CLI_BUILD_CONFIG)
RUST_DECOMPRESS_CLI_STATICLIB := $(RUST_DECOMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
--target-dir $(RUST_DECOMPRESS_CLI_TARGET_DIR) \
--no-default-features --features decompression
$(RUST_DECOMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_DECOMPRESS_CLI_CARGO_FLAGS)
RUST_COMPRESS_BUILD_CONFIG := lib-c1-d0-$(RUST_BUILD_CONFIG)
RUST_COMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_BUILD_CONFIG)
RUST_COMPRESS_STATICLIB := $(RUST_COMPRESS_TARGET_DIR)/release/libzstd_rs.a
RUST_COMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
--target-dir $(RUST_COMPRESS_TARGET_DIR) \
--no-default-features --features compression
$(RUST_COMPRESS_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_COMPRESS_CARGO_FLAGS)
RUST_COMPRESS_CLI_BUILD_CONFIG := cli-c1-d0-$(RUST_BUILD_CONFIG)
RUST_COMPRESS_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_COMPRESS_CLI_BUILD_CONFIG)
RUST_COMPRESS_CLI_STATICLIB := $(RUST_COMPRESS_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
RUST_COMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
--target-dir $(RUST_COMPRESS_CLI_TARGET_DIR) \
--no-default-features --features compression
$(RUST_COMPRESS_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(CARGO) build $(RUST_COMPRESS_CLI_CARGO_FLAGS)
# Most program objects use a configuration-hashed directory, but the compact # Most program objects use a configuration-hashed directory, but the compact
# direct-source variants below do not. Give their C outputs an independent # direct-source variants below do not. Give their C outputs an independent
# mode stamp so they cannot outlive a different Rust HUF archive selection. # mode stamp so they cannot outlive a different Rust HUF archive selection.
@@ -192,6 +254,9 @@ ifeq ($(BACKTRACE), 1)
endif endif
endif endif
RUST_LIBRARY_LINK ?= $(RUST_STATICLIB)
RUST_CLI_LINK ?= $(RUST_CLI_STATICLIB)
SET_CACHE_DIRECTORY = \ SET_CACHE_DIRECTORY = \
+$(MAKE) --no-print-directory $@ \ +$(MAKE) --no-print-directory $@ \
BUILD_DIR=obj/$(HASH_DIR) \ BUILD_DIR=obj/$(HASH_DIR) \
@@ -199,6 +264,8 @@ SET_CACHE_DIRECTORY = \
CFLAGS="$(CFLAGS)" \ CFLAGS="$(CFLAGS)" \
LDFLAGS="$(LDFLAGS)" \ LDFLAGS="$(LDFLAGS)" \
LDLIBS="$(LDLIBS)" \ LDLIBS="$(LDLIBS)" \
RUST_LIBRARY_LINK="$(RUST_LIBRARY_LINK)" \
RUST_CLI_LINK="$(RUST_CLI_LINK)" \
ZSTD_ALL_SRC="$(ZSTD_ALL_SRC)" ZSTD_ALL_SRC="$(ZSTD_ALL_SRC)"
@@ -227,7 +294,7 @@ else
# BUILD_DIR is defined # BUILD_DIR is defined
ZSTD_OBJ := $(addprefix $(BUILD_DIR)/, $(ZSTD_ALL_OBJ)) ZSTD_OBJ := $(addprefix $(BUILD_DIR)/, $(ZSTD_ALL_OBJ))
$(BUILD_DIR)/zstd : $(ZSTD_OBJ) $(RUST_STATICLIB) $(BUILD_DIR)/zstd : $(ZSTD_OBJ) $(RUST_LIBRARY_LINK) $(RUST_CLI_LINK)
@echo "$(THREAD_MSG)" @echo "$(THREAD_MSG)"
@echo "$(ZLIB_MSG)" @echo "$(ZLIB_MSG)"
@echo "$(LZMA_MSG)" @echo "$(LZMA_MSG)"
@@ -267,14 +334,14 @@ zstd32 : CPPFLAGS += -DZSTD_LEGACY_SUPPORT=$(ZSTD_LEGACY_SUPPORT)
ifneq (,$(filter Windows%,$(OS))) ifneq (,$(filter Windows%,$(OS)))
zstd32 : $(RES32_FILE) zstd32 : $(RES32_FILE)
endif endif
zstd32 : $(ZSTDLIB_FULL_SRC) $(ZSTD_CLI_SRC) $(RUST_STATICLIB_32) zstd32 : $(ZSTDLIB_FULL_SRC) $(ZSTD_CLI_SRC) $(RUST_STATICLIB_32) $(RUST_CLI_STATICLIB_32)
$(CC) -m32 $(FLAGS) $^ -o $@$(EXT) $(CC) -m32 $(FLAGS) $^ -o $@$(EXT)
## zstd-nolegacy: same scope as zstd, with removed support of legacy formats ## zstd-nolegacy: same scope as zstd, with removed support of legacy formats
CLEAN += zstd-nolegacy CLEAN += zstd-nolegacy
zstd-nolegacy : LDFLAGS += $(THREAD_LD) $(ZLIBLD) $(LZMALD) $(LZ4LD) $(DEBUGFLAGS_LD) zstd-nolegacy : LDFLAGS += $(THREAD_LD) $(ZLIBLD) $(LZMALD) $(LZ4LD) $(DEBUGFLAGS_LD)
zstd-nolegacy : CPPFLAGS += -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 zstd-nolegacy : CPPFLAGS += -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0
zstd-nolegacy : $(ZSTDLIB_CORE_SRC) $(ZDICT_SRC) $(ZSTD_CLI_OBJ) $(RUST_STATICLIB) zstd-nolegacy : $(ZSTDLIB_CORE_SRC) $(ZDICT_SRC) $(ZSTD_CLI_OBJ) $(RUST_STATICLIB) $(RUST_CLI_STATICLIB)
$(CC) $(FLAGS) $^ -o $@$(EXT) $(LDFLAGS) $(CC) $(FLAGS) $^ -o $@$(EXT) $(LDFLAGS)
.PHONY: zstd-nomt .PHONY: zstd-nomt
@@ -300,6 +367,7 @@ zstd-noxz : zstd
zstd-dll : LDFLAGS+= -L$(LIB_BINDIR) zstd-dll : LDFLAGS+= -L$(LIB_BINDIR)
zstd-dll : LDLIBS += -lzstd zstd-dll : LDLIBS += -lzstd
zstd-dll : ZSTDLIB_LOCAL_SRC = xxhash.c pool.c threading.c zstd-dll : ZSTDLIB_LOCAL_SRC = xxhash.c pool.c threading.c
zstd-dll : RUST_LIBRARY_LINK =
zstd-dll : zstd zstd-dll : zstd
@@ -331,20 +399,20 @@ CLEAN += zstd-small zstd-frugal
# requested HUF decoder mode from the C sources while the Rust archive sees it. # requested HUF decoder mode from the C sources while the Rust archive sees it.
ZSTD_SMALL_HUF_CFLAGS := $(filter -DHUF_FORCE_DECOMPRESS_X1% -DHUF_FORCE_DECOMPRESS_X2%,$(CFLAGS)) ZSTD_SMALL_HUF_CFLAGS := $(filter -DHUF_FORCE_DECOMPRESS_X1% -DHUF_FORCE_DECOMPRESS_X2%,$(CFLAGS))
zstd-small: CFLAGS = -Os -Wl,-s $(ZSTD_SMALL_HUF_CFLAGS) zstd-small: CFLAGS = -Os -Wl,-s $(ZSTD_SMALL_HUF_CFLAGS)
zstd-frugal zstd-small: $(ZSTDLIB_CORE_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_STATICLIB) zstd-frugal zstd-small: $(ZSTDLIB_CORE_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_STATICLIB) $(RUST_CLI_STATICLIB)
$(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT) $(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT)
CLEAN += zstd-decompress CLEAN += zstd-decompress
zstd-decompress: $(ZSTDLIB_COMMON_SRC) $(ZSTDLIB_DECOMPRESS_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_STATICLIB) zstd-decompress: $(ZSTDLIB_COMMON_SRC) $(ZSTDLIB_DECOMPRESS_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_DECOMPRESS_STATICLIB) $(RUST_DECOMPRESS_CLI_STATICLIB)
$(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NOCOMPRESS -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT) $(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NOCOMPRESS -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT)
CLEAN += zstd-compress CLEAN += zstd-compress
zstd-compress: $(ZSTDLIB_COMMON_SRC) $(ZSTDLIB_COMPRESS_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_STATICLIB) zstd-compress: $(ZSTDLIB_COMMON_SRC) $(ZSTDLIB_COMPRESS_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_COMPRESS_STATICLIB) $(RUST_COMPRESS_CLI_STATICLIB)
$(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NODECOMPRESS -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT) $(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NODECOMPRESS -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT)
## zstd-dictBuilder: executable supporting dictionary creation and compression (only) ## zstd-dictBuilder: executable supporting dictionary creation and compression (only)
CLEAN += zstd-dictBuilder CLEAN += zstd-dictBuilder
zstd-dictBuilder: $(ZSTDLIB_COMMON_SRC) $(ZSTDLIB_COMPRESS_SRC) $(ZDICT_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c dibio.c $(RUST_STATICLIB) zstd-dictBuilder: $(ZSTDLIB_COMMON_SRC) $(ZSTDLIB_COMPRESS_SRC) $(ZDICT_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c dibio.c $(RUST_COMPRESS_STATICLIB) $(RUST_COMPRESS_CLI_STATICLIB)
$(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODECOMPRESS -DZSTD_NOTRACE $^ -o $@$(EXT) $(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODECOMPRESS -DZSTD_NOTRACE $^ -o $@$(EXT)
RUST_DIRECT_LINK_TARGETS := zstd32 zstd-nolegacy zstd-small zstd-frugal \ RUST_DIRECT_LINK_TARGETS := zstd32 zstd-nolegacy zstd-small zstd-frugal \
+5
View File
@@ -501,6 +501,11 @@ void FIO_setNbFilesTotal(FIO_ctx_t* const fCtx, int value)
fCtx->nbFilesTotal = value; fCtx->nbFilesTotal = value;
} }
void FIO_setHasStdinInput(FIO_ctx_t* const fCtx, int value)
{
fCtx->hasStdinInput = value != 0;
}
void FIO_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames) { void FIO_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames) {
size_t i = 0; size_t i = 0;
for ( ; i < filenames->tableSize; ++i) { for ( ; i < filenames->tableSize; ++i) {
+1
View File
@@ -105,6 +105,7 @@ void FIO_setMMapDict(FIO_prefs_t* const prefs, ZSTD_ParamSwitch_e value);
/* FIO_ctx_t functions */ /* FIO_ctx_t functions */
void FIO_setNbFilesTotal(FIO_ctx_t* const fCtx, int value); void FIO_setNbFilesTotal(FIO_ctx_t* const fCtx, int value);
void FIO_setHasStdinInput(FIO_ctx_t* const fCtx, int value);
void FIO_setHasStdoutOutput(FIO_ctx_t* const fCtx, int value); void FIO_setHasStdoutOutput(FIO_ctx_t* const fCtx, int value);
void FIO_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames); void FIO_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames);
+8 -1640
View File
@@ -8,1651 +8,19 @@
* You may select, at your option, one of the above-listed licenses. * You may select, at your option, one of the above-listed licenses.
*/ */
/*-************************************ /* The CLI parser and control flow live in rust/src/zstd_cli.rs. Keep this
* Dependencies * translation unit as the stable C entry point used by program launchers. */
**************************************/ #include "../lib/zstd.h"
#include "platform.h" /* PLATFORM_POSIX_VERSION */
#include "util.h" /* UTIL_HAS_CREATEFILELIST, UTIL_createFileList, UTIL_isConsole */
#include <stdlib.h> /* getenv */
#include <string.h> /* strcmp, strlen */
#include <stdio.h> /* fprintf(), stdin, stdout, stderr */
#include <assert.h> /* assert */
#include "fileio.h" /* stdinmark, stdoutmark, ZSTD_EXTENSION */ int ZSTD_rust_cli_main(int argCount, const char* const argv[]);
#ifndef ZSTD_NOBENCH const char* ZSTD_rust_cli_expected_version(void);
# include "benchzstd.h" /* BMK_benchFilesAdvanced */
#endif
#ifndef ZSTD_NODICT
# include "dibio.h" /* ZDICT_cover_params_t, DiB_trainFromFiles() */
#endif
#ifndef ZSTD_NOTRACE
# include "zstdcli_trace.h"
#endif
#include "../lib/zstd.h" /* ZSTD_VERSION_STRING, ZSTD_minCLevel, ZSTD_maxCLevel */
#include "fileio_asyncio.h"
#include "fileio_common.h"
/*-************************************ const char* ZSTD_rust_cli_expected_version(void)
* Tuning parameters
**************************************/
#ifndef ZSTDCLI_CLEVEL_DEFAULT
# define ZSTDCLI_CLEVEL_DEFAULT 3
#endif
#ifndef ZSTDCLI_CLEVEL_MAX
# define ZSTDCLI_CLEVEL_MAX 19 /* without using --ultra */
#endif
#ifndef ZSTDCLI_NBTHREADS_DEFAULT
#define ZSTDCLI_NBTHREADS_DEFAULT MAX(1, MIN(4, UTIL_countLogicalCores() / 4))
#endif
/*-************************************
* Constants
**************************************/
#define COMPRESSOR_NAME "Zstandard CLI"
#ifndef ZSTD_VERSION
# define ZSTD_VERSION "v" ZSTD_VERSION_STRING
#endif
#define AUTHOR "Yann Collet"
#define WELCOME_MESSAGE "*** %s (%i-bit) %s, by %s ***\n", COMPRESSOR_NAME, (int)(sizeof(size_t)*8), ZSTD_VERSION, AUTHOR
#define ZSTD_ZSTDMT "zstdmt"
#define ZSTD_UNZSTD "unzstd"
#define ZSTD_CAT "zstdcat"
#define ZSTD_ZCAT "zcat"
#define ZSTD_GZ "gzip"
#define ZSTD_GUNZIP "gunzip"
#define ZSTD_GZCAT "gzcat"
#define ZSTD_LZMA "lzma"
#define ZSTD_UNLZMA "unlzma"
#define ZSTD_XZ "xz"
#define ZSTD_UNXZ "unxz"
#define ZSTD_LZ4 "lz4"
#define ZSTD_UNLZ4 "unlz4"
#define KB *(1 <<10)
#define MB *(1 <<20)
#define GB *(1U<<30)
#define DISPLAY_LEVEL_DEFAULT 2
static const char* g_defaultDictName = "dictionary";
static const unsigned g_defaultMaxDictSize = 110 KB;
static const int g_defaultDictCLevel = 3;
static const unsigned g_defaultSelectivityLevel = 9;
static const unsigned g_defaultMaxWindowLog = 27;
#define OVERLAP_LOG_DEFAULT 9999
#define LDM_PARAM_DEFAULT 9999 /* Default for parameters where 0 is valid */
static U32 g_overlapLog = OVERLAP_LOG_DEFAULT;
static U32 g_ldmHashLog = 0;
static U32 g_ldmMinMatch = 0;
static U32 g_ldmHashRateLog = LDM_PARAM_DEFAULT;
static U32 g_ldmBucketSizeLog = LDM_PARAM_DEFAULT;
#define DEFAULT_ACCEL 1
typedef enum { cover, fastCover, legacy } dictType;
/*-************************************
* Display Macros
**************************************/
#undef DISPLAYLEVEL
#define DISPLAYLEVEL(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } }
static int g_displayLevel = DISPLAY_LEVEL_DEFAULT; /* 0 : no display, 1: errors, 2 : + result + interaction + warnings, 3 : + progression, 4 : + information */
/*-************************************
* Check Version (when CLI linked to dynamic library)
**************************************/
/* Due to usage of experimental symbols and capabilities by the CLI,
* the CLI must be linked against a dynamic library of same version */
static void checkLibVersion(void)
{ {
if (strcmp(ZSTD_VERSION_STRING, ZSTD_versionString())) { return ZSTD_VERSION_STRING;
DISPLAYLEVEL(1, "Error : incorrect library version (expecting : %s ; actual : %s ) \n",
ZSTD_VERSION_STRING, ZSTD_versionString());
DISPLAYLEVEL(1, "Please update library to version %s, or use stand-alone zstd binary \n",
ZSTD_VERSION_STRING);
exit(1);
} }
}
/*! exeNameMatch() :
@return : a non-zero value if exeName matches test, excluding the extension
*/
static int exeNameMatch(const char* exeName, const char* test)
{
return !strncmp(exeName, test, strlen(test)) &&
(exeName[strlen(test)] == '\0' || exeName[strlen(test)] == '.');
}
/*-************************************
* Command Line
**************************************/
/* print help either in `stderr` or `stdout` depending on originating request
* error (badUsage) => stderr
* help (usageAdvanced) => stdout
*/
static void usage(FILE* f, const char* programName)
{
DISPLAY_F(f, "Compress or decompress the INPUT file(s); reads from STDIN if INPUT is `-` or not provided.\n\n");
DISPLAY_F(f, "Usage: %s [OPTIONS...] [INPUT... | -] [-o OUTPUT]\n\n", programName);
DISPLAY_F(f, "Options:\n");
DISPLAY_F(f, " -o OUTPUT Write output to a single file, OUTPUT.\n");
DISPLAY_F(f, " -k, --keep Preserve INPUT file(s). [Default] \n");
DISPLAY_F(f, " --rm Remove INPUT file(s) after successful (de)compression.\n");
#ifdef ZSTD_GZCOMPRESS
if (exeNameMatch(programName, ZSTD_GZ)) { /* behave like gzip */
DISPLAY_F(f, " -n, --no-name Do not store original filename when compressing.\n\n");
}
#endif
DISPLAY_F(f, "\n");
#ifndef ZSTD_NOCOMPRESS
DISPLAY_F(f, " -# Desired compression level, where `#` is a number between 1 and %d;\n", ZSTDCLI_CLEVEL_MAX);
DISPLAY_F(f, " lower numbers provide faster compression, higher numbers yield\n");
DISPLAY_F(f, " better compression ratios. [Default: %d]\n\n", ZSTDCLI_CLEVEL_DEFAULT);
#endif
#ifndef ZSTD_NODECOMPRESS
DISPLAY_F(f, " -d, --decompress Perform decompression.\n");
#endif
DISPLAY_F(f, " -D DICT Use DICT as the dictionary for compression or decompression.\n\n");
DISPLAY_F(f, " -f, --force Disable input and output checks. Allows overwriting existing files,\n");
DISPLAY_F(f, " receiving input from the console, printing output to STDOUT, and\n");
DISPLAY_F(f, " operating on links, block devices, etc. Unrecognized formats will be\n");
DISPLAY_F(f, " passed-through through as-is.\n\n");
DISPLAY_F(f, " -h Display short usage and exit.\n");
DISPLAY_F(f, " -H, --help Display full help and exit.\n");
DISPLAY_F(f, " -V, --version Display the program version and exit.\n");
DISPLAY_F(f, "\n");
}
static void usageAdvanced(const char* programName)
{
DISPLAYOUT(WELCOME_MESSAGE);
DISPLAYOUT("\n");
usage(stdout, programName);
DISPLAYOUT("Advanced options:\n");
DISPLAYOUT(" -c, --stdout Write to STDOUT (even if it is a console) and keep the INPUT file(s).\n\n");
DISPLAYOUT(" -v, --verbose Enable verbose output; pass multiple times to increase verbosity.\n");
DISPLAYOUT(" -q, --quiet Suppress warnings; pass twice to suppress errors.\n");
#ifndef ZSTD_NOTRACE
DISPLAYOUT(" --trace LOG Log tracing information to LOG.\n");
#endif
DISPLAYOUT("\n");
DISPLAYOUT(" --[no-]progress Forcibly show/hide the progress counter. NOTE: Any (de)compressed\n");
DISPLAYOUT(" output to terminal will mix with progress counter text.\n\n");
#ifdef UTIL_HAS_CREATEFILELIST
DISPLAYOUT(" -r Operate recursively on directories.\n");
DISPLAYOUT(" --filelist LIST Read a list of files to operate on from LIST.\n");
DISPLAYOUT(" --output-dir-flat DIR Store processed files in DIR.\n");
#endif
#ifdef UTIL_HAS_MIRRORFILELIST
DISPLAYOUT(" --output-dir-mirror DIR Store processed files in DIR, respecting original directory structure.\n");
#endif
if (AIO_supported())
DISPLAYOUT(" --[no-]asyncio Use asynchronous IO. [Default: Enabled]\n");
DISPLAYOUT("\n");
#ifndef ZSTD_NOCOMPRESS
DISPLAYOUT(" --[no-]check Add XXH64 integrity checksums during compression. [Default: Add, Validate]\n");
#ifndef ZSTD_NODECOMPRESS
DISPLAYOUT(" If `-d` is present, ignore/validate checksums during decompression.\n");
#endif
#else
#ifdef ZSTD_NOCOMPRESS
DISPLAYOUT(" --[no-]check Ignore/validate checksums during decompression. [Default: Validate]");
#endif
#endif /* ZSTD_NOCOMPRESS */
DISPLAYOUT("\n");
DISPLAYOUT(" -- Treat remaining arguments after `--` as files.\n");
#ifndef ZSTD_NOCOMPRESS
DISPLAYOUT("\n");
DISPLAYOUT("Advanced compression options:\n");
DISPLAYOUT(" --ultra Enable levels beyond %i, up to %i; requires more memory.\n", ZSTDCLI_CLEVEL_MAX, ZSTD_maxCLevel());
DISPLAYOUT(" --fast[=#] Use to very fast compression levels. [Default: %u]\n", 1);
#ifdef ZSTD_GZCOMPRESS
if (exeNameMatch(programName, ZSTD_GZ)) { /* behave like gzip */
DISPLAYOUT(" --best Compatibility alias for `-9`.\n");
}
#endif
DISPLAYOUT(" --adapt Dynamically adapt compression level to I/O conditions.\n");
DISPLAYOUT(" --long[=#] Enable long distance matching with window log #. [Default: %u]\n", g_defaultMaxWindowLog);
DISPLAYOUT(" --patch-from=REF Use REF as the reference point for Zstandard's diff engine. \n\n");
# ifdef ZSTD_MULTITHREAD
DISPLAYOUT(" -T# Spawn # compression threads. [Default: 1; pass 0 for core count.]\n");
DISPLAYOUT(" --single-thread Share a single thread for I/O and compression (slightly different than `-T1`).\n");
DISPLAYOUT(" --auto-threads={physical|logical}\n");
DISPLAYOUT(" Use physical/logical cores when using `-T0`. [Default: Physical]\n\n");
DISPLAYOUT(" -B# Set job size to #. [Default: 0 (automatic)]\n");
DISPLAYOUT(" --rsyncable Compress using a rsync-friendly method (`-B` sets block size). \n");
DISPLAYOUT("\n");
# endif
DISPLAYOUT(" --exclude-compressed Only compress files that are not already compressed.\n\n");
DISPLAYOUT(" --stream-size=# Specify size of streaming input from STDIN.\n");
DISPLAYOUT(" --size-hint=# Optimize compression parameters for streaming input of approximately size #.\n");
DISPLAYOUT(" --target-compressed-block-size=#\n");
DISPLAYOUT(" Generate compressed blocks of approximately # size.\n\n");
DISPLAYOUT(" --no-dictID Don't write `dictID` into the header (dictionary compression only).\n");
DISPLAYOUT(" --[no-]compress-literals Force (un)compressed literals.\n");
DISPLAYOUT(" --[no-]row-match-finder Explicitly enable/disable the fast, row-based matchfinder for\n");
DISPLAYOUT(" the 'greedy', 'lazy', and 'lazy2' strategies.\n");
DISPLAYOUT("\n");
DISPLAYOUT(" --format=zstd Compress files to the `.zst` format. [Default]\n");
DISPLAYOUT(" --[no-]mmap-dict Memory-map dictionary file rather than mallocing and loading all at once\n");
#ifdef ZSTD_GZCOMPRESS
DISPLAYOUT(" --format=gzip Compress files to the `.gz` format.\n");
#endif
#ifdef ZSTD_LZMACOMPRESS
DISPLAYOUT(" --format=xz Compress files to the `.xz` format.\n");
DISPLAYOUT(" --format=lzma Compress files to the `.lzma` format.\n");
#endif
#ifdef ZSTD_LZ4COMPRESS
DISPLAYOUT( " --format=lz4 Compress files to the `.lz4` format.\n");
#endif
#endif /* !ZSTD_NOCOMPRESS */
#ifndef ZSTD_NODECOMPRESS
DISPLAYOUT("\n");
DISPLAYOUT("Advanced decompression options:\n");
DISPLAYOUT(" -l Print information about Zstandard-compressed files.\n");
DISPLAYOUT(" --test Test compressed file integrity.\n");
DISPLAYOUT(" -M# Set the memory usage limit to # megabytes.\n");
# if ZSTD_SPARSE_DEFAULT
DISPLAYOUT(" --[no-]sparse Enable sparse mode. [Default: Enabled for files, disabled for STDOUT.]\n");
# else
DISPLAYOUT(" --[no-]sparse Enable sparse mode. [Default: Disabled]\n");
# endif
{
char const* passThroughDefault = "Disabled";
if (exeNameMatch(programName, ZSTD_CAT) ||
exeNameMatch(programName, ZSTD_ZCAT) ||
exeNameMatch(programName, ZSTD_GZCAT)) {
passThroughDefault = "Enabled";
}
DISPLAYOUT(" --[no-]pass-through Pass through uncompressed files as-is. [Default: %s]\n", passThroughDefault);
}
#endif /* ZSTD_NODECOMPRESS */
#ifndef ZSTD_NODICT
DISPLAYOUT("\n");
DISPLAYOUT("Dictionary builder:\n");
DISPLAYOUT(" --train Create a dictionary from a training set of files.\n\n");
DISPLAYOUT(" --train-cover[=k=#,d=#,steps=#,split=#,shrink[=#]]\n");
DISPLAYOUT(" Use the cover algorithm (with optional arguments).\n");
DISPLAYOUT(" --train-fastcover[=k=#,d=#,f=#,steps=#,split=#,accel=#,shrink[=#]]\n");
DISPLAYOUT(" Use the fast cover algorithm (with optional arguments).\n\n");
DISPLAYOUT(" --train-legacy[=s=#] Use the legacy algorithm with selectivity #. [Default: %u]\n", g_defaultSelectivityLevel);
DISPLAYOUT(" -o NAME Use NAME as dictionary name. [Default: %s]\n", g_defaultDictName);
DISPLAYOUT(" --maxdict=# Limit dictionary to specified size #. [Default: %u]\n", g_defaultMaxDictSize);
DISPLAYOUT(" --dictID=# Force dictionary ID to #. [Default: Random]\n");
#endif
#ifndef ZSTD_NOBENCH
DISPLAYOUT("\n");
DISPLAYOUT("Benchmark options:\n");
DISPLAYOUT(" -b# Perform benchmarking with compression level #. [Default: %d]\n", ZSTDCLI_CLEVEL_DEFAULT);
DISPLAYOUT(" -e# Test all compression levels up to #; starting level is `-b#`. [Default: 1]\n");
DISPLAYOUT(" -i# Set the minimum evaluation to time # seconds. [Default: 3]\n");
DISPLAYOUT(" -B# Cut file into independent chunks of size #. [Default: No chunking]\n");
DISPLAYOUT(" -S Output one benchmark result per input file. [Default: Consolidated result]\n");
DISPLAYOUT(" -D dictionary Benchmark using dictionary \n");
DISPLAYOUT(" --priority=rt Set process priority to real-time.\n");
#endif
}
static void badUsage(const char* programName, const char* parameter)
{
DISPLAYLEVEL(1, "Incorrect parameter: %s \n", parameter);
if (g_displayLevel >= 2) usage(stderr, programName);
}
static void waitEnter(void)
{
int unused;
DISPLAY("Press enter to continue... \n");
unused = getchar();
(void)unused;
}
static const char* lastNameFromPath(const char* path)
{
const char* name = path;
if (strrchr(name, '/')) name = strrchr(name, '/') + 1;
if (strrchr(name, '\\')) name = strrchr(name, '\\') + 1; /* windows */
return name;
}
static void errorOut(const char* msg)
{
DISPLAYLEVEL(1, "%s \n", msg); exit(1);
}
/*! readU32FromCharChecked() :
* @return 0 if success, and store the result in *value.
* allows and interprets K, KB, KiB, M, MB and MiB suffix.
* Will also modify `*stringPtr`, advancing it to position where it stopped reading.
* @return 1 if an overflow error occurs */
static int readU32FromCharChecked(const char** stringPtr, unsigned* value)
{
unsigned result = 0;
while ((**stringPtr >='0') && (**stringPtr <='9')) {
unsigned const max = ((unsigned)(-1)) / 10;
unsigned last = result;
if (result > max) return 1; /* overflow error */
result *= 10;
result += (unsigned)(**stringPtr - '0');
if (result < last) return 1; /* overflow error */
(*stringPtr)++ ;
}
if ((**stringPtr=='K') || (**stringPtr=='M')) {
unsigned const maxK = ((unsigned)(-1)) >> 10;
if (result > maxK) return 1; /* overflow error */
result <<= 10;
if (**stringPtr=='M') {
if (result > maxK) return 1; /* overflow error */
result <<= 10;
}
(*stringPtr)++; /* skip `K` or `M` */
if (**stringPtr=='i') (*stringPtr)++;
if (**stringPtr=='B') (*stringPtr)++;
}
*value = result;
return 0;
}
/*! readU32FromChar() :
* @return : unsigned integer value read from input in `char` format.
* allows and interprets K, KB, KiB, M, MB and MiB suffix.
* Will also modify `*stringPtr`, advancing it to position where it stopped reading.
* Note : function will exit() program if digit sequence overflows */
static unsigned readU32FromChar(const char** stringPtr) {
static const char errorMsg[] = "error: numeric value overflows 32-bit unsigned int";
unsigned result;
if (readU32FromCharChecked(stringPtr, &result)) { errorOut(errorMsg); }
return result;
}
/*! readIntFromChar() :
* @return : signed integer value read from input in `char` format.
* allows and interprets K, KB, KiB, M, MB and MiB suffix.
* Will also modify `*stringPtr`, advancing it to position where it stopped reading.
* Note : function will exit() program if digit sequence overflows */
static int readIntFromChar(const char** stringPtr) {
static const char errorMsg[] = "error: numeric value overflows 32-bit int";
int sign = 1;
unsigned result;
if (**stringPtr=='-') {
(*stringPtr)++;
sign = -1;
}
if (readU32FromCharChecked(stringPtr, &result)) { errorOut(errorMsg); }
return (int) result * sign;
}
/*! readSizeTFromCharChecked() :
* @return 0 if success, and store the result in *value.
* allows and interprets K, KB, KiB, M, MB and MiB suffix.
* Will also modify `*stringPtr`, advancing it to position where it stopped reading.
* @return 1 if an overflow error occurs */
static int readSizeTFromCharChecked(const char** stringPtr, size_t* value)
{
size_t result = 0;
while ((**stringPtr >='0') && (**stringPtr <='9')) {
size_t const max = ((size_t)(-1)) / 10;
size_t last = result;
if (result > max) return 1; /* overflow error */
result *= 10;
result += (size_t)(**stringPtr - '0');
if (result < last) return 1; /* overflow error */
(*stringPtr)++ ;
}
if ((**stringPtr=='K') || (**stringPtr=='M')) {
size_t const maxK = ((size_t)(-1)) >> 10;
if (result > maxK) return 1; /* overflow error */
result <<= 10;
if (**stringPtr=='M') {
if (result > maxK) return 1; /* overflow error */
result <<= 10;
}
(*stringPtr)++; /* skip `K` or `M` */
if (**stringPtr=='i') (*stringPtr)++;
if (**stringPtr=='B') (*stringPtr)++;
}
*value = result;
return 0;
}
/*! readSizeTFromChar() :
* @return : size_t value read from input in `char` format.
* allows and interprets K, KB, KiB, M, MB and MiB suffix.
* Will also modify `*stringPtr`, advancing it to position where it stopped reading.
* Note : function will exit() program if digit sequence overflows */
static size_t readSizeTFromChar(const char** stringPtr) {
static const char errorMsg[] = "error: numeric value overflows size_t";
size_t result;
if (readSizeTFromCharChecked(stringPtr, &result)) { errorOut(errorMsg); }
return result;
}
/** longCommandWArg() :
* check if *stringPtr is the same as longCommand.
* If yes, @return 1 and advances *stringPtr to the position which immediately follows longCommand.
* @return 0 and doesn't modify *stringPtr otherwise.
*/
static int longCommandWArg(const char** stringPtr, const char* longCommand)
{
size_t const comSize = strlen(longCommand);
int const result = !strncmp(*stringPtr, longCommand, comSize);
if (result) *stringPtr += comSize;
return result;
}
#ifndef ZSTD_NODICT
static const unsigned kDefaultRegression = 1;
/**
* parseCoverParameters() :
* reads cover parameters from *stringPtr (e.g. "--train-cover=k=48,d=8,steps=32") into *params
* @return 1 means that cover parameters were correct
* @return 0 in case of malformed parameters
*/
static unsigned parseCoverParameters(const char* stringPtr, ZDICT_cover_params_t* params)
{
memset(params, 0, sizeof(*params));
for (; ;) {
if (longCommandWArg(&stringPtr, "k=")) { params->k = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "d=")) { params->d = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "steps=")) { params->steps = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "split=")) {
unsigned splitPercentage = readU32FromChar(&stringPtr);
params->splitPoint = (double)splitPercentage / 100.0;
if (stringPtr[0]==',') { stringPtr++; continue; } else break;
}
if (longCommandWArg(&stringPtr, "shrink")) {
params->shrinkDictMaxRegression = kDefaultRegression;
params->shrinkDict = 1;
if (stringPtr[0]=='=') {
stringPtr++;
params->shrinkDictMaxRegression = readU32FromChar(&stringPtr);
}
if (stringPtr[0]==',') {
stringPtr++;
continue;
}
else break;
}
return 0;
}
if (stringPtr[0] != 0) return 0;
DISPLAYLEVEL(4, "cover: k=%u\nd=%u\nsteps=%u\nsplit=%u\nshrink%u\n", params->k, params->d, params->steps, (unsigned)(params->splitPoint * 100), params->shrinkDictMaxRegression);
return 1;
}
/**
* parseFastCoverParameters() :
* reads fastcover parameters from *stringPtr (e.g. "--train-fastcover=k=48,d=8,f=20,steps=32,accel=2") into *params
* @return 1 means that fastcover parameters were correct
* @return 0 in case of malformed parameters
*/
static unsigned parseFastCoverParameters(const char* stringPtr, ZDICT_fastCover_params_t* params)
{
memset(params, 0, sizeof(*params));
for (; ;) {
if (longCommandWArg(&stringPtr, "k=")) { params->k = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "d=")) { params->d = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "f=")) { params->f = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "steps=")) { params->steps = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "accel=")) { params->accel = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "split=")) {
unsigned splitPercentage = readU32FromChar(&stringPtr);
params->splitPoint = (double)splitPercentage / 100.0;
if (stringPtr[0]==',') { stringPtr++; continue; } else break;
}
if (longCommandWArg(&stringPtr, "shrink")) {
params->shrinkDictMaxRegression = kDefaultRegression;
params->shrinkDict = 1;
if (stringPtr[0]=='=') {
stringPtr++;
params->shrinkDictMaxRegression = readU32FromChar(&stringPtr);
}
if (stringPtr[0]==',') {
stringPtr++;
continue;
}
else break;
}
return 0;
}
if (stringPtr[0] != 0) return 0;
DISPLAYLEVEL(4, "cover: k=%u\nd=%u\nf=%u\nsteps=%u\nsplit=%u\naccel=%u\nshrink=%u\n", params->k, params->d, params->f, params->steps, (unsigned)(params->splitPoint * 100), params->accel, params->shrinkDictMaxRegression);
return 1;
}
/**
* parseLegacyParameters() :
* reads legacy dictionary builder parameters from *stringPtr (e.g. "--train-legacy=selectivity=8") into *selectivity
* @return 1 means that legacy dictionary builder parameters were correct
* @return 0 in case of malformed parameters
*/
static unsigned parseLegacyParameters(const char* stringPtr, unsigned* selectivity)
{
if (!longCommandWArg(&stringPtr, "s=") && !longCommandWArg(&stringPtr, "selectivity=")) { return 0; }
*selectivity = readU32FromChar(&stringPtr);
if (stringPtr[0] != 0) return 0;
DISPLAYLEVEL(4, "legacy: selectivity=%u\n", *selectivity);
return 1;
}
static ZDICT_cover_params_t defaultCoverParams(void)
{
ZDICT_cover_params_t params;
memset(&params, 0, sizeof(params));
params.d = 8;
params.steps = 4;
params.splitPoint = 1.0;
params.shrinkDict = 0;
params.shrinkDictMaxRegression = kDefaultRegression;
return params;
}
static ZDICT_fastCover_params_t defaultFastCoverParams(void)
{
ZDICT_fastCover_params_t params;
memset(&params, 0, sizeof(params));
params.d = 8;
params.f = 20;
params.steps = 4;
params.splitPoint = 0.75; /* different from default splitPoint of cover */
params.accel = DEFAULT_ACCEL;
params.shrinkDict = 0;
params.shrinkDictMaxRegression = kDefaultRegression;
return params;
}
#endif
/** parseAdaptParameters() :
* reads adapt parameters from *stringPtr (e.g. "--adapt=min=1,max=19) and store them into adaptMinPtr and adaptMaxPtr.
* Both adaptMinPtr and adaptMaxPtr must be already allocated and correctly initialized.
* There is no guarantee that any of these values will be updated.
* @return 1 means that parsing was successful,
* @return 0 in case of malformed parameters
*/
static unsigned parseAdaptParameters(const char* stringPtr, int* adaptMinPtr, int* adaptMaxPtr)
{
for ( ; ;) {
if (longCommandWArg(&stringPtr, "min=")) { *adaptMinPtr = readIntFromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "max=")) { *adaptMaxPtr = readIntFromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
DISPLAYLEVEL(4, "invalid compression parameter \n");
return 0;
}
if (stringPtr[0] != 0) return 0; /* check the end of string */
if (*adaptMinPtr > *adaptMaxPtr) {
DISPLAYLEVEL(4, "incoherent adaptation limits \n");
return 0;
}
return 1;
}
/** parseCompressionParameters() :
* reads compression parameters from *stringPtr (e.g. "--zstd=wlog=23,clog=23,hlog=22,slog=6,mml=3,tlen=48,strat=6") into *params
* @return 1 means that compression parameters were correct
* @return 0 in case of malformed parameters
*/
static unsigned parseCompressionParameters(const char* stringPtr, ZSTD_compressionParameters* params)
{
for ( ; ;) {
if (longCommandWArg(&stringPtr, "windowLog=") || longCommandWArg(&stringPtr, "wlog=")) { params->windowLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "chainLog=") || longCommandWArg(&stringPtr, "clog=")) { params->chainLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "hashLog=") || longCommandWArg(&stringPtr, "hlog=")) { params->hashLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "searchLog=") || longCommandWArg(&stringPtr, "slog=")) { params->searchLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "minMatch=") || longCommandWArg(&stringPtr, "mml=")) { params->minMatch = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "targetLength=") || longCommandWArg(&stringPtr, "tlen=")) { params->targetLength = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "strategy=") || longCommandWArg(&stringPtr, "strat=")) { params->strategy = (ZSTD_strategy)(readU32FromChar(&stringPtr)); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "overlapLog=") || longCommandWArg(&stringPtr, "ovlog=")) { g_overlapLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "ldmHashLog=") || longCommandWArg(&stringPtr, "lhlog=")) { g_ldmHashLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "ldmMinMatch=") || longCommandWArg(&stringPtr, "lmml=")) { g_ldmMinMatch = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "ldmBucketSizeLog=") || longCommandWArg(&stringPtr, "lblog=")) { g_ldmBucketSizeLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
if (longCommandWArg(&stringPtr, "ldmHashRateLog=") || longCommandWArg(&stringPtr, "lhrlog=")) { g_ldmHashRateLog = readU32FromChar(&stringPtr); if (stringPtr[0]==',') { stringPtr++; continue; } else break; }
DISPLAYLEVEL(4, "invalid compression parameter \n");
return 0;
}
if (stringPtr[0] != 0) return 0; /* check the end of string */
return 1;
}
static void setMaxCompression(ZSTD_compressionParameters* params)
{
params->windowLog = ZSTD_WINDOWLOG_MAX;
params->chainLog = ZSTD_CHAINLOG_MAX;
params->hashLog = ZSTD_HASHLOG_MAX;
params->searchLog = ZSTD_SEARCHLOG_MAX;
params->minMatch = ZSTD_MINMATCH_MIN;
params->targetLength = ZSTD_TARGETLENGTH_MAX;
params->strategy = ZSTD_STRATEGY_MAX;
g_overlapLog = ZSTD_OVERLAPLOG_MAX;
g_ldmHashLog = ZSTD_LDM_HASHLOG_MAX;
g_ldmHashRateLog = 0; /* automatically derived */
g_ldmMinMatch = 16; /* heuristic */
g_ldmBucketSizeLog = ZSTD_LDM_BUCKETSIZELOG_MAX;
}
static void printVersion(void)
{
if (g_displayLevel < DISPLAY_LEVEL_DEFAULT) {
DISPLAYOUT("%s\n", ZSTD_VERSION_STRING);
return;
}
DISPLAYOUT(WELCOME_MESSAGE);
if (g_displayLevel >= 3) {
/* format support */
DISPLAYOUT("*** supports: zstd");
#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>0) && (ZSTD_LEGACY_SUPPORT<8)
DISPLAYOUT(", zstd legacy v0.%d+", ZSTD_LEGACY_SUPPORT);
#endif
#ifdef ZSTD_GZCOMPRESS
DISPLAYOUT(", gzip");
#endif
#ifdef ZSTD_LZ4COMPRESS
DISPLAYOUT(", lz4");
#endif
#ifdef ZSTD_LZMACOMPRESS
DISPLAYOUT(", lzma, xz ");
#endif
DISPLAYOUT("\n");
if (g_displayLevel >= 4) {
/* library versions */
DISPLAYOUT("zlib version %s\n", FIO_zlibVersion());
DISPLAYOUT("lz4 version %s\n", FIO_lz4Version());
DISPLAYOUT("lzma version %s\n", FIO_lzmaVersion());
/* posix support */
#ifdef _POSIX_C_SOURCE
DISPLAYOUT("_POSIX_C_SOURCE defined: %ldL\n", (long) _POSIX_C_SOURCE);
#endif
#ifdef _POSIX_VERSION
DISPLAYOUT("_POSIX_VERSION defined: %ldL \n", (long) _POSIX_VERSION);
#endif
#ifdef PLATFORM_POSIX_VERSION
DISPLAYOUT("PLATFORM_POSIX_VERSION defined: %ldL\n", (long) PLATFORM_POSIX_VERSION);
#endif
} }
}
#define ZSTD_NB_STRATEGIES 9
static const char* ZSTD_strategyMap[ZSTD_NB_STRATEGIES + 1] = { "", "ZSTD_fast",
"ZSTD_dfast", "ZSTD_greedy", "ZSTD_lazy", "ZSTD_lazy2", "ZSTD_btlazy2",
"ZSTD_btopt", "ZSTD_btultra", "ZSTD_btultra2"};
#ifndef ZSTD_NOCOMPRESS
static void printDefaultCParams(const char* filename, const char* dictFileName, int cLevel) {
unsigned long long fileSize = UTIL_getFileSize(filename);
const size_t dictSize = dictFileName != NULL ? (size_t)UTIL_getFileSize(dictFileName) : 0;
const ZSTD_compressionParameters cParams = ZSTD_getCParams(cLevel, fileSize, dictSize);
if (fileSize != UTIL_FILESIZE_UNKNOWN) DISPLAY("%s (%llu bytes)\n", filename, fileSize);
else DISPLAY("%s (src size unknown)\n", filename);
DISPLAY(" - windowLog : %u\n", cParams.windowLog);
DISPLAY(" - chainLog : %u\n", cParams.chainLog);
DISPLAY(" - hashLog : %u\n", cParams.hashLog);
DISPLAY(" - searchLog : %u\n", cParams.searchLog);
DISPLAY(" - minMatch : %u\n", cParams.minMatch);
DISPLAY(" - targetLength : %u\n", cParams.targetLength);
assert(cParams.strategy < ZSTD_NB_STRATEGIES + 1);
DISPLAY(" - strategy : %s (%u)\n", ZSTD_strategyMap[(int)cParams.strategy], (unsigned)cParams.strategy);
}
static void printActualCParams(const char* filename, const char* dictFileName, int cLevel, const ZSTD_compressionParameters* cParams) {
unsigned long long fileSize = UTIL_getFileSize(filename);
const size_t dictSize = dictFileName != NULL ? (size_t)UTIL_getFileSize(dictFileName) : 0;
ZSTD_compressionParameters actualCParams = ZSTD_getCParams(cLevel, fileSize, dictSize);
assert(g_displayLevel >= 4);
actualCParams.windowLog = cParams->windowLog == 0 ? actualCParams.windowLog : cParams->windowLog;
actualCParams.chainLog = cParams->chainLog == 0 ? actualCParams.chainLog : cParams->chainLog;
actualCParams.hashLog = cParams->hashLog == 0 ? actualCParams.hashLog : cParams->hashLog;
actualCParams.searchLog = cParams->searchLog == 0 ? actualCParams.searchLog : cParams->searchLog;
actualCParams.minMatch = cParams->minMatch == 0 ? actualCParams.minMatch : cParams->minMatch;
actualCParams.targetLength = cParams->targetLength == 0 ? actualCParams.targetLength : cParams->targetLength;
actualCParams.strategy = cParams->strategy == 0 ? actualCParams.strategy : cParams->strategy;
DISPLAY("--zstd=wlog=%d,clog=%d,hlog=%d,slog=%d,mml=%d,tlen=%d,strat=%d\n",
actualCParams.windowLog, actualCParams.chainLog, actualCParams.hashLog, actualCParams.searchLog,
actualCParams.minMatch, actualCParams.targetLength, actualCParams.strategy);
}
#endif
/* Environment variables for parameter setting */
#define ENV_CLEVEL "ZSTD_CLEVEL"
#define ENV_NBTHREADS "ZSTD_NBTHREADS" /* takes lower precedence than directly specifying -T# in the CLI */
/* pick up environment variable */
static int init_cLevel(void) {
const char* const env = getenv(ENV_CLEVEL);
if (env != NULL) {
const char* ptr = env;
int sign = 1;
if (*ptr == '-') {
sign = -1;
ptr++;
} else if (*ptr == '+') {
ptr++;
}
if ((*ptr>='0') && (*ptr<='9')) {
unsigned absLevel;
if (readU32FromCharChecked(&ptr, &absLevel)) {
DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: numeric value too large \n", ENV_CLEVEL, env);
return ZSTDCLI_CLEVEL_DEFAULT;
} else if (*ptr == 0) {
return sign * (int)absLevel;
} }
DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: not a valid integer value \n", ENV_CLEVEL, env);
}
return ZSTDCLI_CLEVEL_DEFAULT;
}
#ifdef ZSTD_MULTITHREAD
static unsigned default_nbThreads(void) {
const char* const env = getenv(ENV_NBTHREADS);
if (env != NULL) {
const char* ptr = env;
if ((*ptr>='0') && (*ptr<='9')) {
unsigned nbThreads;
if (readU32FromCharChecked(&ptr, &nbThreads)) {
DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: numeric value too large \n", ENV_NBTHREADS, env);
return ZSTDCLI_NBTHREADS_DEFAULT;
} else if (*ptr == 0) {
return nbThreads;
}
}
DISPLAYLEVEL(2, "Ignore environment variable setting %s=%s: not a valid unsigned value \n", ENV_NBTHREADS, env);
}
return ZSTDCLI_NBTHREADS_DEFAULT;
}
#endif
#define NEXT_FIELD(ptr) { \
if (*argument == '=') { \
ptr = ++argument; \
argument += strlen(ptr); \
} else { \
argNb++; \
if (argNb >= argCount) { \
DISPLAYLEVEL(1, "error: missing command argument \n"); \
CLEAN_RETURN(1); \
} \
ptr = argv[argNb]; \
assert(ptr != NULL); \
if (ptr[0]=='-') { \
DISPLAYLEVEL(1, "error: command cannot be separated from its argument by another command \n"); \
CLEAN_RETURN(1); \
} } }
#define NEXT_UINT32(val32) { \
const char* __nb; \
NEXT_FIELD(__nb); \
val32 = readU32FromChar(&__nb); \
if(*__nb != 0) { \
errorOut("error: only numeric values with optional suffixes K, KB, KiB, M, MB, MiB are allowed"); \
} \
}
#define NEXT_TSIZE(valTsize) { \
const char* __nb; \
NEXT_FIELD(__nb); \
valTsize = readSizeTFromChar(&__nb); \
if(*__nb != 0) { \
errorOut("error: only numeric values with optional suffixes K, KB, KiB, M, MB, MiB are allowed"); \
} \
}
typedef enum { zom_compress, zom_decompress, zom_test, zom_bench, zom_train, zom_list } zstd_operation_mode;
#define CLEAN_RETURN(i) { operationResult = (i); goto _end; }
#ifdef ZSTD_NOCOMPRESS
/* symbols from compression library are not defined and should not be invoked */
# define MINCLEVEL -99
# define MAXCLEVEL 22
#else
# define MINCLEVEL ZSTD_minCLevel()
# define MAXCLEVEL ZSTD_maxCLevel()
#endif
int main(int argCount, const char* argv[]) int main(int argCount, const char* argv[])
{ {
int argNb, return ZSTD_rust_cli_main(argCount, argv);
followLinks = 0,
allowBlockDevices = 0,
forceStdin = 0,
forceStdout = 0,
hasStdout = 0,
ldmFlag = 0,
main_pause = 0,
adapt = 0,
adaptMin = MINCLEVEL,
adaptMax = MAXCLEVEL,
rsyncable = 0,
nextArgumentsAreFiles = 0,
operationResult = 0,
separateFiles = 0,
setRealTimePrio = 0,
singleThread = 0,
defaultLogicalCores = 0,
showDefaultCParams = 0,
ultra=0,
contentSize=1,
removeSrcFile=0;
ZSTD_ParamSwitch_e mmapDict=ZSTD_ps_auto;
ZSTD_ParamSwitch_e useRowMatchFinder = ZSTD_ps_auto;
FIO_compressionType_t cType = FIO_zstdCompression;
int nbWorkers = -1; /* -1 means unset */
double compressibility = -1.0; /* lorem ipsum generator */
unsigned bench_nbSeconds = 3; /* would be better if this value was synchronized from bench */
size_t blockSize = 0;
FIO_prefs_t* const prefs = FIO_createPreferences();
FIO_ctx_t* const fCtx = FIO_createContext();
FIO_progressSetting_e progress = FIO_ps_auto;
zstd_operation_mode operation = zom_compress;
ZSTD_compressionParameters compressionParams;
int cLevel = init_cLevel();
int cLevelLast = MINCLEVEL - 1; /* lower than minimum */
unsigned recursive = 0;
unsigned memLimit = 0;
FileNamesTable* filenames = UTIL_allocateFileNamesTable((size_t)argCount); /* argCount >= 1 */
FileNamesTable* file_of_names = UTIL_allocateFileNamesTable((size_t)argCount); /* argCount >= 1 */
const char* programName = argv[0];
const char* outFileName = NULL;
const char* outDirName = NULL;
const char* outMirroredDirName = NULL;
const char* dictFileName = NULL;
const char* patchFromDictFileName = NULL;
const char* suffix = ZSTD_EXTENSION;
unsigned maxDictSize = g_defaultMaxDictSize;
unsigned dictID = 0;
size_t streamSrcSize = 0;
size_t targetCBlockSize = 0;
size_t srcSizeHint = 0;
size_t nbInputFileNames = 0;
int dictCLevel = g_defaultDictCLevel;
unsigned dictSelect = g_defaultSelectivityLevel;
#ifndef ZSTD_NODICT
ZDICT_cover_params_t coverParams = defaultCoverParams();
ZDICT_fastCover_params_t fastCoverParams = defaultFastCoverParams();
dictType dict = fastCover;
#endif
#ifndef ZSTD_NOBENCH
BMK_advancedParams_t benchParams = BMK_initAdvancedParams();
#endif
ZSTD_ParamSwitch_e literalCompressionMode = ZSTD_ps_auto;
/* init */
checkLibVersion();
(void)recursive; (void)cLevelLast; /* not used when ZSTD_NOBENCH set */
(void)memLimit;
assert(argCount >= 1);
if ((filenames==NULL) || (file_of_names==NULL)) { DISPLAYLEVEL(1, "zstd: allocation error \n"); exit(1); }
programName = lastNameFromPath(programName);
/* preset behaviors */
if (exeNameMatch(programName, ZSTD_ZSTDMT)) nbWorkers=0, singleThread=0;
if (exeNameMatch(programName, ZSTD_UNZSTD)) operation=zom_decompress;
if (exeNameMatch(programName, ZSTD_CAT)) { operation=zom_decompress; FIO_overwriteMode(prefs); forceStdout=1; followLinks=1; FIO_setPassThroughFlag(prefs, 1); outFileName=stdoutmark; g_displayLevel=1; } /* supports multiple formats */
if (exeNameMatch(programName, ZSTD_ZCAT)) { operation=zom_decompress; FIO_overwriteMode(prefs); forceStdout=1; followLinks=1; FIO_setPassThroughFlag(prefs, 1); outFileName=stdoutmark; g_displayLevel=1; } /* behave like zcat, also supports multiple formats */
if (exeNameMatch(programName, ZSTD_GZ)) { /* behave like gzip */
suffix = GZ_EXTENSION; cType = FIO_gzipCompression; removeSrcFile=1;
dictCLevel = cLevel = 6; /* gzip default is -6 */
}
if (exeNameMatch(programName, ZSTD_GUNZIP)) { operation=zom_decompress; removeSrcFile=1; } /* behave like gunzip, also supports multiple formats */
if (exeNameMatch(programName, ZSTD_GZCAT)) { operation=zom_decompress; FIO_overwriteMode(prefs); forceStdout=1; followLinks=1; FIO_setPassThroughFlag(prefs, 1); outFileName=stdoutmark; g_displayLevel=1; } /* behave like gzcat, also supports multiple formats */
if (exeNameMatch(programName, ZSTD_LZMA)) { suffix = LZMA_EXTENSION; cType = FIO_lzmaCompression; removeSrcFile=1; } /* behave like lzma */
if (exeNameMatch(programName, ZSTD_UNLZMA)) { operation=zom_decompress; cType = FIO_lzmaCompression; removeSrcFile=1; } /* behave like unlzma, also supports multiple formats */
if (exeNameMatch(programName, ZSTD_XZ)) { suffix = XZ_EXTENSION; cType = FIO_xzCompression; removeSrcFile=1; } /* behave like xz */
if (exeNameMatch(programName, ZSTD_UNXZ)) { operation=zom_decompress; cType = FIO_xzCompression; removeSrcFile=1; } /* behave like unxz, also supports multiple formats */
if (exeNameMatch(programName, ZSTD_LZ4)) { suffix = LZ4_EXTENSION; cType = FIO_lz4Compression; } /* behave like lz4 */
if (exeNameMatch(programName, ZSTD_UNLZ4)) { operation=zom_decompress; cType = FIO_lz4Compression; } /* behave like unlz4, also supports multiple formats */
memset(&compressionParams, 0, sizeof(compressionParams));
/* init crash handler */
FIO_addAbortHandler();
/* command switches */
for (argNb=1; argNb<argCount; argNb++) {
const char* argument = argv[argNb];
const char* const originalArgument = argument;
if (!argument) continue; /* Protection if argument empty */
if (nextArgumentsAreFiles) {
UTIL_refFilename(filenames, argument);
continue;
}
/* "-" means stdin/stdout */
if (!strcmp(argument, "-")){
UTIL_refFilename(filenames, stdinmark);
continue;
}
/* Decode commands (note : aggregated commands are allowed) */
if (argument[0]=='-') {
if (argument[1]=='-') {
/* long commands (--long-word) */
if (!strcmp(argument, "--")) { nextArgumentsAreFiles=1; continue; } /* only file names allowed from now on */
if (!strcmp(argument, "--list")) { operation=zom_list; continue; }
if (!strcmp(argument, "--compress")) { operation=zom_compress; continue; }
if (!strcmp(argument, "--decompress")) { operation=zom_decompress; continue; }
if (!strcmp(argument, "--uncompress")) { operation=zom_decompress; continue; }
if (!strcmp(argument, "--force")) { FIO_overwriteMode(prefs); forceStdin=1; forceStdout=1; followLinks=1; allowBlockDevices=1; continue; }
if (!strcmp(argument, "--version")) { printVersion(); CLEAN_RETURN(0); }
if (!strcmp(argument, "--help")) { usageAdvanced(programName); CLEAN_RETURN(0); }
if (!strcmp(argument, "--verbose")) { g_displayLevel++; continue; }
if (!strcmp(argument, "--quiet")) { g_displayLevel--; continue; }
if (!strcmp(argument, "--stdout")) { forceStdout=1; outFileName=stdoutmark; continue; }
if (!strcmp(argument, "--ultra")) { ultra=1; continue; }
if (!strcmp(argument, "--check")) { FIO_setChecksumFlag(prefs, 2); continue; }
if (!strcmp(argument, "--no-check")) { FIO_setChecksumFlag(prefs, 0); continue; }
if (!strcmp(argument, "--sparse")) { FIO_setSparseWrite(prefs, 2); continue; }
if (!strcmp(argument, "--no-sparse")) { FIO_setSparseWrite(prefs, 0); continue; }
if (!strcmp(argument, "--pass-through")) { FIO_setPassThroughFlag(prefs, 1); continue; }
if (!strcmp(argument, "--no-pass-through")) { FIO_setPassThroughFlag(prefs, 0); continue; }
if (!strcmp(argument, "--test")) { operation=zom_test; continue; }
if (!strcmp(argument, "--asyncio")) { FIO_setAsyncIOFlag(prefs, 1); continue;}
if (!strcmp(argument, "--no-asyncio")) { FIO_setAsyncIOFlag(prefs, 0); continue;}
if (!strcmp(argument, "--train")) { operation=zom_train; if (outFileName==NULL) outFileName=g_defaultDictName; continue; }
if (!strcmp(argument, "--no-dictID")) { FIO_setDictIDFlag(prefs, 0); continue; }
if (!strcmp(argument, "--keep")) { removeSrcFile=0; continue; }
if (!strcmp(argument, "--rm")) { removeSrcFile=1; continue; }
if (!strcmp(argument, "--priority=rt")) { setRealTimePrio = 1; continue; }
if (!strcmp(argument, "--show-default-cparams")) { showDefaultCParams = 1; continue; }
if (!strcmp(argument, "--content-size")) { contentSize = 1; continue; }
if (!strcmp(argument, "--no-content-size")) { contentSize = 0; continue; }
if (!strcmp(argument, "--adapt")) { adapt = 1; continue; }
if (!strcmp(argument, "--no-row-match-finder")) { useRowMatchFinder = ZSTD_ps_disable; continue; }
if (!strcmp(argument, "--row-match-finder")) { useRowMatchFinder = ZSTD_ps_enable; continue; }
if (longCommandWArg(&argument, "--adapt=")) { adapt = 1; if (!parseAdaptParameters(argument, &adaptMin, &adaptMax)) { badUsage(programName, originalArgument); CLEAN_RETURN(1); } continue; }
if (!strcmp(argument, "--single-thread")) { nbWorkers = 0; singleThread = 1; continue; }
if (!strcmp(argument, "--format=zstd")) { suffix = ZSTD_EXTENSION; cType = FIO_zstdCompression; continue; }
if (!strcmp(argument, "--mmap-dict")) { mmapDict = ZSTD_ps_enable; continue; }
if (!strcmp(argument, "--no-mmap-dict")) { mmapDict = ZSTD_ps_disable; continue; }
#ifdef ZSTD_GZCOMPRESS
if (!strcmp(argument, "--format=gzip")) { suffix = GZ_EXTENSION; cType = FIO_gzipCompression; continue; }
if (exeNameMatch(programName, ZSTD_GZ)) { /* behave like gzip */
if (!strcmp(argument, "--best")) { dictCLevel = cLevel = 9; continue; }
if (!strcmp(argument, "--no-name")) { /* ignore for now */; continue; }
}
#endif
#ifdef ZSTD_LZMACOMPRESS
if (!strcmp(argument, "--format=lzma")) { suffix = LZMA_EXTENSION; cType = FIO_lzmaCompression; continue; }
if (!strcmp(argument, "--format=xz")) { suffix = XZ_EXTENSION; cType = FIO_xzCompression; continue; }
#endif
#ifdef ZSTD_LZ4COMPRESS
if (!strcmp(argument, "--format=lz4")) { suffix = LZ4_EXTENSION; cType = FIO_lz4Compression; continue; }
#endif
if (!strcmp(argument, "--rsyncable")) { rsyncable = 1; continue; }
if (!strcmp(argument, "--compress-literals")) { literalCompressionMode = ZSTD_ps_enable; continue; }
if (!strcmp(argument, "--no-compress-literals")) { literalCompressionMode = ZSTD_ps_disable; continue; }
if (!strcmp(argument, "--no-progress")) { progress = FIO_ps_never; continue; }
if (!strcmp(argument, "--progress")) { progress = FIO_ps_always; continue; }
if (!strcmp(argument, "--exclude-compressed")) { FIO_setExcludeCompressedFile(prefs, 1); continue; }
if (!strcmp(argument, "--fake-stdin-is-console")) { UTIL_fakeStdinIsConsole(); continue; }
if (!strcmp(argument, "--fake-stdout-is-console")) { UTIL_fakeStdoutIsConsole(); continue; }
if (!strcmp(argument, "--fake-stderr-is-console")) { UTIL_fakeStderrIsConsole(); continue; }
if (!strcmp(argument, "--trace-file-stat")) { UTIL_traceFileStat(); continue; }
if (!strcmp(argument, "--max")) {
if (sizeof(void*)==4) {
DISPLAYLEVEL(2, "--max is incompatible with 32-bit mode \n");
badUsage(programName, originalArgument);
CLEAN_RETURN(1);
}
ultra=1; ldmFlag = 1; setMaxCompression(&compressionParams);
continue;
}
/* long commands with arguments */
#ifndef ZSTD_NODICT
if (longCommandWArg(&argument, "--train-cover")) {
operation = zom_train;
if (outFileName == NULL)
outFileName = g_defaultDictName;
dict = cover;
/* Allow optional arguments following an = */
if (*argument == 0) { memset(&coverParams, 0, sizeof(coverParams)); }
else if (*argument++ != '=') { badUsage(programName, originalArgument); CLEAN_RETURN(1); }
else if (!parseCoverParameters(argument, &coverParams)) { badUsage(programName, originalArgument); CLEAN_RETURN(1); }
continue;
}
if (longCommandWArg(&argument, "--train-fastcover")) {
operation = zom_train;
if (outFileName == NULL)
outFileName = g_defaultDictName;
dict = fastCover;
/* Allow optional arguments following an = */
if (*argument == 0) { memset(&fastCoverParams, 0, sizeof(fastCoverParams)); }
else if (*argument++ != '=') { badUsage(programName, originalArgument); CLEAN_RETURN(1); }
else if (!parseFastCoverParameters(argument, &fastCoverParams)) { badUsage(programName, originalArgument); CLEAN_RETURN(1); }
continue;
}
if (longCommandWArg(&argument, "--train-legacy")) {
operation = zom_train;
if (outFileName == NULL)
outFileName = g_defaultDictName;
dict = legacy;
/* Allow optional arguments following an = */
if (*argument == 0) { continue; }
else if (*argument++ != '=') { badUsage(programName, originalArgument); CLEAN_RETURN(1); }
else if (!parseLegacyParameters(argument, &dictSelect)) { badUsage(programName, originalArgument); CLEAN_RETURN(1); }
continue;
}
#endif
if (longCommandWArg(&argument, "--threads")) { NEXT_UINT32(nbWorkers); continue; }
if (longCommandWArg(&argument, "--memlimit")) { NEXT_UINT32(memLimit); continue; }
if (longCommandWArg(&argument, "--memory")) { NEXT_UINT32(memLimit); continue; }
if (longCommandWArg(&argument, "--memlimit-decompress")) { NEXT_UINT32(memLimit); continue; }
if (longCommandWArg(&argument, "--block-size")) { NEXT_TSIZE(blockSize); continue; }
if (longCommandWArg(&argument, "--maxdict")) { NEXT_UINT32(maxDictSize); continue; }
if (longCommandWArg(&argument, "--dictID")) { NEXT_UINT32(dictID); continue; }
if (longCommandWArg(&argument, "--zstd=")) { if (!parseCompressionParameters(argument, &compressionParams)) { badUsage(programName, originalArgument); CLEAN_RETURN(1); } ; cType = FIO_zstdCompression; continue; }
if (longCommandWArg(&argument, "--stream-size")) { NEXT_TSIZE(streamSrcSize); continue; }
if (longCommandWArg(&argument, "--target-compressed-block-size")) { NEXT_TSIZE(targetCBlockSize); continue; }
if (longCommandWArg(&argument, "--size-hint")) { NEXT_TSIZE(srcSizeHint); continue; }
if (longCommandWArg(&argument, "--output-dir-flat")) {
NEXT_FIELD(outDirName);
if (strlen(outDirName) == 0) {
DISPLAYLEVEL(1, "error: output dir cannot be empty string (did you mean to pass '.' instead?)\n");
CLEAN_RETURN(1);
}
continue;
}
if (longCommandWArg(&argument, "--auto-threads")) {
const char* threadDefault = NULL;
NEXT_FIELD(threadDefault);
if (strcmp(threadDefault, "logical") == 0)
defaultLogicalCores = 1;
continue;
}
#ifdef UTIL_HAS_MIRRORFILELIST
if (longCommandWArg(&argument, "--output-dir-mirror")) {
NEXT_FIELD(outMirroredDirName);
if (strlen(outMirroredDirName) == 0) {
DISPLAYLEVEL(1, "error: output dir cannot be empty string (did you mean to pass '.' instead?)\n");
CLEAN_RETURN(1);
}
continue;
}
#endif
#ifndef ZSTD_NOTRACE
if (longCommandWArg(&argument, "--trace")) { char const* traceFile; NEXT_FIELD(traceFile); TRACE_enable(traceFile); continue; }
#endif
if (longCommandWArg(&argument, "--patch-from")) { NEXT_FIELD(patchFromDictFileName); ultra = 1; continue; }
if (longCommandWArg(&argument, "--long")) {
unsigned ldmWindowLog = 0;
ldmFlag = 1;
ultra = 1;
/* Parse optional window log */
if (*argument == '=') {
++argument;
ldmWindowLog = readU32FromChar(&argument);
} else if (*argument != 0) {
/* Invalid character following --long */
badUsage(programName, originalArgument);
CLEAN_RETURN(1);
} else {
ldmWindowLog = g_defaultMaxWindowLog;
}
/* Only set windowLog if not already set by --zstd */
if (compressionParams.windowLog == 0)
compressionParams.windowLog = ldmWindowLog;
continue;
}
#ifndef ZSTD_NOCOMPRESS /* linking ZSTD_minCLevel() requires compression support */
if (longCommandWArg(&argument, "--fast")) {
/* Parse optional acceleration factor */
if (*argument == '=') {
U32 const maxFast = (U32)-ZSTD_minCLevel();
U32 fastLevel;
++argument;
fastLevel = readU32FromChar(&argument);
if (fastLevel > maxFast) fastLevel = maxFast;
if (fastLevel) {
dictCLevel = cLevel = -(int)fastLevel;
} else {
badUsage(programName, originalArgument);
CLEAN_RETURN(1);
}
} else if (*argument != 0) {
/* Invalid character following --fast */
badUsage(programName, originalArgument);
CLEAN_RETURN(1);
} else {
cLevel = -1; /* default for --fast */
}
continue;
}
#endif
if (longCommandWArg(&argument, "--filelist")) {
const char* listName;
NEXT_FIELD(listName);
UTIL_refFilename(file_of_names, listName);
continue;
}
badUsage(programName, originalArgument);
CLEAN_RETURN(1);
}
argument++;
while (argument[0]!=0) {
#ifndef ZSTD_NOCOMPRESS
/* compression Level */
if ((*argument>='0') && (*argument<='9')) {
dictCLevel = cLevel = (int)readU32FromChar(&argument);
continue;
}
#endif
switch(argument[0])
{
/* Display help */
case 'V': printVersion(); CLEAN_RETURN(0); /* Version Only */
case 'H': usageAdvanced(programName); CLEAN_RETURN(0);
case 'h': usage(stdout, programName); CLEAN_RETURN(0);
/* Compress */
case 'z': operation=zom_compress; argument++; break;
/* Decoding */
case 'd':
#ifndef ZSTD_NOBENCH
benchParams.mode = BMK_decodeOnly;
if (operation==zom_bench) { argument++; break; } /* benchmark decode (hidden option) */
#endif
operation=zom_decompress; argument++; break;
/* Force stdout, even if stdout==console */
case 'c': forceStdout=1; outFileName=stdoutmark; argument++; break;
/* destination file name */
case 'o': argument++; NEXT_FIELD(outFileName); break;
/* do not store filename - gzip compatibility - nothing to do */
case 'n': argument++; break;
/* Use file content as dictionary */
case 'D': argument++; NEXT_FIELD(dictFileName); break;
/* Overwrite */
case 'f': FIO_overwriteMode(prefs); forceStdin=1; forceStdout=1; followLinks=1; allowBlockDevices=1; argument++; break;
/* Verbose mode */
case 'v': g_displayLevel++; argument++; break;
/* Quiet mode */
case 'q': g_displayLevel--; argument++; break;
/* keep source file (default) */
case 'k': removeSrcFile=0; argument++; break;
/* Checksum */
case 'C': FIO_setChecksumFlag(prefs, 2); argument++; break;
/* test compressed file */
case 't': operation=zom_test; argument++; break;
/* limit memory */
case 'M':
argument++;
memLimit = readU32FromChar(&argument);
break;
case 'l': operation=zom_list; argument++; break;
#ifdef UTIL_HAS_CREATEFILELIST
/* recursive */
case 'r': recursive=1; argument++; break;
#endif
#ifndef ZSTD_NOBENCH
/* Benchmark */
case 'b':
operation=zom_bench;
argument++;
break;
/* range bench (benchmark only) */
case 'e':
/* compression Level */
argument++;
cLevelLast = (int)readU32FromChar(&argument);
break;
/* Modify Nb Iterations (benchmark only) */
case 'i':
argument++;
bench_nbSeconds = readU32FromChar(&argument);
break;
/* cut input into blocks (benchmark only) */
case 'B':
argument++;
blockSize = readU32FromChar(&argument);
break;
/* benchmark files separately (hidden option) */
case 'S':
argument++;
separateFiles = 1;
break;
#endif /* ZSTD_NOBENCH */
/* nb of threads (hidden option) */
case 'T':
argument++;
nbWorkers = readU32FromChar(&argument);
break;
/* Dictionary Selection level */
case 's':
argument++;
dictSelect = readU32FromChar(&argument);
break;
/* Pause at the end (-p) or set an additional param (-p#) (hidden option) */
case 'p': argument++;
#ifndef ZSTD_NOBENCH
if ((*argument>='0') && (*argument<='9')) {
benchParams.additionalParam = (int)readU32FromChar(&argument);
} else
#endif
main_pause=1;
break;
/* Select compressibility of synthetic sample */
case 'P':
argument++;
compressibility = (double)readU32FromChar(&argument) / 100;
break;
/* unknown command */
default :
{ char shortArgument[3] = {'-', 0, 0};
shortArgument[1] = argument[0];
badUsage(programName, shortArgument);
CLEAN_RETURN(1);
}
}
}
continue;
} /* if (argument[0]=='-') */
/* none of the above : add filename to list */
UTIL_refFilename(filenames, argument);
}
/* Welcome message (if verbose) */
DISPLAYLEVEL(3, WELCOME_MESSAGE);
#ifdef ZSTD_MULTITHREAD
if ((operation==zom_decompress) && (nbWorkers > 1)) {
DISPLAYLEVEL(2, "Warning : decompression does not support multi-threading\n");
}
if ((nbWorkers==0) && (!singleThread)) {
/* automatically set # workers based on # of reported cpus */
if (defaultLogicalCores) {
nbWorkers = (unsigned)UTIL_countLogicalCores();
DISPLAYLEVEL(3, "Note: %d logical core(s) detected \n", nbWorkers);
} else {
nbWorkers = (unsigned)UTIL_countPhysicalCores();
DISPLAYLEVEL(3, "Note: %d physical core(s) detected \n", nbWorkers);
}
}
/* Resolve to default if nbWorkers is still unset */
if (nbWorkers == -1) {
if (operation == zom_decompress) {
nbWorkers = 1;
} else {
nbWorkers = default_nbThreads();
}
}
if (operation != zom_bench)
DISPLAYLEVEL(4, "Compressing with %u worker threads \n", nbWorkers);
#else
(void)singleThread; (void)nbWorkers; (void)defaultLogicalCores;
#endif
g_utilDisplayLevel = g_displayLevel;
#ifdef UTIL_HAS_CREATEFILELIST
if (!followLinks) {
unsigned u, fileNamesNb;
unsigned const nbFilenames = (unsigned)filenames->tableSize;
for (u=0, fileNamesNb=0; u<nbFilenames; u++) {
if ( UTIL_isLink(filenames->fileNames[u])
&& !UTIL_isFIFO(filenames->fileNames[u])
) {
DISPLAYLEVEL(2, "Warning : %s is a symbolic link, ignoring \n", filenames->fileNames[u]);
} else {
filenames->fileNames[fileNamesNb++] = filenames->fileNames[u];
} }
if (fileNamesNb == 0 && nbFilenames > 0) /* all names are eliminated */
CLEAN_RETURN(1);
filenames->tableSize = fileNamesNb;
} /* if (!followLinks) */
/* read names from a file */
if (file_of_names->tableSize) {
size_t const nbFileLists = file_of_names->tableSize;
size_t flNb;
for (flNb=0; flNb < nbFileLists; flNb++) {
FileNamesTable* const fnt = UTIL_createFileNamesTable_fromFileName(file_of_names->fileNames[flNb]);
if (fnt==NULL) {
DISPLAYLEVEL(1, "zstd: error reading %s \n", file_of_names->fileNames[flNb]);
CLEAN_RETURN(1);
}
filenames = UTIL_mergeFileNamesTable(filenames, fnt);
}
}
nbInputFileNames = filenames->tableSize; /* saving number of input files */
if (recursive) { /* at this stage, filenameTable is a list of paths, which can contain both files and directories */
UTIL_expandFNT(&filenames, followLinks);
}
#else
(void)followLinks;
#endif
if (operation == zom_list) {
#ifndef ZSTD_NODECOMPRESS
int const ret = FIO_listMultipleFiles((unsigned)filenames->tableSize, filenames->fileNames, g_displayLevel);
CLEAN_RETURN(ret);
#else
DISPLAYLEVEL(1, "file information is not supported \n");
CLEAN_RETURN(1);
#endif
}
/* Check if benchmark is selected */
if (operation==zom_bench) {
#ifndef ZSTD_NOBENCH
if (cType != FIO_zstdCompression) {
DISPLAYLEVEL(1, "benchmark mode is only compatible with zstd format \n");
CLEAN_RETURN(1);
}
benchParams.blockSize = blockSize;
benchParams.targetCBlockSize = targetCBlockSize;
benchParams.nbWorkers = (int)nbWorkers;
benchParams.realTime = (unsigned)setRealTimePrio;
benchParams.nbSeconds = bench_nbSeconds;
benchParams.ldmFlag = ldmFlag;
benchParams.ldmMinMatch = (int)g_ldmMinMatch;
benchParams.ldmHashLog = (int)g_ldmHashLog;
benchParams.useRowMatchFinder = (int)useRowMatchFinder;
if (g_ldmBucketSizeLog != LDM_PARAM_DEFAULT) {
benchParams.ldmBucketSizeLog = (int)g_ldmBucketSizeLog;
}
if (g_ldmHashRateLog != LDM_PARAM_DEFAULT) {
benchParams.ldmHashRateLog = (int)g_ldmHashRateLog;
}
benchParams.literalCompressionMode = literalCompressionMode;
if (benchParams.mode == BMK_decodeOnly) cLevel = cLevelLast = 0;
if (cLevel > ZSTD_maxCLevel()) cLevel = ZSTD_maxCLevel();
if (cLevelLast > ZSTD_maxCLevel()) cLevelLast = ZSTD_maxCLevel();
if (cLevelLast < cLevel) cLevelLast = cLevel;
DISPLAYLEVEL(3, "Benchmarking ");
if (filenames->tableSize > 1)
DISPLAYLEVEL(3, "%u files ", (unsigned)filenames->tableSize);
if (cLevelLast > cLevel) {
DISPLAYLEVEL(3, "from level %d to %d ", cLevel, cLevelLast);
} else {
DISPLAYLEVEL(3, "at level %d ", cLevel);
}
DISPLAYLEVEL(3, "using %i threads \n", nbWorkers);
if (filenames->tableSize > 0) {
if(separateFiles) {
unsigned i;
for(i = 0; i < filenames->tableSize; i++) {
operationResult = BMK_benchFilesAdvanced(&filenames->fileNames[i], 1, dictFileName, cLevel, cLevelLast, &compressionParams, g_displayLevel, &benchParams);
}
} else {
operationResult = BMK_benchFilesAdvanced(filenames->fileNames, (unsigned)filenames->tableSize, dictFileName, cLevel, cLevelLast, &compressionParams, g_displayLevel, &benchParams);
}
} else {
operationResult = BMK_syntheticTest(compressibility, cLevel, cLevelLast, &compressionParams, g_displayLevel, &benchParams);
}
#else
(void)bench_nbSeconds; (void)blockSize; (void)setRealTimePrio; (void)separateFiles; (void)compressibility;
#endif
goto _end;
}
/* Check if dictionary builder is selected */
if (operation==zom_train) {
#ifndef ZSTD_NODICT
ZDICT_params_t zParams;
zParams.compressionLevel = dictCLevel;
zParams.notificationLevel = (unsigned)g_displayLevel;
zParams.dictID = dictID;
if (dict == cover) {
int const optimize = !coverParams.k || !coverParams.d;
coverParams.nbThreads = (unsigned)nbWorkers;
coverParams.zParams = zParams;
operationResult = DiB_trainFromFiles(outFileName, maxDictSize, filenames->fileNames, (int)filenames->tableSize, blockSize, NULL, &coverParams, NULL, optimize, memLimit);
} else if (dict == fastCover) {
int const optimize = !fastCoverParams.k || !fastCoverParams.d;
fastCoverParams.nbThreads = (unsigned)nbWorkers;
fastCoverParams.zParams = zParams;
operationResult = DiB_trainFromFiles(outFileName, maxDictSize, filenames->fileNames, (int)filenames->tableSize, blockSize, NULL, NULL, &fastCoverParams, optimize, memLimit);
} else {
ZDICT_legacy_params_t dictParams;
memset(&dictParams, 0, sizeof(dictParams));
dictParams.selectivityLevel = dictSelect;
dictParams.zParams = zParams;
operationResult = DiB_trainFromFiles(outFileName, maxDictSize, filenames->fileNames, (int)filenames->tableSize, blockSize, &dictParams, NULL, NULL, 0, memLimit);
}
#else
(void)dictCLevel; (void)dictSelect; (void)dictID; (void)maxDictSize; /* not used when ZSTD_NODICT set */
DISPLAYLEVEL(1, "training mode not available \n");
operationResult = 1;
#endif
goto _end;
}
#ifndef ZSTD_NODECOMPRESS
if (operation==zom_test) { FIO_setTestMode(prefs, 1); outFileName=nulmark; removeSrcFile=0; } /* test mode */
#endif
/* No input filename ==> use stdin and stdout */
if (filenames->tableSize == 0) {
/* It is possible that the input
was a number of empty directories. In this case
stdin and stdout should not be used */
if (nbInputFileNames > 0 ){
DISPLAYLEVEL(1, "please provide correct input file(s) or non-empty directories -- ignored \n");
CLEAN_RETURN(0);
}
UTIL_refFilename(filenames, stdinmark);
}
if (filenames->tableSize == 1 && !strcmp(filenames->fileNames[0], stdinmark) && !outFileName)
outFileName = stdoutmark; /* when input is stdin, default output is stdout */
/* Check if input/output defined as console; trigger an error in this case */
if (!forceStdin
&& (UTIL_searchFileNamesTable(filenames, stdinmark) != -1)
&& UTIL_isConsole(stdin) ) {
DISPLAYLEVEL(1, "stdin is a console, aborting\n");
CLEAN_RETURN(1);
}
if ( (!outFileName || !strcmp(outFileName, stdoutmark))
&& UTIL_isConsole(stdout)
&& (UTIL_searchFileNamesTable(filenames, stdinmark) != -1)
&& !forceStdout
&& operation!=zom_decompress ) {
DISPLAYLEVEL(1, "stdout is a console, aborting\n");
CLEAN_RETURN(1);
}
#ifndef ZSTD_NOCOMPRESS
/* check compression level limits */
{ int const maxCLevel = ultra ? ZSTD_maxCLevel() : ZSTDCLI_CLEVEL_MAX;
if (cLevel > maxCLevel) {
DISPLAYLEVEL(2, "Warning : compression level higher than max, reduced to %i \n", maxCLevel);
cLevel = maxCLevel;
} }
#endif
if (showDefaultCParams) {
if (operation == zom_decompress) {
DISPLAYLEVEL(1, "error : can't use --show-default-cparams in decompression mode \n");
CLEAN_RETURN(1);
}
}
if (dictFileName != NULL && patchFromDictFileName != NULL) {
DISPLAYLEVEL(1, "error : can't use -D and --patch-from=# at the same time \n");
CLEAN_RETURN(1);
}
if (patchFromDictFileName != NULL && filenames->tableSize > 1) {
DISPLAYLEVEL(1, "error : can't use --patch-from=# on multiple files \n");
CLEAN_RETURN(1);
}
/* No status message by default when output is stdout */
hasStdout = outFileName && !strcmp(outFileName,stdoutmark);
if (hasStdout && (g_displayLevel==2)) g_displayLevel=1;
/* when stderr is not the console, do not pollute it with progress updates (unless requested) */
if (!UTIL_isConsole(stderr) && (progress!=FIO_ps_always)) progress=FIO_ps_never;
FIO_setProgressSetting(progress);
/* don't remove source files when output is stdout */;
if (hasStdout && removeSrcFile) {
DISPLAYLEVEL(3, "Note: src files are not removed when output is stdout \n");
removeSrcFile = 0;
}
FIO_setRemoveSrcFile(prefs, removeSrcFile);
/* IO Stream/File */
FIO_setHasStdoutOutput(fCtx, hasStdout);
FIO_setNbFilesTotal(fCtx, (int)filenames->tableSize);
FIO_determineHasStdinInput(fCtx, filenames);
FIO_setNotificationLevel(g_displayLevel);
FIO_setAllowBlockDevices(prefs, allowBlockDevices);
FIO_setPatchFromMode(prefs, patchFromDictFileName != NULL);
FIO_setMMapDict(prefs, mmapDict);
if (memLimit == 0) {
if (compressionParams.windowLog == 0) {
memLimit = (U32)1 << g_defaultMaxWindowLog;
} else {
memLimit = (U32)1 << (compressionParams.windowLog & 31);
} }
if (patchFromDictFileName != NULL)
dictFileName = patchFromDictFileName;
FIO_setMemLimit(prefs, memLimit);
if (operation==zom_compress) {
#ifndef ZSTD_NOCOMPRESS
FIO_setCompressionType(prefs, cType);
FIO_setContentSize(prefs, contentSize);
FIO_setNbWorkers(prefs, (int)nbWorkers);
FIO_setBlockSize(prefs, (int)blockSize);
if (g_overlapLog!=OVERLAP_LOG_DEFAULT) FIO_setOverlapLog(prefs, (int)g_overlapLog);
FIO_setLdmFlag(prefs, (unsigned)ldmFlag);
FIO_setLdmHashLog(prefs, (int)g_ldmHashLog);
FIO_setLdmMinMatch(prefs, (int)g_ldmMinMatch);
if (g_ldmBucketSizeLog != LDM_PARAM_DEFAULT) FIO_setLdmBucketSizeLog(prefs, (int)g_ldmBucketSizeLog);
if (g_ldmHashRateLog != LDM_PARAM_DEFAULT) FIO_setLdmHashRateLog(prefs, (int)g_ldmHashRateLog);
FIO_setAdaptiveMode(prefs, adapt);
FIO_setUseRowMatchFinder(prefs, (int)useRowMatchFinder);
FIO_setAdaptMin(prefs, adaptMin);
FIO_setAdaptMax(prefs, adaptMax);
FIO_setRsyncable(prefs, rsyncable);
FIO_setStreamSrcSize(prefs, streamSrcSize);
FIO_setTargetCBlockSize(prefs, targetCBlockSize);
FIO_setSrcSizeHint(prefs, srcSizeHint);
FIO_setLiteralCompressionMode(prefs, literalCompressionMode);
FIO_setSparseWrite(prefs, 0);
if (adaptMin > cLevel) cLevel = adaptMin;
if (adaptMax < cLevel) cLevel = adaptMax;
/* Compare strategies constant with the ground truth */
{ ZSTD_bounds strategyBounds = ZSTD_cParam_getBounds(ZSTD_c_strategy);
assert(ZSTD_NB_STRATEGIES == strategyBounds.upperBound);
(void)strategyBounds; }
if (showDefaultCParams || g_displayLevel >= 4) {
size_t fileNb;
for (fileNb = 0; fileNb < (size_t)filenames->tableSize; fileNb++) {
if (showDefaultCParams)
printDefaultCParams(filenames->fileNames[fileNb], dictFileName, cLevel);
if (g_displayLevel >= 4)
printActualCParams(filenames->fileNames[fileNb], dictFileName, cLevel, &compressionParams);
}
}
if (g_displayLevel >= 4)
FIO_displayCompressionParameters(prefs);
if ((filenames->tableSize==1) && outFileName)
operationResult = FIO_compressFilename(fCtx, prefs, outFileName, filenames->fileNames[0], dictFileName, cLevel, compressionParams);
else
operationResult = FIO_compressMultipleFilenames(fCtx, prefs, filenames->fileNames, outMirroredDirName, outDirName, outFileName, suffix, dictFileName, cLevel, compressionParams);
#else
/* these variables are only used when compression mode is enabled */
(void)contentSize; (void)suffix; (void)adapt; (void)rsyncable;
(void)ultra; (void)cLevel; (void)ldmFlag; (void)literalCompressionMode;
(void)targetCBlockSize; (void)streamSrcSize; (void)srcSizeHint;
(void)ZSTD_strategyMap; (void)useRowMatchFinder; (void)cType;
DISPLAYLEVEL(1, "Compression not supported \n");
#endif
} else { /* decompression or test */
#ifndef ZSTD_NODECOMPRESS
if (filenames->tableSize == 1 && outFileName) {
operationResult = FIO_decompressFilename(fCtx, prefs, outFileName, filenames->fileNames[0], dictFileName);
} else {
operationResult = FIO_decompressMultipleFilenames(fCtx, prefs, filenames->fileNames, outMirroredDirName, outDirName, outFileName, dictFileName);
}
#else
DISPLAYLEVEL(1, "Decompression not supported \n");
#endif
}
_end:
FIO_freePreferences(prefs);
FIO_freeContext(fCtx);
if (main_pause) waitEnter();
UTIL_freeFileNamesTable(filenames);
UTIL_freeFileNamesTable(file_of_names);
#ifndef ZSTD_NOTRACE
TRACE_finish();
#endif
return operationResult;
} }
+25 -4
View File
@@ -37,6 +37,8 @@ zstd ABI:
including row-based and dictionary search variants. including row-based and dictionary search variants.
- `zstd_opt_tree` maintains the binary-tree index used by optimal matching; - `zstd_opt_tree` maintains the binary-tree index used by optimal matching;
the dynamic-programming optimal parser itself remains in C for now. the dynamic-programming optimal parser itself remains in C for now.
- `zstd_ldm` implements long-distance-match parameter selection, table
maintenance, sequence generation, and sequence consumption.
- Runtime support - Runtime support
- `threading` provides platform pthread wrappers required by zstd headers. - `threading` provides platform pthread wrappers required by zstd headers.
- `pool` implements the bounded worker pool used by multithreaded compression. - `pool` implements the bounded worker pool used by multithreaded compression.
@@ -45,11 +47,20 @@ zstd ABI:
- Block decompression - Block decompression
- `zstd_decompress_block` decodes literal and sequence sections, maintains - `zstd_decompress_block` decodes literal and sequence sections, maintains
FSE/Huffman repeat state, and executes compressed-block sequences. FSE/Huffman repeat state, and executes compressed-block sequences.
- `zstd_decompress` owns the public decompression context, one-shot,
dictionary, parameter, and streaming state machines. Its C shim retains
configuration-dependent context allocation plus legacy and trace leaves.
- Command-line frontend
- `zstd_cli` owns the Rust parser, safety policy, and dispatch. It is built
by the separate `cli/` static-library package only for program archives,
so library builds do not acquire program-only dependencies. The C
`fileio` backend still owns file opening, safe replacement, sparse writes,
metadata, and streaming I/O.
The optimal block matcher, high-level frame decompression, dictionary-building, The optimal block matcher, high-level frame compression, dictionary-building,
legacy, and CLI translation units are still C. They must move before the legacy decoding callbacks, and the CLI file-I/O backend are still C. They must
rewrite is complete. Keeping that boundary explicit prevents a passing hybrid move before the rewrite is complete. Keeping that boundary explicit prevents a
build from being mistaken for the final all-Rust result. passing hybrid build from being mistaken for the final all-Rust result.
## Compatibility boundary ## Compatibility boundary
@@ -79,6 +90,16 @@ cargo test --all-targets
cargo build --release cargo build --release
``` ```
The program-only Rust archive has its own feature matrix and should be checked
from `rust/cli` as well:
```sh
cargo clippy --all-targets -- -D warnings
cargo test --all-targets
cargo test --no-default-features --features compression --all-targets
cargo test --no-default-features --features decompression --all-targets
```
Then run original compatibility tests from the repository root, starting with Then run original compatibility tests from the repository root, starting with
the narrow target for the component being migrated. For example: the narrow target for the component being migrated. For example:
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "zstd-cli-rs"
version = "0.1.0"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "zstd-cli-rs"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[features]
default = ["compression", "decompression"]
compression = []
decompression = []
+2
View File
@@ -0,0 +1,2 @@
#[path = "../../src/zstd_cli.rs"]
mod zstd_cli;
+4
View File
@@ -32,6 +32,8 @@ pub mod zstd_compress_superblock;
#[cfg(feature = "decompression")] #[cfg(feature = "decompression")]
pub mod zstd_ddict; pub mod zstd_ddict;
#[cfg(feature = "decompression")] #[cfg(feature = "decompression")]
pub mod zstd_decompress;
#[cfg(feature = "decompression")]
pub mod zstd_decompress_block; pub mod zstd_decompress_block;
#[cfg(feature = "compression")] #[cfg(feature = "compression")]
pub mod zstd_double_fast; pub mod zstd_double_fast;
@@ -40,6 +42,8 @@ pub mod zstd_fast;
#[cfg(feature = "compression")] #[cfg(feature = "compression")]
pub mod zstd_lazy; pub mod zstd_lazy;
#[cfg(feature = "compression")] #[cfg(feature = "compression")]
pub mod zstd_ldm;
#[cfg(feature = "compression")]
pub mod zstd_opt_tree; pub mod zstd_opt_tree;
#[cfg(feature = "compression")] #[cfg(feature = "compression")]
pub mod zstd_presplit; pub mod zstd_presplit;
+1541
View File
@@ -0,0 +1,1541 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
//! Rust command-line frontend for zstd.
//!
//! This is intentionally a parser and dispatch layer, not a second file I/O
//! implementation. It reuses the mature C `fileio` layer through its narrow
//! public-in-the-programs-tree ABI: file opening, safe replacement, sparse
//! writes, dictionary loading, streaming, and metadata preservation remain in
//! `programs/fileio.c` for this first migration step.
//!
//! Remaining C-only CLI boundaries are called out in `unsupported()` below:
//! benchmark execution, dictionary training, recursive/file-list expansion,
//! tracing, alternate-format selection, and the advanced directory modes.
use std::env;
use std::ffi::{CStr, CString, OsStr, OsString};
use std::fs;
use std::io::{self, IsTerminal, Write};
use std::os::raw::{c_char, c_int, c_uint};
use std::path::Path;
use std::ptr;
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
const DEFAULT_CLEVEL: i32 = 3;
#[cfg(feature = "compression")]
const DEFAULT_MAX_CLEVEL: i32 = 19;
const DEFAULT_MEM_LIMIT: u32 = 1 << 27;
const DEFAULT_LONG_WINDOW_LOG: u32 = 27;
const MAX_FAST_ACCELERATION: i32 = 128 << 10;
const STDIN_MARK: &str = "/*stdin*\\";
const STDOUT_MARK: &str = "/*stdout*\\";
#[cfg(windows)]
const NULL_MARK: &str = "NUL";
#[cfg(not(windows))]
const NULL_MARK: &str = "/dev/null";
#[cfg(feature = "compression")]
const ZSTD_SUFFIX: &[u8] = b".zst\0";
const FIO_ZSTD_COMPRESSION: c_int = 0;
const FIO_PS_AUTO: c_int = 0;
const FIO_PS_NEVER: c_int = 1;
const FIO_PS_ALWAYS: c_int = 2;
const ZSTD_PS_AUTO: c_int = 0;
const ZSTD_PS_ENABLE: c_int = 1;
const ZSTD_PS_DISABLE: c_int = 2;
#[repr(C)]
struct FIO_prefs_t {
_private: [u8; 0],
}
#[repr(C)]
struct FIO_ctx_t {
_private: [u8; 0],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct ZSTD_compressionParameters {
windowLog: u32,
chainLog: u32,
hashLog: u32,
searchLog: u32,
minMatch: u32,
targetLength: u32,
strategy: c_int,
}
unsafe extern "C" {
fn ZSTD_versionString() -> *const c_char;
fn ZSTD_rust_cli_expected_version() -> *const c_char;
static mut g_utilDisplayLevel: c_int;
#[cfg(feature = "compression")]
fn ZSTD_minCLevel() -> c_int;
#[cfg(feature = "compression")]
fn ZSTD_maxCLevel() -> c_int;
#[cfg(feature = "compression")]
fn UTIL_countPhysicalCores() -> c_int;
#[cfg(feature = "compression")]
fn UTIL_countLogicalCores() -> c_int;
fn FIO_createPreferences() -> *mut FIO_prefs_t;
fn FIO_freePreferences(prefs: *mut FIO_prefs_t);
fn FIO_createContext() -> *mut FIO_ctx_t;
fn FIO_freeContext(ctx: *mut FIO_ctx_t);
fn FIO_addAbortHandler();
fn FIO_setCompressionType(prefs: *mut FIO_prefs_t, compression_type: c_int);
fn FIO_overwriteMode(prefs: *mut FIO_prefs_t);
fn FIO_setAdaptiveMode(prefs: *mut FIO_prefs_t, adapt: c_int);
#[cfg(feature = "compression")]
fn FIO_setAdaptMin(prefs: *mut FIO_prefs_t, level: c_int);
#[cfg(feature = "compression")]
fn FIO_setAdaptMax(prefs: *mut FIO_prefs_t, level: c_int);
fn FIO_setUseRowMatchFinder(prefs: *mut FIO_prefs_t, mode: c_int);
fn FIO_setBlockSize(prefs: *mut FIO_prefs_t, block_size: c_int);
fn FIO_setChecksumFlag(prefs: *mut FIO_prefs_t, checksum: c_int);
fn FIO_setDictIDFlag(prefs: *mut FIO_prefs_t, dict_id: c_int);
fn FIO_setLdmBucketSizeLog(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setLdmFlag(prefs: *mut FIO_prefs_t, value: c_uint);
fn FIO_setLdmHashRateLog(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setLdmHashLog(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setLdmMinMatch(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setMemLimit(prefs: *mut FIO_prefs_t, limit: c_uint);
#[cfg(feature = "compression")]
fn FIO_setNbWorkers(prefs: *mut FIO_prefs_t, workers: c_int);
fn FIO_setOverlapLog(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setRemoveSrcFile(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setSparseWrite(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setRsyncable(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setStreamSrcSize(prefs: *mut FIO_prefs_t, value: usize);
fn FIO_setTargetCBlockSize(prefs: *mut FIO_prefs_t, value: usize);
fn FIO_setSrcSizeHint(prefs: *mut FIO_prefs_t, value: usize);
#[cfg(feature = "decompression")]
fn FIO_setTestMode(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setLiteralCompressionMode(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setProgressSetting(value: c_int);
fn FIO_setNotificationLevel(value: c_int);
fn FIO_setExcludeCompressedFile(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setAllowBlockDevices(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setContentSize(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setAsyncIOFlag(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setPassThroughFlag(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setMMapDict(prefs: *mut FIO_prefs_t, value: c_int);
fn FIO_setNbFilesTotal(ctx: *mut FIO_ctx_t, value: c_int);
fn FIO_setHasStdinInput(ctx: *mut FIO_ctx_t, value: c_int);
fn FIO_setHasStdoutOutput(ctx: *mut FIO_ctx_t, value: c_int);
#[cfg(feature = "compression")]
fn FIO_compressFilename(
ctx: *mut FIO_ctx_t,
prefs: *mut FIO_prefs_t,
output: *const c_char,
input: *const c_char,
dict: *const c_char,
level: c_int,
params: ZSTD_compressionParameters,
) -> c_int;
#[cfg(feature = "decompression")]
fn FIO_decompressFilename(
ctx: *mut FIO_ctx_t,
prefs: *mut FIO_prefs_t,
output: *const c_char,
input: *const c_char,
dict: *const c_char,
) -> c_int;
#[cfg(feature = "compression")]
fn FIO_compressMultipleFilenames(
ctx: *mut FIO_ctx_t,
prefs: *mut FIO_prefs_t,
inputs: *const *const c_char,
output_mirror_dir: *const c_char,
output_dir: *const c_char,
output: *const c_char,
suffix: *const c_char,
dict: *const c_char,
level: c_int,
params: ZSTD_compressionParameters,
) -> c_int;
#[cfg(feature = "decompression")]
fn FIO_decompressMultipleFilenames(
ctx: *mut FIO_ctx_t,
prefs: *mut FIO_prefs_t,
inputs: *const *const c_char,
output_mirror_dir: *const c_char,
output_dir: *const c_char,
output: *const c_char,
dict: *const c_char,
) -> c_int;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Operation {
Compress,
Decompress,
Test,
}
#[derive(Debug)]
enum Action {
Run(Box<Cli>),
Help { advanced: bool },
Version { quiet: bool },
}
#[derive(Debug)]
struct Cli {
operation: Operation,
inputs: Vec<CString>,
output: Option<CString>,
dictionary: Option<CString>,
level: i32,
ultra: bool,
display_level: i32,
force: bool,
force_stdout: bool,
remove_source: bool,
checksum: Option<i32>,
sparse: Option<i32>,
pass_through: Option<i32>,
content_size: i32,
dict_id: Option<i32>,
async_io: Option<i32>,
mmap_dict: i32,
progress: i32,
workers: Option<i32>,
block_size: Option<usize>,
mem_limit: Option<u32>,
ldm: bool,
ldm_hash_log: Option<i32>,
ldm_min_match: Option<i32>,
ldm_bucket_size_log: Option<i32>,
ldm_hash_rate_log: Option<i32>,
overlap_log: Option<i32>,
adapt: bool,
adapt_min: Option<i32>,
adapt_max: Option<i32>,
rsyncable: bool,
stream_src_size: Option<usize>,
target_cblock_size: Option<usize>,
src_size_hint: Option<usize>,
literal_compression: Option<i32>,
row_match_finder: i32,
exclude_compressed: bool,
compression_params: ZSTD_compressionParameters,
unsupported_program: Option<String>,
}
impl Cli {
fn new(program_name: &str) -> Self {
let mut cli = Self {
operation: Operation::Compress,
inputs: Vec::new(),
output: None,
dictionary: None,
level: default_level(),
ultra: false,
display_level: 2,
force: false,
force_stdout: false,
remove_source: false,
checksum: None,
sparse: None,
pass_through: None,
content_size: 1,
dict_id: None,
async_io: None,
mmap_dict: ZSTD_PS_AUTO,
progress: FIO_PS_AUTO,
workers: None,
block_size: None,
mem_limit: None,
ldm: false,
ldm_hash_log: None,
ldm_min_match: None,
ldm_bucket_size_log: None,
ldm_hash_rate_log: None,
overlap_log: None,
adapt: false,
adapt_min: None,
adapt_max: None,
rsyncable: false,
stream_src_size: None,
target_cblock_size: None,
src_size_hint: None,
literal_compression: None,
row_match_finder: ZSTD_PS_AUTO,
exclude_compressed: false,
compression_params: ZSTD_compressionParameters::default(),
unsupported_program: None,
};
match program_name {
"unzstd" => cli.operation = Operation::Decompress,
"zstdmt" => cli.workers = Some(0),
"zstdcat" | "zcat" => {
cli.operation = Operation::Decompress;
cli.output = Some(cstring(STDOUT_MARK).expect("static stdout marker"));
cli.force = true;
cli.force_stdout = true;
cli.pass_through = Some(1);
cli.display_level = 1;
}
"gzip" | "gunzip" | "gzcat" | "lzma" | "unlzma" | "xz" | "unxz" | "lz4" | "unlz4" => {
cli.unsupported_program = Some(program_name.to_owned())
}
_ => {}
}
cli
}
}
fn cstring(value: &str) -> Result<CString, String> {
CString::new(value).map_err(|_| format!("argument contains an interior NUL: {value:?}"))
}
fn os_cstring(value: &OsStr) -> Result<CString, String> {
#[cfg(unix)]
{
CString::new(value.as_bytes()).map_err(|_| "file name contains an interior NUL".to_owned())
}
#[cfg(not(unix))]
{
cstring(&value.to_string_lossy())
}
}
fn default_level() -> i32 {
match env::var("ZSTD_CLEVEL") {
Ok(value) => value.parse::<i32>().unwrap_or(DEFAULT_CLEVEL),
Err(_) => DEFAULT_CLEVEL,
}
}
#[cfg(feature = "compression")]
unsafe fn default_worker_count() -> i32 {
if let Ok(value) = env::var("ZSTD_NBTHREADS") {
if let Ok(workers) = value.parse::<u32>() {
if let Ok(workers) = i32::try_from(workers) {
return workers;
}
}
}
let logical_cores = unsafe { UTIL_countLogicalCores() }.max(1);
(logical_cores / 4).clamp(1, 4)
}
#[cfg(feature = "compression")]
unsafe fn resolved_worker_count(workers: Option<i32>) -> i32 {
match workers {
Some(0) => unsafe { UTIL_countPhysicalCores() }.max(1),
Some(workers) => workers,
None => unsafe { default_worker_count() },
}
}
fn program_basename(value: &OsStr) -> String {
Path::new(value)
.file_name()
.unwrap_or(value)
.to_string_lossy()
.split('.')
.next()
.unwrap_or("zstd")
.to_owned()
}
fn usage(advanced: bool) {
let mut out = io::stdout().lock();
let _ = writeln!(
out,
"Compress or decompress INPUT file(s); reads stdin when INPUT is '-' or omitted."
);
let _ = writeln!(out, "\nUsage: zstd [OPTIONS...] [INPUT... | -] [-o OUTPUT]");
let _ = writeln!(out, "\nCore options:");
let _ = writeln!(
out,
" -o OUTPUT, -c, --stdout Select output file or stdout"
);
let _ = writeln!(out, " -d, --decompress Decompress");
let _ = writeln!(out, " -t, --test Test compressed input");
let _ = writeln!(
out,
" -# Compression level (default {DEFAULT_CLEVEL})"
);
let _ = writeln!(out, " -D DICT Use a dictionary");
let _ = writeln!(
out,
" -f, --force Overwrite output / allow stdio"
);
let _ = writeln!(
out,
" -k, --keep | --rm Preserve or remove source after success"
);
let _ = writeln!(out, " -q, --quiet | -v, --verbose Adjust display level");
let _ = writeln!(out, " -V, --version Print version");
let _ = writeln!(out, " -h | -H, --help Print help");
if advanced {
let _ = writeln!(out, "\nImplemented advanced compression controls:");
let _ = writeln!(
out,
" --fast[=#], --ultra, --long[=#], --threads=#, --block-size=#"
);
let _ = writeln!(
out,
" --zstd=wlog=#,clog=#,hlog=#,slog=#,mml=#,tlen=#,strat=#"
);
let _ = writeln!(
out,
" --[no-]check, --[no-]sparse, --[no-]progress, --[no-]asyncio"
);
let _ = writeln!(
out,
" --adapt[=min=#,max=#], --rsyncable, --[no-]row-match-finder"
);
let _ = writeln!(
out,
"\nNot yet migrated: benchmark, dictionary training, recursive/file-list expansion,"
);
let _ = writeln!(out, "trace, alternate formats, and output-directory modes.");
}
}
fn print_version(quiet: bool) {
let version = unsafe { CStr::from_ptr(ZSTD_versionString()) }
.to_string_lossy()
.into_owned();
if quiet {
println!("{version}");
} else {
println!(
"*** Zstandard CLI ({}-bit) v{version}, by Yann Collet ***",
usize::BITS
);
}
}
fn check_lib_version() -> Result<(), String> {
let expected = unsafe { CStr::from_ptr(ZSTD_rust_cli_expected_version()) };
let actual = unsafe { CStr::from_ptr(ZSTD_versionString()) };
if expected == actual {
return Ok(());
}
Err(format!(
"incorrect library version (expecting: {}; actual: {})",
expected.to_string_lossy(),
actual.to_string_lossy()
))
}
fn parse_size(value: &str) -> Result<usize, String> {
let split = value
.find(|character: char| !character.is_ascii_digit())
.unwrap_or(value.len());
let (digits, suffix) = value.split_at(split);
if digits.is_empty() {
return Err(format!("expected a numeric value, got {value:?}"));
}
let mut number = digits
.parse::<usize>()
.map_err(|_| format!("numeric value overflows size_t: {value:?}"))?;
let normalized = suffix.trim_end_matches('B').trim_end_matches('i');
let shift = match normalized {
"" => 0,
"K" | "k" => 10,
"M" | "m" => 20,
"G" | "g" => 30,
_ => return Err(format!("unsupported numeric suffix in {value:?}")),
};
number = number
.checked_shl(shift)
.ok_or_else(|| format!("numeric value overflows size_t: {value:?}"))?;
Ok(number)
}
fn parse_u32(value: &str, name: &str) -> Result<u32, String> {
let size = parse_size(value)?;
u32::try_from(size).map_err(|_| format!("{name} is too large: {value:?}"))
}
fn parse_i32(value: &str, name: &str) -> Result<i32, String> {
value
.parse::<i32>()
.map_err(|_| format!("invalid {name}: {value:?}"))
}
fn parse_worker_count(value: &str) -> Result<i32, String> {
let workers = parse_i32(value, "thread count")?;
if workers < 0 {
return Err(format!("thread count must not be negative: {value:?}"));
}
Ok(workers)
}
fn next_value(
attached: Option<&str>,
args: &[OsString],
index: &mut usize,
option: &str,
) -> Result<String, String> {
if let Some(value) = attached {
if !value.is_empty() {
return Ok(value.to_owned());
}
}
*index += 1;
let Some(value) = args.get(*index) else {
return Err(format!("missing argument for {option}"));
};
let rendered = value.to_string_lossy().into_owned();
if rendered.starts_with('-') {
return Err(format!(
"{option} cannot be separated from its argument by another option"
));
}
Ok(rendered)
}
fn next_os_value(args: &[OsString], index: &mut usize, option: &str) -> Result<OsString, String> {
*index += 1;
let Some(value) = args.get(*index) else {
return Err(format!("missing argument for {option}"));
};
if value.to_string_lossy().starts_with('-') {
return Err(format!(
"{option} cannot be separated from its argument by another option"
));
}
Ok(value.clone())
}
#[cfg(unix)]
fn short_attached_value(value: &OsStr, start: usize) -> Option<OsString> {
let bytes = &value.as_bytes()[start..];
if bytes.is_empty() {
return None;
}
let bytes = if bytes.first() == Some(&b'=') {
&bytes[1..]
} else {
bytes
};
Some(OsString::from_vec(bytes.to_vec()))
}
#[cfg(not(unix))]
fn short_attached_value(value: &OsStr, start: usize) -> Option<OsString> {
let rendered = value.to_string_lossy();
let attached = &rendered[start..];
if attached.is_empty() {
return None;
}
Some(OsString::from(
attached.strip_prefix('=').unwrap_or(attached),
))
}
fn parse_compression_parameters(value: &str, cli: &mut Cli) -> Result<(), String> {
for item in value.split(',') {
let Some((name, raw)) = item.split_once('=') else {
return Err(format!("invalid --zstd parameter {item:?}"));
};
let parsed = parse_u32(raw, name)?;
match name {
"windowLog" | "wlog" => cli.compression_params.windowLog = parsed,
"chainLog" | "clog" => cli.compression_params.chainLog = parsed,
"hashLog" | "hlog" => cli.compression_params.hashLog = parsed,
"searchLog" | "slog" => cli.compression_params.searchLog = parsed,
"minMatch" | "mml" => cli.compression_params.minMatch = parsed,
"targetLength" | "tlen" => cli.compression_params.targetLength = parsed,
"strategy" | "strat" => cli.compression_params.strategy = parsed as c_int,
"overlapLog" | "ovlog" => cli.overlap_log = Some(parsed as i32),
"ldmHashLog" | "lhlog" => cli.ldm_hash_log = Some(parsed as i32),
"ldmMinMatch" | "lmml" => cli.ldm_min_match = Some(parsed as i32),
"ldmBucketSizeLog" | "lblog" => cli.ldm_bucket_size_log = Some(parsed as i32),
"ldmHashRateLog" | "lhrlog" => cli.ldm_hash_rate_log = Some(parsed as i32),
_ => return Err(format!("unknown --zstd parameter {name:?}")),
}
}
Ok(())
}
fn parse_adapt(value: &str, cli: &mut Cli) -> Result<(), String> {
cli.adapt = true;
if value.is_empty() {
return Ok(());
}
for item in value.split(',') {
let Some((name, raw)) = item.split_once('=') else {
return Err(format!("invalid --adapt parameter {item:?}"));
};
match name {
"min" => cli.adapt_min = Some(parse_i32(raw, "adapt minimum")?),
"max" => cli.adapt_max = Some(parse_i32(raw, "adapt maximum")?),
_ => return Err(format!("unknown --adapt parameter {name:?}")),
}
}
if let (Some(minimum), Some(maximum)) = (cli.adapt_min, cli.adapt_max) {
if minimum > maximum {
return Err("--adapt minimum must not exceed its maximum".to_owned());
}
}
Ok(())
}
fn unsupported(option: &str) -> Result<(), String> {
Err(format!(
"{option} is not yet implemented by the Rust CLI frontend"
))
}
fn parse_long_option(
option: &str,
args: &[OsString],
index: &mut usize,
cli: &mut Cli,
) -> Result<Option<Action>, String> {
let (name, attached) = option
.split_once('=')
.map_or((option, None), |(name, value)| (name, Some(value)));
if attached.is_some()
&& matches!(
name,
"--compress"
| "--decompress"
| "--uncompress"
| "--test"
| "--force"
| "--keep"
| "--rm"
| "--stdout"
| "--version"
| "--help"
| "--verbose"
| "--quiet"
| "--check"
| "--no-check"
| "--sparse"
| "--no-sparse"
| "--pass-through"
| "--no-pass-through"
| "--content-size"
| "--no-content-size"
| "--no-dictID"
| "--asyncio"
| "--no-asyncio"
| "--mmap-dict"
| "--no-mmap-dict"
| "--progress"
| "--no-progress"
| "--ultra"
| "--no-row-match-finder"
| "--row-match-finder"
| "--rsyncable"
| "--compress-literals"
| "--no-compress-literals"
| "--exclude-compressed"
| "--no-name"
)
{
return Err(format!("{name} does not take an argument"));
}
match name {
"--" => Ok(None),
"--compress" => {
cli.operation = Operation::Compress;
Ok(None)
}
"--decompress" | "--uncompress" => {
cli.operation = Operation::Decompress;
Ok(None)
}
"--test" => {
cli.operation = Operation::Test;
Ok(None)
}
"--force" => {
cli.force = true;
Ok(None)
}
"--keep" => {
cli.remove_source = false;
Ok(None)
}
"--no-name" => Ok(None),
"--rm" => {
cli.remove_source = true;
Ok(None)
}
"--stdout" => {
cli.output = Some(cstring(STDOUT_MARK)?);
cli.force_stdout = true;
Ok(None)
}
"--version" => Ok(Some(Action::Version {
quiet: cli.display_level < 2,
})),
"--help" => Ok(Some(Action::Help { advanced: true })),
"--verbose" => {
cli.display_level += 1;
Ok(None)
}
"--quiet" => {
cli.display_level -= 1;
Ok(None)
}
"--check" => {
cli.checksum = Some(2);
Ok(None)
}
"--no-check" => {
cli.checksum = Some(0);
Ok(None)
}
"--sparse" => {
cli.sparse = Some(2);
Ok(None)
}
"--no-sparse" => {
cli.sparse = Some(0);
Ok(None)
}
"--pass-through" => {
cli.pass_through = Some(1);
Ok(None)
}
"--no-pass-through" => {
cli.pass_through = Some(0);
Ok(None)
}
"--content-size" => {
cli.content_size = 1;
Ok(None)
}
"--no-content-size" => {
cli.content_size = 0;
Ok(None)
}
"--no-dictID" => {
cli.dict_id = Some(0);
Ok(None)
}
"--asyncio" => {
cli.async_io = Some(1);
Ok(None)
}
"--no-asyncio" => {
cli.async_io = Some(0);
Ok(None)
}
"--mmap-dict" => {
cli.mmap_dict = ZSTD_PS_ENABLE;
Ok(None)
}
"--no-mmap-dict" => {
cli.mmap_dict = ZSTD_PS_DISABLE;
Ok(None)
}
"--progress" => {
cli.progress = FIO_PS_ALWAYS;
Ok(None)
}
"--no-progress" => {
cli.progress = FIO_PS_NEVER;
Ok(None)
}
"--ultra" => {
cli.ultra = true;
Ok(None)
}
"--fast" => {
let mut level = match attached {
Some(value) => parse_i32(value, "fast level")?,
None => 1,
};
if level <= 0 {
return Err("fast level must be positive".to_owned());
}
level = level.min(MAX_FAST_ACCELERATION);
cli.level = -level;
Ok(None)
}
"--long" => {
cli.ldm = true;
cli.ultra = true;
if let Some(value) = attached {
cli.compression_params.windowLog = parse_u32(value, "long window log")?;
} else if cli.compression_params.windowLog == 0 {
cli.compression_params.windowLog = DEFAULT_LONG_WINDOW_LOG;
}
Ok(None)
}
"--adapt" => {
parse_adapt(attached.unwrap_or(""), cli)?;
Ok(None)
}
"--no-row-match-finder" => {
cli.row_match_finder = ZSTD_PS_DISABLE;
Ok(None)
}
"--row-match-finder" => {
cli.row_match_finder = ZSTD_PS_ENABLE;
Ok(None)
}
"--rsyncable" => {
cli.rsyncable = true;
Ok(None)
}
"--compress-literals" => {
cli.literal_compression = Some(ZSTD_PS_ENABLE);
Ok(None)
}
"--no-compress-literals" => {
cli.literal_compression = Some(ZSTD_PS_DISABLE);
Ok(None)
}
"--exclude-compressed" => {
cli.exclude_compressed = true;
Ok(None)
}
"--threads" => {
let value = next_value(attached, args, index, "--threads")?;
cli.workers = Some(parse_worker_count(&value)?);
Ok(None)
}
"--memlimit" | "--memory" | "--memlimit-decompress" => {
let value = next_value(attached, args, index, name)?;
cli.mem_limit = Some(parse_u32(&value, "memory limit")?);
Ok(None)
}
"--block-size" => {
let value = next_value(attached, args, index, name)?;
cli.block_size = Some(parse_size(&value)?);
Ok(None)
}
"--stream-size" => {
let value = next_value(attached, args, index, name)?;
cli.stream_src_size = Some(parse_size(&value)?);
Ok(None)
}
"--target-compressed-block-size" => {
let value = next_value(attached, args, index, name)?;
cli.target_cblock_size = Some(parse_size(&value)?);
Ok(None)
}
"--size-hint" => {
let value = next_value(attached, args, index, name)?;
cli.src_size_hint = Some(parse_size(&value)?);
Ok(None)
}
"--zstd" => {
let value = next_value(attached, args, index, "--zstd")?;
parse_compression_parameters(&value, cli)?;
Ok(None)
}
"--list"
| "--train"
| "--train-cover"
| "--train-fastcover"
| "--train-legacy"
| "--max"
| "--maxdict"
| "--dictID"
| "--filelist"
| "--output-dir-flat"
| "--output-dir-mirror"
| "--patch-from"
| "--trace"
| "--format"
| "--priority"
| "--single-thread"
| "--auto-threads"
| "--fake-stdin-is-console"
| "--fake-stdout-is-console"
| "--fake-stderr-is-console"
| "--trace-file-stat"
| "--show-default-cparams" => {
unsupported(name)?;
Ok(None)
}
_ => Err(format!("unknown option {option:?}")),
}
}
fn parse_short_options(
value: &str,
raw_value: &OsStr,
args: &[OsString],
index: &mut usize,
cli: &mut Cli,
) -> Result<Option<Action>, String> {
let mut offset = 1usize;
let bytes = value.as_bytes();
while offset < bytes.len() {
let option = bytes[offset] as char;
match option {
'0'..='9' => {
let mut digits_end = offset;
while digits_end < bytes.len() && bytes[digits_end].is_ascii_digit() {
digits_end += 1;
}
cli.level = parse_i32(&value[offset..digits_end], "compression level")?;
offset = digits_end;
continue;
}
'd' => cli.operation = Operation::Decompress,
'z' => cli.operation = Operation::Compress,
't' => cli.operation = Operation::Test,
'c' => {
cli.output = Some(cstring(STDOUT_MARK)?);
cli.force_stdout = true;
}
'f' => cli.force = true,
'k' => cli.remove_source = false,
'n' => {}
'q' => cli.display_level -= 1,
'v' => cli.display_level += 1,
'C' => cli.checksum = Some(2),
'h' => return Ok(Some(Action::Help { advanced: false })),
'H' => return Ok(Some(Action::Help { advanced: true })),
'V' => {
return Ok(Some(Action::Version {
quiet: cli.display_level < 2,
}))
}
'o' | 'D' | 'T' | 'M' | 'B' => {
let attached = short_attached_value(raw_value, offset + 1);
let argument = attached
.map(Ok)
.unwrap_or_else(|| next_os_value(args, index, &format!("-{option}")))?;
match option {
'o' => cli.output = Some(os_cstring(&argument)?),
'D' => cli.dictionary = Some(os_cstring(&argument)?),
'T' => cli.workers = Some(parse_worker_count(&argument.to_string_lossy())?),
'M' => {
cli.mem_limit =
Some(parse_u32(&argument.to_string_lossy(), "memory limit")?)
}
'B' => cli.block_size = Some(parse_size(&argument.to_string_lossy())?),
_ => unreachable!(),
}
break;
}
'b' | 'e' | 'i' | 'l' | 'p' | 'P' | 'r' | 's' | 'S' => {
unsupported(&format!("-{option}"))?;
}
_ => return Err(format!("unknown option -{option}")),
}
offset += 1;
}
Ok(None)
}
fn parse_args(args: Vec<OsString>) -> Result<Action, String> {
let program_name = args
.first()
.map_or_else(|| "zstd".to_owned(), |value| program_basename(value));
let mut cli = Cli::new(&program_name);
let mut end_of_options = false;
let mut index = 1usize;
while index < args.len() {
let rendered = args[index].to_string_lossy().into_owned();
if end_of_options {
cli.inputs.push(os_cstring(&args[index])?);
} else if rendered == "--" {
end_of_options = true;
} else if rendered == "-" {
cli.inputs.push(cstring(STDIN_MARK)?);
} else if rendered.starts_with("--") {
if let Some(action) = parse_long_option(&rendered, &args, &mut index, &mut cli)? {
return Ok(action);
}
} else if rendered.starts_with('-') {
if let Some(action) =
parse_short_options(&rendered, &args[index], &args, &mut index, &mut cli)?
{
return Ok(action);
}
} else {
cli.inputs.push(os_cstring(&args[index])?);
}
index += 1;
}
Ok(Action::Run(Box::new(cli)))
}
unsafe fn apply_preferences(cli: &Cli, prefs: *mut FIO_prefs_t, ctx: *mut FIO_ctx_t) {
let display_level = if is_stdout(cli.output.as_ref()) && cli.display_level == 2 {
1
} else {
cli.display_level
};
unsafe {
g_utilDisplayLevel = display_level;
FIO_setCompressionType(prefs, FIO_ZSTD_COMPRESSION);
FIO_setNotificationLevel(display_level);
FIO_setProgressSetting(
if !io::stderr().is_terminal() && cli.progress != FIO_PS_ALWAYS {
FIO_PS_NEVER
} else {
cli.progress
},
);
FIO_setRemoveSrcFile(
prefs,
i32::from(
cli.remove_source
&& cli.operation != Operation::Test
&& !is_stdout(cli.output.as_ref()),
),
);
FIO_setAllowBlockDevices(prefs, i32::from(cli.force));
FIO_setMMapDict(prefs, cli.mmap_dict);
FIO_setUseRowMatchFinder(prefs, cli.row_match_finder);
FIO_setMemLimit(
prefs,
cli.mem_limit
.filter(|limit| *limit != 0)
.unwrap_or_else(|| {
if cli.compression_params.windowLog == 0 {
DEFAULT_MEM_LIMIT
} else {
1_u32 << (cli.compression_params.windowLog & 31)
}
}),
);
#[cfg(feature = "compression")]
FIO_setNbWorkers(prefs, resolved_worker_count(cli.workers));
FIO_setLdmFlag(prefs, u32::from(cli.ldm));
FIO_setAdaptiveMode(prefs, i32::from(cli.adapt));
FIO_setRsyncable(prefs, i32::from(cli.rsyncable));
FIO_setExcludeCompressedFile(prefs, i32::from(cli.exclude_compressed));
if cli.force {
FIO_overwriteMode(prefs);
}
if let Some(value) = cli.checksum {
FIO_setChecksumFlag(prefs, value);
}
if cli.operation == Operation::Compress {
FIO_setSparseWrite(prefs, 0);
} else if let Some(value) = cli.sparse {
FIO_setSparseWrite(prefs, value);
}
if let Some(value) = cli.pass_through {
FIO_setPassThroughFlag(prefs, value);
}
FIO_setContentSize(prefs, cli.content_size);
if let Some(value) = cli.dict_id {
FIO_setDictIDFlag(prefs, value);
}
if let Some(value) = cli.async_io {
FIO_setAsyncIOFlag(prefs, value);
}
if let Some(value) = cli.block_size {
FIO_setBlockSize(prefs, value as c_int);
}
if let Some(value) = cli.ldm_hash_log {
FIO_setLdmHashLog(prefs, value);
}
if let Some(value) = cli.ldm_min_match {
FIO_setLdmMinMatch(prefs, value);
}
if let Some(value) = cli.ldm_bucket_size_log {
FIO_setLdmBucketSizeLog(prefs, value);
}
if let Some(value) = cli.ldm_hash_rate_log {
FIO_setLdmHashRateLog(prefs, value);
}
if let Some(value) = cli.overlap_log {
FIO_setOverlapLog(prefs, value);
}
#[cfg(feature = "compression")]
{
FIO_setAdaptMin(prefs, cli.adapt_min.unwrap_or_else(|| ZSTD_minCLevel()));
FIO_setAdaptMax(prefs, cli.adapt_max.unwrap_or_else(|| ZSTD_maxCLevel()));
}
if let Some(value) = cli.stream_src_size {
FIO_setStreamSrcSize(prefs, value);
}
if let Some(value) = cli.target_cblock_size {
FIO_setTargetCBlockSize(prefs, value);
}
if let Some(value) = cli.src_size_hint {
FIO_setSrcSizeHint(prefs, value);
}
if let Some(value) = cli.literal_compression {
FIO_setLiteralCompressionMode(prefs, value);
}
FIO_setNbFilesTotal(ctx, cli.inputs.len() as c_int);
FIO_setHasStdinInput(ctx, i32::from(cli.inputs.iter().any(is_stdin)));
FIO_setHasStdoutOutput(ctx, i32::from(is_stdout(cli.output.as_ref())));
}
}
fn is_stdout(value: Option<&CString>) -> bool {
value.is_some_and(|value| value.as_bytes() == STDOUT_MARK.as_bytes())
}
fn is_stdin(value: &CString) -> bool {
value.as_bytes() == STDIN_MARK.as_bytes()
}
#[cfg(unix)]
fn is_non_fifo_symlink(input: &CString) -> bool {
let path = Path::new(OsStr::from_bytes(input.as_bytes()));
let Ok(metadata) = fs::symlink_metadata(path) else {
return false;
};
metadata.file_type().is_symlink()
&& !fs::metadata(path).is_ok_and(|target| target.file_type().is_fifo())
}
#[cfg(not(unix))]
fn is_non_fifo_symlink(input: &CString) -> bool {
let path = Path::new(&input.to_string_lossy().into_owned());
fs::symlink_metadata(path).is_ok_and(|metadata| metadata.file_type().is_symlink())
}
fn filter_symlink_inputs(cli: &mut Cli) {
if cli.force {
return;
}
cli.inputs.retain(|input| {
if is_stdin(input) || !is_non_fifo_symlink(input) {
return true;
}
if cli.display_level >= 2 {
eprintln!(
"zstd: Warning : {} is a symbolic link, ignoring",
input.to_string_lossy()
);
}
false
});
}
fn check_terminal_safety(cli: &Cli) -> Result<(), String> {
let has_stdin = cli.inputs.iter().any(is_stdin);
if has_stdin && !cli.force && io::stdin().is_terminal() {
return Err("stdin is a console, aborting".to_owned());
}
if has_stdin
&& is_stdout(cli.output.as_ref())
&& !cli.force
&& !cli.force_stdout
&& cli.operation != Operation::Decompress
&& io::stdout().is_terminal()
{
return Err("stdout is a console, aborting".to_owned());
}
Ok(())
}
#[cfg(feature = "compression")]
unsafe fn run_compress(
cli: &Cli,
ctx: *mut FIO_ctx_t,
prefs: *mut FIO_prefs_t,
inputs: &[*const c_char],
output: *const c_char,
dictionary: *const c_char,
) -> c_int {
if inputs.len() == 1 && !output.is_null() {
unsafe {
FIO_compressFilename(
ctx,
prefs,
output,
inputs[0],
dictionary,
cli.level,
cli.compression_params,
)
}
} else {
unsafe {
FIO_compressMultipleFilenames(
ctx,
prefs,
inputs.as_ptr(),
ptr::null(),
ptr::null(),
output,
ZSTD_SUFFIX.as_ptr().cast(),
dictionary,
cli.level,
cli.compression_params,
)
}
}
}
#[cfg(feature = "decompression")]
unsafe fn run_decompress(
operation: Operation,
ctx: *mut FIO_ctx_t,
prefs: *mut FIO_prefs_t,
inputs: &[*const c_char],
output: *const c_char,
dictionary: *const c_char,
) -> c_int {
match operation {
Operation::Test => {
let null_output = cstring(NULL_MARK).expect("static null marker");
unsafe {
FIO_setTestMode(prefs, 1);
FIO_decompressMultipleFilenames(
ctx,
prefs,
inputs.as_ptr(),
ptr::null(),
ptr::null(),
null_output.as_ptr(),
dictionary,
)
}
}
Operation::Decompress if inputs.len() == 1 && !output.is_null() => unsafe {
FIO_decompressFilename(ctx, prefs, output, inputs[0], dictionary)
},
Operation::Decompress => unsafe {
FIO_decompressMultipleFilenames(
ctx,
prefs,
inputs.as_ptr(),
ptr::null(),
ptr::null(),
output,
dictionary,
)
},
Operation::Compress => unreachable!("compression is dispatched separately"),
}
}
fn run_cli(mut cli: Cli) -> Result<i32, String> {
if let Some(program_name) = &cli.unsupported_program {
return Err(format!(
"{program_name} compatibility mode is not yet implemented by the Rust CLI frontend"
));
}
let explicit_input_count = cli.inputs.len();
filter_symlink_inputs(&mut cli);
if explicit_input_count > 0 && cli.inputs.is_empty() {
return Ok(1);
}
if cli.operation == Operation::Test {
cli.output = Some(cstring(NULL_MARK)?);
cli.remove_source = false;
}
if cli.inputs.is_empty() {
cli.inputs.push(cstring(STDIN_MARK)?);
if cli.output.is_none() {
cli.output = Some(cstring(STDOUT_MARK)?);
}
}
if cli.inputs.len() == 1
&& cli.inputs[0].as_bytes() == STDIN_MARK.as_bytes()
&& cli.output.is_none()
{
cli.output = Some(cstring(STDOUT_MARK)?);
}
check_terminal_safety(&cli)?;
if cli.operation == Operation::Compress {
#[cfg(not(feature = "compression"))]
return Err("Compression not supported".to_owned());
#[cfg(feature = "compression")]
{
let min_level = unsafe { ZSTD_minCLevel() };
let max_level = unsafe { ZSTD_maxCLevel() };
let ceiling = if cli.ultra {
max_level
} else {
DEFAULT_MAX_CLEVEL.min(max_level)
};
if cli.level > ceiling {
eprintln!("zstd: warning: compression level reduced to {ceiling}");
cli.level = ceiling;
}
if cli.level < min_level {
return Err(format!(
"compression level {} is below {min_level}",
cli.level
));
}
if let (Some(minimum), Some(maximum)) = (cli.adapt_min, cli.adapt_max) {
if minimum > maximum {
return Err("adaptation minimum exceeds maximum".to_owned());
}
cli.level = cli.level.clamp(minimum, maximum);
}
}
} else {
#[cfg(not(feature = "decompression"))]
return Err("Decompression not supported".to_owned());
}
let prefs = unsafe { FIO_createPreferences() };
let ctx = unsafe { FIO_createContext() };
if prefs.is_null() || ctx.is_null() {
unsafe {
if !prefs.is_null() {
FIO_freePreferences(prefs);
}
if !ctx.is_null() {
FIO_freeContext(ctx);
}
}
return Err("could not allocate C file-I/O state".to_owned());
}
let result = {
unsafe {
FIO_addAbortHandler();
apply_preferences(&cli, prefs, ctx);
}
let output = cli
.output
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr());
let dictionary = cli
.dictionary
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr());
let inputs: Vec<*const c_char> = cli.inputs.iter().map(|value| value.as_ptr()).collect();
match cli.operation {
Operation::Compress => {
#[cfg(feature = "compression")]
{
unsafe { run_compress(&cli, ctx, prefs, &inputs, output, dictionary) }
}
#[cfg(not(feature = "compression"))]
unreachable!("unsupported compression was rejected above")
}
Operation::Decompress | Operation::Test => {
#[cfg(feature = "decompression")]
{
unsafe {
run_decompress(cli.operation, ctx, prefs, &inputs, output, dictionary)
}
}
#[cfg(not(feature = "decompression"))]
unreachable!("unsupported decompression was rejected above")
}
}
};
unsafe {
FIO_freePreferences(prefs);
FIO_freeContext(ctx);
}
Ok(result)
}
fn run_from_args(args: Vec<OsString>) -> c_int {
match parse_args(args) {
Ok(Action::Help { advanced }) => {
usage(advanced);
0
}
Ok(Action::Version { quiet }) => {
print_version(quiet);
0
}
Ok(Action::Run(cli)) => match run_cli(*cli) {
Ok(result) => result,
Err(error) => {
eprintln!("zstd: {error}");
1
}
},
Err(error) => {
eprintln!("zstd: {error}\nTry `zstd --help` for usage.");
1
}
}
}
unsafe fn argv_to_os_strings(
arg_count: c_int,
argv: *const *const c_char,
) -> Result<Vec<OsString>, String> {
if arg_count <= 0 || argv.is_null() {
return Err("invalid argv supplied by C main".to_owned());
}
let count = arg_count as usize;
let mut args = Vec::with_capacity(count);
for index in 0..count {
let argument = unsafe { *argv.add(index) };
if argument.is_null() {
return Err(format!("argv[{index}] is null"));
}
let bytes = unsafe { CStr::from_ptr(argument) }.to_bytes();
#[cfg(unix)]
args.push(OsString::from_vec(bytes.to_vec()));
#[cfg(not(unix))]
args.push(OsString::from(String::from_utf8_lossy(bytes).into_owned()));
}
Ok(args)
}
/// C `main()` entry point retained by the small `programs/zstdcli.c` forwarder.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_cli_main(arg_count: c_int, argv: *const *const c_char) -> c_int {
if let Err(error) = check_lib_version() {
eprintln!("zstd: {error}");
return 1;
}
match unsafe { argv_to_os_strings(arg_count, argv) } {
Ok(args) => run_from_args(args),
Err(error) => {
eprintln!("zstd: {error}");
1
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(values: &[&str]) -> Cli {
let args = values.iter().map(OsString::from).collect();
match parse_args(args).expect("arguments should parse") {
Action::Run(cli) => *cli,
Action::Help { .. } | Action::Version { .. } => panic!("expected a run action"),
}
}
#[test]
fn defaults_preserve_the_c_fileio_contract() {
let cli = parse(&["zstd", "input"]);
assert_eq!(cli.content_size, 1);
assert_eq!(cli.workers, None);
assert_eq!(cli.operation, Operation::Compress);
}
#[test]
fn long_mode_uses_the_cli_default_window_and_enables_ultra() {
let cli = parse(&["zstd", "--long", "input"]);
assert!(cli.ldm);
assert!(cli.ultra);
assert_eq!(cli.compression_params.windowLog, DEFAULT_LONG_WINDOW_LOG);
}
#[test]
fn long_mode_does_not_replace_an_explicit_window_log() {
let cli = parse(&["zstd", "--zstd=wlog=25", "--long", "input"]);
assert_eq!(cli.compression_params.windowLog, 25);
}
#[test]
fn stdio_and_dictionary_short_options_are_preserved() {
let cli = parse(&["zstd", "-dc", "-D", "dict", "-"]);
assert_eq!(cli.operation, Operation::Decompress);
assert!(is_stdout(cli.output.as_ref()));
assert_eq!(
cli.dictionary.as_deref().map(CStr::to_bytes),
Some(&b"dict"[..])
);
assert_eq!(
cli.inputs
.iter()
.map(|input| input.as_bytes())
.collect::<Vec<_>>(),
vec![STDIN_MARK.as_bytes()]
);
}
#[test]
fn stdout_selection_does_not_enable_force_or_pass_through() {
let cli = parse(&["zstd", "-c", "input"]);
assert!(cli.force_stdout);
assert!(!cli.force);
assert_eq!(cli.pass_through, None);
}
#[test]
fn short_level_can_be_combined_with_other_flags() {
let cli = parse(&["zstd", "-5q", "input"]);
assert_eq!(cli.level, 5);
assert_eq!(cli.display_level, 1);
}
#[test]
fn short_option_equals_form_is_accepted() {
let cli = parse(&["zstd", "-T=2", "-M=64M", "-B=1M", "input"]);
assert_eq!(cli.workers, Some(2));
assert_eq!(cli.mem_limit, Some(64 << 20));
assert_eq!(cli.block_size, Some(1 << 20));
}
#[test]
fn valueless_long_flags_reject_attached_values() {
let error = parse_args(vec![OsString::from("zstd"), OsString::from("--rm=0")])
.expect_err("an attached value must not activate --rm");
assert!(error.contains("does not take an argument"));
}
#[test]
fn zstdmt_uses_auto_threads() {
let cli = parse(&["zstdmt", "input"]);
assert_eq!(cli.workers, Some(0));
}
#[test]
fn alternate_format_aliases_fail_before_processing_files() {
let cli = Cli::new("gzip");
assert_eq!(cli.unsupported_program.as_deref(), Some("gzip"));
}
#[cfg(unix)]
#[test]
fn short_path_arguments_keep_non_utf8_bytes() {
let output = OsString::from_vec(vec![b'-', b'o', 0xff, b'.', b'z', b's', b't']);
let action = parse_args(vec![
OsString::from("zstd"),
output,
OsString::from("input"),
])
.expect("arguments should parse");
let Action::Run(cli) = action else {
panic!("expected a run action");
};
assert_eq!(
cli.output.as_deref().map(CStr::to_bytes),
Some(&b"\xff.zst"[..])
);
}
#[test]
fn unsupported_modes_fail_during_parsing() {
let error = parse_args(vec![OsString::from("zstd"), OsString::from("--train")])
.expect_err("training has not yet been migrated");
assert!(error.contains("not yet implemented"));
}
}
+3474
View File
@@ -0,0 +1,3474 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
//! Frame, context, and streaming decompression orchestration.
//!
//! `ZSTD_DCtx` deliberately remains C-owned. The companion C translation
//! unit projects its build-configuration-dependent leaves into
//! [`ZSTD_rustDctxView`]; this module owns the decoder state machine and
//! public ABI while never assumes offsets for the private C context.
use crate::entropy_common::FSE_readNCount;
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
#[cfg(feature = "huf-force-decompress-x1")]
use crate::huf_decompress::HUF_readDTableX1_wksp;
#[cfg(not(feature = "huf-force-decompress-x1"))]
use crate::huf_decompress::HUF_readDTableX2_wksp;
use crate::mem::{MEM_32bits, MEM_readLE16, MEM_readLE32, MEM_readLE64};
use crate::xxhash::{XXH64_digest, XXH64_reset, XXH64_state_t, XXH64_update, XXH64};
use crate::zstd_ddict::{
ZSTD_DDict, ZSTD_DDict_dictContent, ZSTD_DDict_dictSize, ZSTD_copyDDictParameters,
ZSTD_freeDDict, ZSTD_getDictID_fromDDict,
};
use std::cmp::{max, min};
use std::ffi::c_void;
use std::mem::{size_of, MaybeUninit};
use std::os::raw::{c_int, c_uint};
use std::ptr;
const ZSTD_MAGICNUMBER: u32 = 0xFD2F_B528;
const ZSTD_MAGIC_DICTIONARY: u32 = 0xEC30_A437;
const ZSTD_MAGIC_SKIPPABLE_START: u32 = 0x184D_2A50;
const ZSTD_MAGIC_SKIPPABLE_MASK: u32 = 0xFFFF_FFF0;
const ZSTD_FRAMEIDSIZE: usize = 4;
const ZSTD_SKIPPABLEHEADERSIZE: usize = 8;
const ZSTD_BLOCKHEADERSIZE: usize = 3;
const ZSTD_BLOCKSIZE_MAX: usize = 128 << 10;
const ZSTD_BLOCKSIZE_MAX_MIN: usize = 1 << 10;
const ZSTD_WINDOWLOG_ABSOLUTEMIN: usize = 10;
const ZSTD_WINDOWLOG_LIMIT_DEFAULT: usize = 27;
const ZSTD_WINDOWLOG_MAX_32: usize = 30;
const ZSTD_WINDOWLOG_MAX_64: usize = 31;
const WILDCOPY_OVERLENGTH: usize = 32;
const ZSTD_WORKSPACETOOLARGE_FACTOR: usize = 3;
const ZSTD_WORKSPACETOOLARGE_MAXDURATION: usize = 128;
const ZSTD_HUFFDTABLE_CAPACITY_LOG: usize = 12;
const HUF_DTABLE_SIZE: usize = 1 + (1 << ZSTD_HUFFDTABLE_CAPACITY_LOG);
const ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32: usize = 157;
const LL_FSE_LOG: usize = 9;
const OFF_FSE_LOG: usize = 8;
const ML_FSE_LOG: usize = 9;
const MAX_LL: usize = 35;
const MAX_ML: usize = 52;
const MAX_OFF: usize = 31;
const ZSTD_REP_NUM: usize = 3;
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_CONTENTSIZE_ERROR: u64 = u64::MAX - 1;
const ZSTD_F_ZSTD1: c_int = 0;
const ZSTD_F_ZSTD1_MAGICLESS: c_int = 1;
const ZSTD_FRAME: c_int = 0;
const ZSTD_SKIPPABLE_FRAME: c_int = 1;
const ZSTD_BM_BUFFERED: c_int = 0;
const ZSTD_BM_STABLE: c_int = 1;
const ZSTD_D_VALIDATE_CHECKSUM: c_int = 0;
const ZSTD_D_IGNORE_CHECKSUM: c_int = 1;
const ZSTD_RMD_REF_SINGLE_DDICT: c_int = 0;
const ZSTD_RMD_REF_MULTIPLE_DDICTS: c_int = 1;
const ZSTD_DLM_BY_COPY: c_int = 0;
const ZSTD_DLM_BY_REF: c_int = 1;
const ZSTD_DCT_AUTO: c_int = 0;
const ZSTD_DCT_RAW_CONTENT: c_int = 1;
const ZSTD_USE_INDEFINITELY: c_int = -1;
const ZSTD_DONT_USE: c_int = 0;
const ZSTD_USE_ONCE: c_int = 1;
const ZSTDDS_GET_FRAME_HEADER_SIZE: c_int = 0;
const ZSTDDS_DECODE_FRAME_HEADER: c_int = 1;
const ZSTDDS_DECODE_BLOCK_HEADER: c_int = 2;
const ZSTDDS_DECOMPRESS_BLOCK: c_int = 3;
const ZSTDDS_DECOMPRESS_LAST_BLOCK: c_int = 4;
const ZSTDDS_CHECK_CHECKSUM: c_int = 5;
const ZSTDDS_DECODE_SKIPPABLE_HEADER: c_int = 6;
const ZSTDDS_SKIP_FRAME: c_int = 7;
const ZDSS_INIT: c_int = 0;
const ZDSS_LOAD_HEADER: c_int = 1;
const ZDSS_READ: c_int = 2;
const ZDSS_LOAD: c_int = 3;
const ZDSS_FLUSH: c_int = 4;
const BT_RAW: c_int = 0;
const BT_RLE: c_int = 1;
const BT_COMPRESSED: c_int = 2;
const BT_RESERVED: c_int = 3;
const ZSTD_D_WINDOW_LOG_MAX: c_int = 100;
const ZSTD_D_FORMAT: c_int = 1000;
const ZSTD_D_STABLE_OUT_BUFFER: c_int = 1001;
const ZSTD_D_FORCE_IGNORE_CHECKSUM: c_int = 1002;
const ZSTD_D_REF_MULTIPLE_DDICTS: c_int = 1003;
const ZSTD_D_DISABLE_HUFFMAN_ASSEMBLY: c_int = 1004;
const ZSTD_D_MAX_BLOCK_SIZE: c_int = 1005;
const ZSTD_RESET_SESSION_ONLY: c_int = 1;
const ZSTD_RESET_PARAMETERS: c_int = 2;
const ZSTD_RESET_SESSION_AND_PARAMETERS: c_int = 3;
const ZSTD_NIT_FRAME_HEADER: c_int = 0;
const ZSTD_NIT_BLOCK_HEADER: c_int = 1;
const ZSTD_NIT_BLOCK: c_int = 2;
const ZSTD_NIT_LAST_BLOCK: c_int = 3;
const ZSTD_NIT_CHECKSUM: c_int = 4;
const ZSTD_NIT_SKIPPABLE_FRAME: c_int = 5;
const LL_BASE: [u32; MAX_LL + 1] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 40, 48, 64,
0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, 0x10000,
];
const OF_BASE: [u32; MAX_OFF + 1] = [
0, 1, 1, 5, 0xD, 0x1D, 0x3D, 0x7D, 0xFD, 0x1FD, 0x3FD, 0x7FD, 0xFFD, 0x1FFD, 0x3FFD, 0x7FFD,
0xFFFD, 0x1FFFD, 0x3FFFD, 0x7FFFD, 0xFFFFD, 0x1FFFFD, 0x3FFFFD, 0x7FFFFD, 0xFFFFFD, 0x1FFFFFD,
0x3FFFFFD, 0x7FFFFFD, 0xFFFFFFD, 0x1FFFFFFD, 0x3FFFFFFD, 0x7FFFFFFD,
];
const OF_BITS: [u8; MAX_OFF + 1] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31,
];
const ML_BASE: [u32; MAX_ML + 1] = [
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 34, 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 0x83, 0x103, 0x203,
0x403, 0x803, 0x1003, 0x2003, 0x4003, 0x8003, 0x10003,
];
const LL_BITS: [u8; MAX_LL + 1] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16,
];
const ML_BITS: [u8; MAX_ML + 1] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
];
#[repr(C)]
pub struct ZSTD_DCtx {
_private: [u8; 0],
}
pub type ZSTD_DStream = ZSTD_DCtx;
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ZSTD_FrameHeader {
pub frame_content_size: u64,
pub window_size: u64,
pub block_size_max: c_uint,
pub frame_type: c_int,
pub header_size: c_uint,
pub dict_id: c_uint,
pub checksum_flag: c_uint,
pub reserved1: c_uint,
pub reserved2: c_uint,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ZSTD_inBuffer {
pub src: *const c_void,
pub size: usize,
pub pos: usize,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ZSTD_outBuffer {
pub dst: *mut c_void,
pub size: usize,
pub pos: usize,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ZSTD_bounds {
pub error: usize,
pub lower_bound: c_int,
pub upper_bound: c_int,
}
type ZstdAllocFunction = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
type ZstdFreeFunction = unsafe extern "C" fn(*mut c_void, *mut c_void);
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ZSTD_customMem {
custom_alloc: Option<ZstdAllocFunction>,
custom_free: Option<ZstdFreeFunction>,
opaque: *mut c_void,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct BlockProperties {
block_type: c_int,
last_block: u32,
orig_size: u32,
}
#[repr(C)]
pub struct ZSTD_entropyDTables_t {
ll_table: [crate::zstd_decompress_block::ZSTD_seqSymbol; 1 + (1 << LL_FSE_LOG)],
of_table: [crate::zstd_decompress_block::ZSTD_seqSymbol; 1 + (1 << OFF_FSE_LOG)],
ml_table: [crate::zstd_decompress_block::ZSTD_seqSymbol; 1 + (1 << ML_FSE_LOG)],
huf_table: [u32; HUF_DTABLE_SIZE],
rep: [u32; ZSTD_REP_NUM],
workspace: [u32; ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32],
}
/// C-provided leaves of `ZSTD_DCtx_s`. Every pointer is produced under the
/// active C preprocessor configuration; Rust never hard-codes a private
/// decoder-context offset.
#[repr(C)]
#[derive(Clone, Copy)]
struct ZSTD_rustDctxView {
dctx: *mut c_void,
llt_ptr: *mut c_void,
mlt_ptr: *mut c_void,
oft_ptr: *mut c_void,
huf_ptr: *mut c_void,
entropy: *mut c_void,
workspace: *mut c_void,
workspace_size: usize,
previous_dst_end: *mut c_void,
prefix_start: *mut c_void,
virtual_start: *mut c_void,
dict_end: *mut c_void,
expected: *mut c_void,
f_params: *mut c_void,
processed_c_size: *mut c_void,
decoded_size: *mut c_void,
b_type: *mut c_void,
stage: *mut c_void,
lit_entropy: *mut c_void,
fse_entropy: *mut c_void,
xxh_state: *mut c_void,
header_size: *mut c_void,
format: *mut c_void,
force_ignore_checksum: *mut c_void,
validate_checksum: *mut c_void,
lit_ptr: *mut c_void,
custom_mem: *mut c_void,
lit_size: *mut c_void,
rle_size: *mut c_void,
static_size: *mut c_void,
is_frame_decompression: *mut c_void,
ddict_local: *mut c_void,
ddict: *mut c_void,
dict_id: *mut c_void,
ddict_is_cold: *mut c_void,
dict_uses: *mut c_void,
ddict_set: *mut c_void,
ref_multiple_ddicts: *mut c_void,
disable_huf_asm: *mut c_void,
max_block_size_param: *mut c_void,
stream_stage: *mut c_void,
in_buff: *mut c_void,
in_buff_size: *mut c_void,
in_pos: *mut c_void,
max_window_size: *mut c_void,
out_buff: *mut c_void,
out_buff_size: *mut c_void,
out_start: *mut c_void,
out_end: *mut c_void,
lh_size: *mut c_void,
legacy_context: *mut c_void,
previous_legacy_version: *mut c_void,
legacy_version: *mut c_void,
hostage_byte: *mut c_void,
no_forward_progress: *mut c_void,
out_buffer_mode: *mut c_void,
expected_out_buffer: *mut c_void,
lit_buffer: *mut c_void,
lit_buffer_end: *mut c_void,
lit_buffer_location: *mut c_void,
lit_extra_buffer: *mut c_void,
lit_extra_buffer_size: usize,
header_buffer: *mut c_void,
header_buffer_size: usize,
oversized_duration: *mut c_void,
fuzz_begin: *mut c_void,
fuzz_end: *mut c_void,
dctx_size: usize,
}
unsafe extern "C" {
fn ZSTD_rust_dctx_view(dctx: *mut ZSTD_DCtx, out: *mut ZSTD_rustDctxView);
fn ZSTD_rust_dctx_sizeof() -> usize;
fn ZSTD_rust_dctx_alloc(custom_mem: ZSTD_customMem) -> *mut ZSTD_DCtx;
fn ZSTD_rust_dctx_free_storage(dctx: *mut ZSTD_DCtx, custom_mem: ZSTD_customMem);
fn ZSTD_rust_dctx_init_platform(dctx: *mut ZSTD_DCtx);
fn ZSTD_rust_dctx_default_max_window_size() -> usize;
fn ZSTD_rust_no_forward_progress_max() -> c_int;
fn ZSTD_rust_heapmode() -> c_int;
fn ZSTD_rust_decompress_stack(
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
) -> usize;
fn ZSTD_rust_custom_malloc(size: usize, custom_mem: ZSTD_customMem) -> *mut c_void;
fn ZSTD_rust_custom_calloc(size: usize, custom_mem: ZSTD_customMem) -> *mut c_void;
fn ZSTD_rust_custom_free(allocation: *mut c_void, custom_mem: ZSTD_customMem);
fn ZSTD_rust_create_ddict(
dict: *const c_void,
dict_size: usize,
dict_load_method: c_int,
dict_content_type: c_int,
custom_mem: ZSTD_customMem,
) -> *mut ZSTD_DDict;
fn ZSTD_rust_dctx_trace_end(
dctx: *mut ZSTD_DCtx,
uncompressed_size: u64,
compressed_size: u64,
streaming: c_int,
);
fn ZSTD_rust_dctx_trace_begin(dctx: *mut ZSTD_DCtx);
fn ZSTD_rust_dctx_copy_prefix(dst: *mut ZSTD_DCtx, src: *const ZSTD_DCtx);
fn ZSTD_rust_legacy_is(src: *const c_void, src_size: usize) -> c_uint;
fn ZSTD_rust_legacy_get_decompressed_size(src: *const c_void, src_size: usize) -> u64;
fn ZSTD_rust_legacy_find_compressed_size(src: *const c_void, src_size: usize) -> usize;
fn ZSTD_rust_legacy_frame_size_info(
src: *const c_void,
src_size: usize,
compressed_size: *mut usize,
decompressed_bound: *mut u64,
nb_blocks: *mut usize,
) -> usize;
fn ZSTD_rust_legacy_decompress(
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
dict: *const c_void,
dict_size: usize,
) -> usize;
fn ZSTD_rust_legacy_decompress_stream(
dctx: *mut ZSTD_DCtx,
output: *mut ZSTD_outBuffer,
input: *mut ZSTD_inBuffer,
dict: *const c_void,
dict_size: usize,
) -> usize;
fn ZSTD_rust_legacy_free_stream(dctx: *mut ZSTD_DCtx);
fn ZSTD_decompressBlock_internal(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
streaming: c_int,
) -> usize;
fn ZSTD_checkContinuity(dctx: *mut ZSTD_DCtx, dst: *const c_void, dst_size: usize);
}
#[inline]
unsafe fn field<T: Copy>(slot: *mut c_void) -> T {
unsafe { slot.cast::<T>().read() }
}
#[inline]
unsafe fn set_field<T>(slot: *mut c_void, value: T) {
unsafe { slot.cast::<T>().write(value) }
}
unsafe fn dctx_view(dctx: *mut ZSTD_DCtx) -> ZSTD_rustDctxView {
let mut view = MaybeUninit::<ZSTD_rustDctxView>::zeroed();
unsafe { ZSTD_rust_dctx_view(dctx, view.as_mut_ptr()) };
unsafe { view.assume_init() }
}
#[inline]
fn frame_header_prefix(format: c_int) -> usize {
if format == ZSTD_F_ZSTD1 {
5
} else {
1
}
}
#[inline]
fn frame_header_min(format: c_int) -> usize {
if format == ZSTD_F_ZSTD1 {
6
} else {
2
}
}
#[inline]
fn window_log_max() -> usize {
if MEM_32bits() {
ZSTD_WINDOWLOG_MAX_32
} else {
ZSTD_WINDOWLOG_MAX_64
}
}
#[inline]
unsafe fn const_ptr_add(ptr: *const u8, amount: usize) -> *const u8 {
if ptr.is_null() {
debug_assert_eq!(amount, 0);
ptr
} else {
unsafe { ptr.add(amount) }
}
}
#[inline]
unsafe fn ptr_distance(end: *const u8, start: *const u8) -> usize {
(end as usize).wrapping_sub(start as usize)
}
#[inline]
unsafe fn copy_bytes(dst: *mut u8, src: *const u8, len: usize) {
if len != 0 {
unsafe { ptr::copy(src, dst, len) };
}
}
#[inline]
unsafe fn limit_copy(dst: *mut u8, dst_capacity: usize, src: *const u8, src_size: usize) -> usize {
let len = min(dst_capacity, src_size);
if len != 0 {
unsafe { ptr::copy_nonoverlapping(src, dst, len) };
}
len
}
#[inline]
fn default_custom_mem() -> ZSTD_customMem {
ZSTD_customMem {
custom_alloc: None,
custom_free: None,
opaque: ptr::null_mut(),
}
}
#[inline]
fn custom_mem_valid(custom_mem: ZSTD_customMem) -> bool {
custom_mem.custom_alloc.is_some() == custom_mem.custom_free.is_some()
}
#[inline]
unsafe fn get_frame_header_ptr(view: &ZSTD_rustDctxView) -> *mut ZSTD_FrameHeader {
view.f_params.cast()
}
#[inline]
unsafe fn entropy_ptr(view: &ZSTD_rustDctxView) -> *mut ZSTD_entropyDTables_t {
view.entropy.cast()
}
#[inline]
unsafe fn dctx_custom_mem(view: &ZSTD_rustDctxView) -> ZSTD_customMem {
unsafe { field(view.custom_mem) }
}
#[inline]
unsafe fn dctx_ddict(view: &ZSTD_rustDctxView) -> *const ZSTD_DDict {
unsafe { field(view.ddict) }
}
#[inline]
unsafe fn set_dctx_ddict(view: &ZSTD_rustDctxView, ddict: *const ZSTD_DDict) {
unsafe { set_field(view.ddict, ddict) }
}
#[inline]
unsafe fn dctx_ddict_local(view: &ZSTD_rustDctxView) -> *mut ZSTD_DDict {
unsafe { field(view.ddict_local) }
}
#[inline]
unsafe fn set_dctx_ddict_local(view: &ZSTD_rustDctxView, ddict: *mut ZSTD_DDict) {
unsafe { set_field(view.ddict_local, ddict) }
}
#[inline]
unsafe fn get_pointer(slot: *mut c_void) -> *const u8 {
unsafe { field(slot) }
}
#[inline]
unsafe fn set_pointer(slot: *mut c_void, value: *const u8) {
unsafe { set_field(slot, value) }
}
#[inline]
unsafe fn get_mut_pointer(slot: *mut c_void) -> *mut u8 {
unsafe { field(slot) }
}
#[inline]
unsafe fn set_mut_pointer(slot: *mut c_void, value: *mut u8) {
unsafe { set_field(slot, value) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_isFrame(buffer: *const c_void, size: usize) -> c_uint {
if size < ZSTD_FRAMEIDSIZE || buffer.is_null() {
return 0;
}
let magic = unsafe { MEM_readLE32(buffer) };
if magic == ZSTD_MAGICNUMBER
|| (magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START
{
return 1;
}
unsafe { ZSTD_rust_legacy_is(buffer, size) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_isSkippableFrame(buffer: *const c_void, size: usize) -> c_uint {
if size < ZSTD_FRAMEIDSIZE || buffer.is_null() {
return 0;
}
u32::from(
unsafe { MEM_readLE32(buffer) } & ZSTD_MAGIC_SKIPPABLE_MASK == ZSTD_MAGIC_SKIPPABLE_START,
)
}
unsafe fn frame_header_size_internal(src: *const u8, src_size: usize, format: c_int) -> usize {
let min_input_size = frame_header_prefix(format);
if src_size < min_input_size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let fhd = unsafe { *src.add(min_input_size - 1) };
let dict_id = fhd & 3;
let single_segment = (fhd >> 5) & 1;
let fcs_id = fhd >> 6;
let did_size = [0usize, 1, 2, 4][dict_id as usize];
let fcs_size = [0usize, 2, 4, 8][fcs_id as usize];
min_input_size
+ usize::from(single_segment == 0)
+ did_size
+ fcs_size
+ usize::from(single_segment != 0 && fcs_id == 0)
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_frameHeaderSize(src: *const c_void, src_size: usize) -> usize {
unsafe { frame_header_size_internal(src.cast(), src_size, ZSTD_F_ZSTD1) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_getFrameHeader_advanced(
zfh: *mut ZSTD_FrameHeader,
src: *const c_void,
src_size: usize,
format: c_int,
) -> usize {
let min_input_size = frame_header_prefix(format);
if src_size != 0 && src.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
if src_size < min_input_size {
if src_size != 0 && format != ZSTD_F_ZSTD1_MAGICLESS {
let mut header = ZSTD_MAGICNUMBER.to_le_bytes();
unsafe {
ptr::copy_nonoverlapping(src.cast::<u8>(), header.as_mut_ptr(), min(4, src_size))
};
if u32::from_le_bytes(header) != ZSTD_MAGICNUMBER {
header = ZSTD_MAGIC_SKIPPABLE_START.to_le_bytes();
unsafe {
ptr::copy_nonoverlapping(
src.cast::<u8>(),
header.as_mut_ptr(),
min(4, src_size),
)
};
if u32::from_le_bytes(header) & ZSTD_MAGIC_SKIPPABLE_MASK
!= ZSTD_MAGIC_SKIPPABLE_START
{
return ERROR(ZstdErrorCode::PrefixUnknown);
}
}
}
return min_input_size;
}
if zfh.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe { zfh.write(ZSTD_FrameHeader::default()) };
let ip = src.cast::<u8>();
if format != ZSTD_F_ZSTD1_MAGICLESS && unsafe { MEM_readLE32(src) } != ZSTD_MAGICNUMBER {
let magic = unsafe { MEM_readLE32(src) };
if magic & ZSTD_MAGIC_SKIPPABLE_MASK == ZSTD_MAGIC_SKIPPABLE_START {
if src_size < ZSTD_SKIPPABLEHEADERSIZE {
return ZSTD_SKIPPABLEHEADERSIZE;
}
unsafe {
(*zfh).frame_type = ZSTD_SKIPPABLE_FRAME;
(*zfh).dict_id = magic - ZSTD_MAGIC_SKIPPABLE_START;
(*zfh).header_size = ZSTD_SKIPPABLEHEADERSIZE as c_uint;
(*zfh).frame_content_size = MEM_readLE32(ip.add(ZSTD_FRAMEIDSIZE).cast()) as u64;
}
return 0;
}
return ERROR(ZstdErrorCode::PrefixUnknown);
}
let fh_size = unsafe { frame_header_size_internal(ip, src_size, format) };
if ERR_isError(fh_size) {
return fh_size;
}
if src_size < fh_size {
return fh_size;
}
unsafe { (*zfh).header_size = fh_size as c_uint };
let fhd = unsafe { *ip.add(min_input_size - 1) };
if fhd & 0x08 != 0 {
return ERROR(ZstdErrorCode::FrameParameterUnsupported);
}
let dict_id_size_code = fhd & 3;
let checksum_flag = (fhd >> 2) & 1;
let single_segment = (fhd >> 5) & 1;
let fcs_id = fhd >> 6;
let mut pos = min_input_size;
let mut window_size = 0u64;
let mut dict_id = 0u32;
let mut frame_content_size = ZSTD_CONTENTSIZE_UNKNOWN;
if single_segment == 0 {
let wl = unsafe { *ip.add(pos) };
pos += 1;
let window_log = usize::from(wl >> 3) + ZSTD_WINDOWLOG_ABSOLUTEMIN;
if window_log > window_log_max() {
return ERROR(ZstdErrorCode::FrameParameterWindowTooLarge);
}
window_size = 1u64 << window_log;
window_size = window_size.wrapping_add((window_size >> 3) * u64::from(wl & 7));
}
match dict_id_size_code {
0 => {}
1 => {
dict_id = unsafe { *ip.add(pos) } as u32;
pos += 1;
}
2 => {
dict_id = unsafe { MEM_readLE16(ip.add(pos).cast()) as u32 };
pos += 2;
}
_ => {
dict_id = unsafe { MEM_readLE32(ip.add(pos).cast()) };
pos += 4;
}
}
match fcs_id {
0 => {
if single_segment != 0 {
frame_content_size = unsafe { *ip.add(pos) } as u64;
}
}
1 => frame_content_size = unsafe { MEM_readLE16(ip.add(pos).cast()) as u64 + 256 },
2 => frame_content_size = unsafe { MEM_readLE32(ip.add(pos).cast()) as u64 },
_ => frame_content_size = unsafe { MEM_readLE64(ip.add(pos).cast()) },
}
if single_segment != 0 {
window_size = frame_content_size;
}
unsafe {
(*zfh).frame_type = ZSTD_FRAME;
(*zfh).frame_content_size = frame_content_size;
(*zfh).window_size = window_size;
(*zfh).block_size_max = min(window_size, ZSTD_BLOCKSIZE_MAX as u64) as c_uint;
(*zfh).dict_id = dict_id;
(*zfh).checksum_flag = checksum_flag as c_uint;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_getFrameHeader(
zfh: *mut ZSTD_FrameHeader,
src: *const c_void,
src_size: usize,
) -> usize {
unsafe { ZSTD_getFrameHeader_advanced(zfh, src, src_size, ZSTD_F_ZSTD1) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_getFrameContentSize(src: *const c_void, src_size: usize) -> u64 {
if unsafe { ZSTD_rust_legacy_is(src, src_size) } != 0 {
let size = unsafe { ZSTD_rust_legacy_get_decompressed_size(src, src_size) };
return if size == 0 {
ZSTD_CONTENTSIZE_UNKNOWN
} else {
size
};
}
let mut zfh = ZSTD_FrameHeader::default();
if unsafe { ZSTD_getFrameHeader(&mut zfh, src, src_size) } != 0 {
return ZSTD_CONTENTSIZE_ERROR;
}
if zfh.frame_type == ZSTD_SKIPPABLE_FRAME {
0
} else {
zfh.frame_content_size
}
}
unsafe fn read_skippable_frame_size(src: *const u8, src_size: usize) -> usize {
if src_size < ZSTD_SKIPPABLEHEADERSIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let size = unsafe { MEM_readLE32(src.add(ZSTD_FRAMEIDSIZE).cast()) };
if size.wrapping_add(ZSTD_SKIPPABLEHEADERSIZE as u32) < size {
return ERROR(ZstdErrorCode::FrameParameterUnsupported);
}
let frame_size = ZSTD_SKIPPABLEHEADERSIZE + size as usize;
if frame_size > src_size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
frame_size
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_readSkippableFrame(
dst: *mut c_void,
dst_capacity: usize,
magic_variant: *mut c_uint,
src: *const c_void,
src_size: usize,
) -> usize {
if src_size < ZSTD_SKIPPABLEHEADERSIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let src_u8 = src.cast::<u8>();
let magic = unsafe { MEM_readLE32(src) };
let frame_size = unsafe { read_skippable_frame_size(src_u8, src_size) };
if ERR_isError(frame_size) {
return frame_size;
}
let content_size = frame_size - ZSTD_SKIPPABLEHEADERSIZE;
if unsafe { ZSTD_isSkippableFrame(src, src_size) } == 0 {
return ERROR(ZstdErrorCode::FrameParameterUnsupported);
}
if content_size > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if content_size != 0 && !dst.is_null() {
unsafe {
copy_bytes(
dst.cast(),
src_u8.add(ZSTD_SKIPPABLEHEADERSIZE),
content_size,
)
};
}
if !magic_variant.is_null() {
unsafe { magic_variant.write(magic - ZSTD_MAGIC_SKIPPABLE_START) };
}
content_size
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct FrameSizeInfo {
nb_blocks: usize,
compressed_size: usize,
decompressed_bound: u64,
}
#[inline]
fn error_frame_size_info(error: usize) -> FrameSizeInfo {
FrameSizeInfo {
nb_blocks: 0,
compressed_size: error,
decompressed_bound: ZSTD_CONTENTSIZE_ERROR,
}
}
unsafe fn find_frame_size_info(src: *const u8, src_size: usize, format: c_int) -> FrameSizeInfo {
if format == ZSTD_F_ZSTD1 && unsafe { ZSTD_rust_legacy_is(src.cast(), src_size) } != 0 {
let mut info = FrameSizeInfo::default();
let status = unsafe {
ZSTD_rust_legacy_frame_size_info(
src.cast(),
src_size,
&mut info.compressed_size,
&mut info.decompressed_bound,
&mut info.nb_blocks,
)
};
return if ERR_isError(status) {
error_frame_size_info(status)
} else {
info
};
}
if format == ZSTD_F_ZSTD1
&& src_size >= ZSTD_SKIPPABLEHEADERSIZE
&& unsafe { MEM_readLE32(src.cast()) } & ZSTD_MAGIC_SKIPPABLE_MASK
== ZSTD_MAGIC_SKIPPABLE_START
{
return FrameSizeInfo {
nb_blocks: 0,
compressed_size: unsafe { read_skippable_frame_size(src, src_size) },
decompressed_bound: 0,
};
}
let mut zfh = ZSTD_FrameHeader::default();
let header_result =
unsafe { ZSTD_getFrameHeader_advanced(&mut zfh, src.cast(), src_size, format) };
if ERR_isError(header_result) {
return error_frame_size_info(header_result);
}
if header_result != 0 {
return error_frame_size_info(ERROR(ZstdErrorCode::SrcSizeWrong));
}
let mut ip = unsafe { src.add(zfh.header_size as usize) };
let mut remaining = src_size - zfh.header_size as usize;
let mut nb_blocks = 0usize;
loop {
let mut block = BlockProperties::default();
let c_block_size = unsafe {
crate::zstd_decompress_block::ZSTD_getcBlockSize(
ip.cast(),
remaining,
(&mut block as *mut BlockProperties).cast(),
)
};
if ERR_isError(c_block_size) {
return error_frame_size_info(c_block_size);
}
let total_size = match ZSTD_BLOCKHEADERSIZE.checked_add(c_block_size) {
Some(size) => size,
None => return error_frame_size_info(ERROR(ZstdErrorCode::SrcSizeWrong)),
};
if total_size > remaining {
return error_frame_size_info(ERROR(ZstdErrorCode::SrcSizeWrong));
}
ip = unsafe { ip.add(total_size) };
remaining -= total_size;
nb_blocks += 1;
if block.last_block != 0 {
break;
}
}
if zfh.checksum_flag != 0 {
if remaining < 4 {
return error_frame_size_info(ERROR(ZstdErrorCode::SrcSizeWrong));
}
ip = unsafe { ip.add(4) };
}
FrameSizeInfo {
nb_blocks,
compressed_size: unsafe { ip.offset_from(src) as usize },
decompressed_bound: if zfh.frame_content_size != ZSTD_CONTENTSIZE_UNKNOWN {
zfh.frame_content_size
} else {
(nb_blocks as u64).wrapping_mul(zfh.block_size_max as u64)
},
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_findFrameCompressedSize(
src: *const c_void,
src_size: usize,
) -> usize {
unsafe { find_frame_size_info(src.cast(), src_size, ZSTD_F_ZSTD1).compressed_size }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_findDecompressedSize(src: *const c_void, mut src_size: usize) -> u64 {
let mut input = src.cast::<u8>();
let mut total = 0u64;
while src_size >= frame_header_prefix(ZSTD_F_ZSTD1) {
if unsafe { MEM_readLE32(input.cast()) } & ZSTD_MAGIC_SKIPPABLE_MASK
== ZSTD_MAGIC_SKIPPABLE_START
{
let size = unsafe { read_skippable_frame_size(input, src_size) };
if ERR_isError(size) {
return ZSTD_CONTENTSIZE_ERROR;
}
input = unsafe { input.add(size) };
src_size -= size;
continue;
}
let frame_size = unsafe { ZSTD_getFrameContentSize(input.cast(), src_size) };
if frame_size >= ZSTD_CONTENTSIZE_ERROR {
return frame_size;
}
let next = total.wrapping_add(frame_size);
if next < total {
return ZSTD_CONTENTSIZE_ERROR;
}
total = next;
let compressed = unsafe { ZSTD_findFrameCompressedSize(input.cast(), src_size) };
if ERR_isError(compressed) || compressed > src_size {
return ZSTD_CONTENTSIZE_ERROR;
}
input = unsafe { input.add(compressed) };
src_size -= compressed;
}
if src_size != 0 {
ZSTD_CONTENTSIZE_ERROR
} else {
total
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_getDecompressedSize(src: *const c_void, src_size: usize) -> u64 {
let result = unsafe { ZSTD_getFrameContentSize(src, src_size) };
if result >= ZSTD_CONTENTSIZE_ERROR {
0
} else {
result
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressBound(src: *const c_void, mut src_size: usize) -> u64 {
let mut input = src.cast::<u8>();
let mut bound = 0u64;
while src_size != 0 {
let info = unsafe { find_frame_size_info(input, src_size, ZSTD_F_ZSTD1) };
if ERR_isError(info.compressed_size) || info.decompressed_bound == ZSTD_CONTENTSIZE_ERROR {
return ZSTD_CONTENTSIZE_ERROR;
}
if info.compressed_size > src_size {
return ZSTD_CONTENTSIZE_ERROR;
}
bound = bound.wrapping_add(info.decompressed_bound);
input = unsafe { input.add(info.compressed_size) };
src_size -= info.compressed_size;
}
bound
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressionMargin(
src: *const c_void,
mut src_size: usize,
) -> usize {
let mut input = src.cast::<u8>();
let mut margin = 0usize;
let mut max_block_size = 0usize;
while src_size != 0 {
let info = unsafe { find_frame_size_info(input, src_size, ZSTD_F_ZSTD1) };
let mut zfh = ZSTD_FrameHeader::default();
let header = unsafe { ZSTD_getFrameHeader(&mut zfh, input.cast(), src_size) };
if ERR_isError(header) {
return header;
}
if header != 0
|| ERR_isError(info.compressed_size)
|| info.decompressed_bound == ZSTD_CONTENTSIZE_ERROR
{
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if zfh.frame_type == ZSTD_FRAME {
margin = margin
.wrapping_add(zfh.header_size as usize)
.wrapping_add(if zfh.checksum_flag != 0 { 4 } else { 0 })
.wrapping_add(ZSTD_BLOCKHEADERSIZE.wrapping_mul(info.nb_blocks));
max_block_size = max(max_block_size, zfh.block_size_max as usize);
} else {
margin = margin.wrapping_add(info.compressed_size);
}
if info.compressed_size > src_size {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
input = unsafe { input.add(info.compressed_size) };
src_size -= info.compressed_size;
}
margin.wrapping_add(max_block_size)
}
unsafe fn ref_dict_content(view: &ZSTD_rustDctxView, dict: *const u8, dict_size: usize) -> usize {
let previous = unsafe { get_pointer(view.previous_dst_end) };
let prefix = unsafe { get_pointer(view.prefix_start) };
unsafe {
set_pointer(view.dict_end, previous);
/* Do the same address arithmetic as the C implementation without
* forming a Rust pointer outside of an allocation. These are virtual
* history addresses and are only compared/subtracted by the block
* decoder, never dereferenced until they again name live history. */
let history = (previous as usize).wrapping_sub(prefix as usize);
set_pointer(
view.virtual_start,
(dict as usize).wrapping_sub(history) as *const u8,
);
set_pointer(view.prefix_start, dict);
set_pointer(view.previous_dst_end, const_ptr_add(dict, dict_size));
if !view.fuzz_begin.is_null() {
set_pointer(view.fuzz_begin, dict);
set_pointer(view.fuzz_end, const_ptr_add(dict, dict_size));
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_loadDEntropy(
entropy: *mut ZSTD_entropyDTables_t,
dict: *const c_void,
dict_size: usize,
) -> usize {
if dict_size <= 8 || dict.is_null() {
return ERROR(ZstdErrorCode::DictionaryCorrupted);
}
let mut dict_ptr = unsafe { dict.cast::<u8>().add(8) };
let dict_end = unsafe { dict.cast::<u8>().add(dict_size) };
let workspace = entropy.cast::<c_void>();
let workspace_size = size_of::<crate::zstd_decompress_block::ZSTD_seqSymbol>()
* ((1 << LL_FSE_LOG) + (1 << OFF_FSE_LOG) + (1 << ML_FSE_LOG) + 3);
let huf_size = unsafe {
#[cfg(feature = "huf-force-decompress-x1")]
{
HUF_readDTableX1_wksp(
(*entropy).huf_table.as_mut_ptr(),
dict_ptr.cast(),
ptr_distance(dict_end, dict_ptr),
workspace,
workspace_size,
0,
)
}
#[cfg(not(feature = "huf-force-decompress-x1"))]
{
HUF_readDTableX2_wksp(
(*entropy).huf_table.as_mut_ptr(),
dict_ptr.cast(),
ptr_distance(dict_end, dict_ptr),
workspace,
workspace_size,
0,
)
}
};
if ERR_isError(huf_size) {
return ERROR(ZstdErrorCode::DictionaryCorrupted);
}
dict_ptr = unsafe { dict_ptr.add(huf_size) };
unsafe fn load_table(
table: *mut crate::zstd_decompress_block::ZSTD_seqSymbol,
max_symbol: usize,
max_log: usize,
base: *const u32,
bits: *const u8,
workspace: *mut u32,
dict_ptr: *const u8,
dict_end: *const u8,
) -> Result<usize, usize> {
let mut norm = [0i16; MAX_ML + 1];
let mut max = max_symbol as c_uint;
let mut log = 0u32;
let size = unsafe {
FSE_readNCount(
norm.as_mut_ptr(),
&mut max,
&mut log,
dict_ptr.cast(),
ptr_distance(dict_end, dict_ptr),
)
};
if ERR_isError(size) || max as usize > max_symbol || log as usize > max_log {
return Err(ERROR(ZstdErrorCode::DictionaryCorrupted));
}
unsafe {
crate::zstd_decompress_block::ZSTD_buildFSETable(
table,
norm.as_ptr(),
max,
base,
bits,
log,
workspace.cast(),
ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32 * size_of::<u32>(),
0,
);
}
Ok(size)
}
let entropy_ref = unsafe { &mut *entropy };
let off_size = match unsafe {
load_table(
entropy_ref.of_table.as_mut_ptr(),
MAX_OFF,
OFF_FSE_LOG,
OF_BASE.as_ptr(),
OF_BITS.as_ptr(),
entropy_ref.workspace.as_mut_ptr(),
dict_ptr,
dict_end,
)
} {
Ok(size) => size,
Err(error) => return error,
};
dict_ptr = unsafe { dict_ptr.add(off_size) };
let ml_size = match unsafe {
load_table(
entropy_ref.ml_table.as_mut_ptr(),
MAX_ML,
ML_FSE_LOG,
ML_BASE.as_ptr(),
ML_BITS.as_ptr(),
entropy_ref.workspace.as_mut_ptr(),
dict_ptr,
dict_end,
)
} {
Ok(size) => size,
Err(error) => return error,
};
dict_ptr = unsafe { dict_ptr.add(ml_size) };
let ll_size = match unsafe {
load_table(
entropy_ref.ll_table.as_mut_ptr(),
MAX_LL,
LL_FSE_LOG,
LL_BASE.as_ptr(),
LL_BITS.as_ptr(),
entropy_ref.workspace.as_mut_ptr(),
dict_ptr,
dict_end,
)
} {
Ok(size) => size,
Err(error) => return error,
};
dict_ptr = unsafe { dict_ptr.add(ll_size) };
if unsafe { ptr_distance(dict_end, dict_ptr) } < 12 {
return ERROR(ZstdErrorCode::DictionaryCorrupted);
}
let content_size = unsafe { ptr_distance(dict_end, dict_ptr.add(12)) };
for rep in &mut entropy_ref.rep {
let value = unsafe { MEM_readLE32(dict_ptr.cast()) };
dict_ptr = unsafe { dict_ptr.add(4) };
if value == 0 || value as usize > content_size {
return ERROR(ZstdErrorCode::DictionaryCorrupted);
}
*rep = value;
}
unsafe { dict_ptr.offset_from(dict.cast()) as usize }
}
#[repr(C)]
struct DDictHashSet {
table: *mut *const ZSTD_DDict,
size: usize,
count: usize,
}
unsafe fn ddict_hash_index(set: *const DDictHashSet, dict_id: u32) -> usize {
let hash = unsafe { XXH64((&dict_id as *const u32).cast(), size_of::<u32>(), 0) };
hash as usize & (unsafe { (*set).size } - 1)
}
unsafe fn ddict_hashset_create(custom_mem: ZSTD_customMem) -> *mut DDictHashSet {
let set = unsafe { ZSTD_rust_custom_malloc(size_of::<DDictHashSet>(), custom_mem) }
.cast::<DDictHashSet>();
if set.is_null() {
return ptr::null_mut();
}
let table = unsafe { ZSTD_rust_custom_calloc(64 * size_of::<*const ZSTD_DDict>(), custom_mem) }
.cast::<*const ZSTD_DDict>();
if table.is_null() {
unsafe { ZSTD_rust_custom_free(set.cast(), custom_mem) };
return ptr::null_mut();
}
unsafe {
set.write(DDictHashSet {
table,
size: 64,
count: 0,
});
}
set
}
unsafe fn ddict_hashset_free(set: *mut DDictHashSet, custom_mem: ZSTD_customMem) {
if set.is_null() {
return;
}
unsafe {
ZSTD_rust_custom_free((*set).table.cast(), custom_mem);
ZSTD_rust_custom_free(set.cast(), custom_mem);
}
}
unsafe fn ddict_hashset_emplace(set: *mut DDictHashSet, ddict: *const ZSTD_DDict) -> usize {
let dict_id = unsafe { ZSTD_getDictID_fromDDict(ddict) };
let mut index = unsafe { ddict_hash_index(set, dict_id) };
let mask = unsafe { (*set).size } - 1;
if unsafe { (*set).count == (*set).size } {
return ERROR(ZstdErrorCode::Generic);
}
while !unsafe { *(*set).table.add(index) }.is_null() {
if unsafe { ZSTD_getDictID_fromDDict(*(*set).table.add(index)) } == dict_id {
unsafe { (*set).table.add(index).write(ddict) };
return 0;
}
index = (index + 1) & mask;
}
unsafe {
(*set).table.add(index).write(ddict);
(*set).count += 1;
}
0
}
unsafe fn ddict_hashset_expand(set: *mut DDictHashSet, custom_mem: ZSTD_customMem) -> usize {
let old_table = unsafe { (*set).table };
let old_size = unsafe { (*set).size };
let new_size = match old_size.checked_mul(2) {
Some(size) => size,
None => return ERROR(ZstdErrorCode::MemoryAllocation),
};
let new_table =
unsafe { ZSTD_rust_custom_calloc(new_size * size_of::<*const ZSTD_DDict>(), custom_mem) }
.cast::<*const ZSTD_DDict>();
if new_table.is_null() {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
unsafe {
(*set).table = new_table;
(*set).size = new_size;
(*set).count = 0;
for index in 0..old_size {
let ddict = *old_table.add(index);
if !ddict.is_null() {
let result = ddict_hashset_emplace(set, ddict);
if ERR_isError(result) {
return result;
}
}
}
ZSTD_rust_custom_free(old_table.cast(), custom_mem);
}
0
}
unsafe fn ddict_hashset_add(
set: *mut DDictHashSet,
ddict: *const ZSTD_DDict,
custom_mem: ZSTD_customMem,
) -> usize {
let should_expand = unsafe { (*set).count }
.wrapping_mul(4)
.wrapping_div(unsafe { (*set).size })
.wrapping_mul(3)
!= 0;
if should_expand {
let result = unsafe { ddict_hashset_expand(set, custom_mem) };
if ERR_isError(result) {
return result;
}
}
unsafe { ddict_hashset_emplace(set, ddict) }
}
unsafe fn ddict_hashset_get(set: *const DDictHashSet, dict_id: u32) -> *const ZSTD_DDict {
let mut index = unsafe { ddict_hash_index(set, dict_id) };
let mask = unsafe { (*set).size } - 1;
loop {
let ddict = unsafe { *(*set).table.add(index) };
let current_id = unsafe { ZSTD_getDictID_fromDDict(ddict) };
if current_id == dict_id || current_id == 0 {
return ddict;
}
index = (index + 1) & mask;
}
}
unsafe fn reset_parameters(view: &ZSTD_rustDctxView) {
unsafe {
set_field(view.format, ZSTD_F_ZSTD1);
set_field(
view.max_window_size,
ZSTD_rust_dctx_default_max_window_size(),
);
set_field(view.out_buffer_mode, ZSTD_BM_BUFFERED);
set_field(view.force_ignore_checksum, ZSTD_D_VALIDATE_CHECKSUM);
set_field(view.ref_multiple_ddicts, ZSTD_RMD_REF_SINGLE_DDICT);
set_field(view.disable_huf_asm, 0 as c_int);
set_field(view.max_block_size_param, 0 as c_int);
}
}
unsafe fn init_dctx_internal(view: &ZSTD_rustDctxView) {
unsafe {
set_field(view.static_size, 0usize);
set_dctx_ddict(view, ptr::null());
set_dctx_ddict_local(view, ptr::null_mut());
set_pointer(view.dict_end, ptr::null());
set_field(view.ddict_is_cold, 0 as c_int);
set_field(view.dict_uses, ZSTD_DONT_USE);
set_mut_pointer(view.in_buff, ptr::null_mut());
set_field(view.in_buff_size, 0usize);
set_field(view.out_buff_size, 0usize);
set_field(view.stream_stage, ZDSS_INIT);
if !view.legacy_context.is_null() {
set_field(view.legacy_context, ptr::null_mut::<c_void>());
set_field(view.previous_legacy_version, 0u32);
set_field(view.legacy_version, 0u32);
}
set_field(view.no_forward_progress, 0 as c_int);
set_field(view.oversized_duration, 0usize);
set_field(view.is_frame_decompression, 1 as c_int);
set_field(view.ddict_set, ptr::null_mut::<DDictHashSet>());
reset_parameters(view);
if !view.fuzz_end.is_null() {
set_pointer(view.fuzz_end, ptr::null());
}
ZSTD_rust_dctx_init_platform(view.dctx.cast());
}
}
unsafe fn clear_dict(view: &ZSTD_rustDctxView) {
unsafe {
let local = dctx_ddict_local(view);
if !local.is_null() {
ZSTD_freeDDict(local);
}
set_dctx_ddict_local(view, ptr::null_mut());
set_dctx_ddict(view, ptr::null());
set_field(view.dict_uses, ZSTD_DONT_USE);
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_sizeof_DCtx(dctx: *const ZSTD_DCtx) -> usize {
if dctx.is_null() {
return 0;
}
let view = unsafe { dctx_view(dctx.cast_mut()) };
let local = unsafe { dctx_ddict_local(&view) };
let ddict_size = if local.is_null() {
0
} else {
unsafe { crate::zstd_ddict::ZSTD_sizeof_DDict(local) }
};
view.dctx_size
.wrapping_add(ddict_size)
.wrapping_add(unsafe { field::<usize>(view.in_buff_size) })
.wrapping_add(unsafe { field::<usize>(view.out_buff_size) })
}
#[no_mangle]
pub extern "C" fn ZSTD_estimateDCtxSize() -> usize {
unsafe { ZSTD_rust_dctx_sizeof() }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_initStaticDCtx(
workspace: *mut c_void,
workspace_size: usize,
) -> *mut ZSTD_DCtx {
let dctx_size = unsafe { ZSTD_rust_dctx_sizeof() };
if workspace.is_null() || (workspace as usize & 7) != 0 || workspace_size < dctx_size {
return ptr::null_mut();
}
let dctx = workspace.cast::<ZSTD_DCtx>();
let view = unsafe { dctx_view(dctx) };
unsafe {
set_field(view.custom_mem, default_custom_mem());
init_dctx_internal(&view);
set_field(view.static_size, workspace_size);
set_mut_pointer(view.in_buff, workspace.cast::<u8>().add(dctx_size));
}
dctx
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_createDCtx_advanced(custom_mem: ZSTD_customMem) -> *mut ZSTD_DCtx {
if !custom_mem_valid(custom_mem) {
return ptr::null_mut();
}
let dctx = unsafe { ZSTD_rust_dctx_alloc(custom_mem) };
if dctx.is_null() {
return ptr::null_mut();
}
let view = unsafe { dctx_view(dctx) };
unsafe {
set_field(view.custom_mem, custom_mem);
init_dctx_internal(&view);
}
dctx
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_createDCtx() -> *mut ZSTD_DCtx {
unsafe { ZSTD_createDCtx_advanced(default_custom_mem()) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_freeDCtx(dctx: *mut ZSTD_DCtx) -> usize {
if dctx.is_null() {
return 0;
}
let view = unsafe { dctx_view(dctx) };
if unsafe { field::<usize>(view.static_size) } != 0 {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
let custom_mem = unsafe { dctx_custom_mem(&view) };
unsafe {
clear_dict(&view);
let in_buff = get_mut_pointer(view.in_buff);
ZSTD_rust_custom_free(in_buff.cast(), custom_mem);
set_mut_pointer(view.in_buff, ptr::null_mut());
let set: *mut DDictHashSet = field(view.ddict_set);
ddict_hashset_free(set, custom_mem);
set_field(view.ddict_set, ptr::null_mut::<DDictHashSet>());
if !view.legacy_context.is_null() {
ZSTD_rust_legacy_free_stream(dctx);
}
ZSTD_rust_dctx_free_storage(dctx, custom_mem);
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_copyDCtx(dst: *mut ZSTD_DCtx, src: *const ZSTD_DCtx) {
unsafe { ZSTD_rust_dctx_copy_prefix(dst, src) }
}
unsafe fn select_frame_ddict(view: &ZSTD_rustDctxView) {
let set: *mut DDictHashSet = unsafe { field(view.ddict_set) };
if set.is_null() || unsafe { dctx_ddict(view) }.is_null() {
return;
}
let dict_id = unsafe { (*get_frame_header_ptr(view)).dict_id };
let ddict = unsafe { ddict_hashset_get(set, dict_id) };
if !ddict.is_null() {
unsafe {
clear_dict(view);
set_field(view.dict_id, dict_id);
set_dctx_ddict(view, ddict);
set_field(view.dict_uses, ZSTD_USE_INDEFINITELY);
}
}
}
unsafe fn decode_frame_header(
view: &ZSTD_rustDctxView,
src: *const u8,
header_size: usize,
) -> usize {
let format = unsafe { field::<c_int>(view.format) };
let result = unsafe {
ZSTD_getFrameHeader_advanced(get_frame_header_ptr(view), src.cast(), header_size, format)
};
if ERR_isError(result) {
return result;
}
if result != 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if unsafe { field::<c_int>(view.ref_multiple_ddicts) } == ZSTD_RMD_REF_MULTIPLE_DDICTS
&& !unsafe { field::<*mut DDictHashSet>(view.ddict_set) }.is_null()
{
unsafe { select_frame_ddict(view) };
}
if view.fuzz_begin.is_null()
&& unsafe { (*get_frame_header_ptr(view)).dict_id } != 0
&& unsafe { field::<u32>(view.dict_id) } != unsafe { (*get_frame_header_ptr(view)).dict_id }
{
return ERROR(ZstdErrorCode::DictionaryWrong);
}
let validate = u32::from(
unsafe { (*get_frame_header_ptr(view)).checksum_flag } != 0
&& unsafe { field::<c_int>(view.force_ignore_checksum) } == ZSTD_D_VALIDATE_CHECKSUM,
);
unsafe {
set_field(view.validate_checksum, validate);
if validate != 0 {
let _ = XXH64_reset(view.xxh_state.cast::<XXH64_state_t>(), 0);
}
let processed = field::<u64>(view.processed_c_size).wrapping_add(header_size as u64);
set_field(view.processed_c_size, processed);
}
0
}
unsafe fn decompress_begin(view: &ZSTD_rustDctxView) -> usize {
unsafe {
ZSTD_rust_dctx_trace_begin(view.dctx.cast());
let format = field::<c_int>(view.format);
set_field(view.expected, frame_header_prefix(format));
set_field(view.stage, ZSTDDS_GET_FRAME_HEADER_SIZE);
set_field(view.processed_c_size, 0u64);
set_field(view.decoded_size, 0u64);
set_pointer(view.previous_dst_end, ptr::null());
set_pointer(view.prefix_start, ptr::null());
set_pointer(view.virtual_start, ptr::null());
set_pointer(view.dict_end, ptr::null());
let entropy = &mut *entropy_ptr(view);
entropy.huf_table[0] = (ZSTD_HUFFDTABLE_CAPACITY_LOG as u32).wrapping_mul(0x0100_0001);
set_field(view.lit_entropy, 0u32);
set_field(view.fse_entropy, 0u32);
set_field(view.dict_id, 0u32);
set_field(view.b_type, BT_RESERVED);
set_field(view.is_frame_decompression, 1 as c_int);
entropy.rep = [1, 4, 8];
set_field(view.llt_ptr, entropy.ll_table.as_ptr());
set_field(view.mlt_ptr, entropy.ml_table.as_ptr());
set_field(view.oft_ptr, entropy.of_table.as_ptr());
set_field(view.huf_ptr, entropy.huf_table.as_ptr());
}
0
}
unsafe fn decompress_insert_dictionary(
view: &ZSTD_rustDctxView,
mut dict: *const u8,
mut dict_size: usize,
) -> usize {
if dict_size < 8 || unsafe { MEM_readLE32(dict.cast()) } != ZSTD_MAGIC_DICTIONARY {
return unsafe { ref_dict_content(view, dict, dict_size) };
}
unsafe {
set_field(
view.dict_id,
MEM_readLE32(dict.add(ZSTD_FRAMEIDSIZE).cast()),
)
};
let entropy_size = unsafe { ZSTD_loadDEntropy(entropy_ptr(view), dict.cast(), dict_size) };
if ERR_isError(entropy_size) {
return ERROR(ZstdErrorCode::DictionaryCorrupted);
}
dict = unsafe { dict.add(entropy_size) };
dict_size -= entropy_size;
unsafe {
set_field(view.lit_entropy, 1u32);
set_field(view.fse_entropy, 1u32);
ref_dict_content(view, dict, dict_size)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressBegin(dctx: *mut ZSTD_DCtx) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe { decompress_begin(&dctx_view(dctx)) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressBegin_usingDict(
dctx: *mut ZSTD_DCtx,
dict: *const c_void,
dict_size: usize,
) -> usize {
let view = unsafe { dctx_view(dctx) };
let result = unsafe { decompress_begin(&view) };
if ERR_isError(result) {
return result;
}
if !dict.is_null() && dict_size != 0 {
let result = unsafe { decompress_insert_dictionary(&view, dict.cast(), dict_size) };
if ERR_isError(result) {
return ERROR(ZstdErrorCode::DictionaryCorrupted);
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressBegin_usingDDict(
dctx: *mut ZSTD_DCtx,
ddict: *const ZSTD_DDict,
) -> usize {
let view = unsafe { dctx_view(dctx) };
if !ddict.is_null() {
let dict_start = unsafe { ZSTD_DDict_dictContent(ddict) };
let dict_size = unsafe { ZSTD_DDict_dictSize(ddict) };
let dict_end = unsafe { dict_start.cast::<u8>().add(dict_size).cast::<c_void>() };
unsafe {
set_field(
view.ddict_is_cold,
c_int::from(get_pointer(view.dict_end).cast::<c_void>() != dict_end),
)
};
}
let result = unsafe { decompress_begin(&view) };
if ERR_isError(result) {
return result;
}
if !ddict.is_null() {
unsafe { ZSTD_copyDDictParameters(dctx.cast(), ddict) };
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_getDictID_fromDict(dict: *const c_void, dict_size: usize) -> c_uint {
if dict.is_null() || dict_size < 8 || unsafe { MEM_readLE32(dict) } != ZSTD_MAGIC_DICTIONARY {
0
} else {
unsafe { MEM_readLE32(dict.cast::<u8>().add(ZSTD_FRAMEIDSIZE).cast()) }
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_getDictID_fromFrame(src: *const c_void, src_size: usize) -> c_uint {
let mut zfh = ZSTD_FrameHeader::default();
if ERR_isError(unsafe { ZSTD_getFrameHeader(&mut zfh, src, src_size) }) {
0
} else {
zfh.dict_id
}
}
/*-*************************************************************
* Frame decoding
***************************************************************/
#[inline]
unsafe fn copy_raw_block(
dst: *mut u8,
dst_capacity: usize,
src: *const u8,
src_size: usize,
) -> usize {
if src_size > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if dst.is_null() {
return if src_size == 0 {
0
} else {
ERROR(ZstdErrorCode::DstBufferNull)
};
}
unsafe { copy_bytes(dst, src, src_size) };
src_size
}
#[inline]
unsafe fn set_rle_block(
dst: *mut u8,
dst_capacity: usize,
byte: u8,
regenerated_size: usize,
) -> usize {
if regenerated_size > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if dst.is_null() {
return if regenerated_size == 0 {
0
} else {
ERROR(ZstdErrorCode::DstBufferNull)
};
}
if regenerated_size != 0 {
unsafe { dst.write_bytes(byte, regenerated_size) };
}
regenerated_size
}
/// Decode exactly one non-skippable modern frame and advance the caller's
/// input cursor. The state preparation deliberately stays in the public
/// `decompressBegin*()` calls, just as in the C implementation.
unsafe fn decompress_frame(
dctx: *mut ZSTD_DCtx,
view: &ZSTD_rustDctxView,
dst: *mut c_void,
dst_capacity: usize,
src_ptr: &mut *const u8,
src_size_ptr: &mut usize,
) -> usize {
let istart = *src_ptr;
let mut ip = istart;
let ostart = dst.cast::<u8>();
/* `dst == NULL, dstCapacity == 0` is supported for empty frames. A
* wrapping endpoint preserves C's address-only calculation until a
* block decoder reports the appropriate null/size error. */
let oend = ostart.wrapping_add(dst_capacity);
let mut op = ostart;
let mut remaining = *src_size_ptr;
let format = unsafe { field::<c_int>(view.format) };
if remaining < frame_header_min(format) + ZSTD_BLOCKHEADERSIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let header_size =
unsafe { frame_header_size_internal(ip, frame_header_prefix(format), format) };
if ERR_isError(header_size) {
return header_size;
}
if remaining < header_size + ZSTD_BLOCKHEADERSIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let result = unsafe { decode_frame_header(view, ip, header_size) };
if ERR_isError(result) {
return result;
}
ip = unsafe { ip.add(header_size) };
remaining -= header_size;
let max_block_size_param = unsafe { field::<c_int>(view.max_block_size_param) };
if max_block_size_param != 0 {
let mut params = unsafe { field::<ZSTD_FrameHeader>(view.f_params) };
params.block_size_max = min(params.block_size_max, max_block_size_param as c_uint);
unsafe { set_field(view.f_params, params) };
}
loop {
let mut block = BlockProperties::default();
let c_block_size = unsafe {
crate::zstd_decompress_block::ZSTD_getcBlockSize(
ip.cast(),
remaining,
(&mut block as *mut BlockProperties).cast(),
)
};
if ERR_isError(c_block_size) {
return c_block_size;
}
ip = unsafe { ip.add(ZSTD_BLOCKHEADERSIZE) };
remaining -= ZSTD_BLOCKHEADERSIZE;
if c_block_size > remaining {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let mut block_end = oend;
if (ip as usize) >= (op as usize) && (ip as usize) < (block_end as usize) {
block_end = op.wrapping_add((ip as usize).wrapping_sub(op as usize));
}
let block_capacity = (block_end as usize).wrapping_sub(op as usize);
let decoded_size = match block.block_type {
BT_COMPRESSED => unsafe {
ZSTD_decompressBlock_internal(
dctx,
op.cast(),
block_capacity,
ip.cast(),
c_block_size,
0,
)
},
/* This deliberately uses `oend`, not `block_end`: memmove is
* overlap-safe for raw blocks. */
BT_RAW => unsafe {
copy_raw_block(
op,
(oend as usize).wrapping_sub(op as usize),
ip,
c_block_size,
)
},
BT_RLE => {
if c_block_size == 0 {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
unsafe { set_rle_block(op, block_capacity, *ip, block.orig_size as usize) }
}
_ => ERROR(ZstdErrorCode::CorruptionDetected),
};
if ERR_isError(decoded_size) {
return decoded_size;
}
if unsafe { field::<u32>(view.validate_checksum) } != 0 {
let _ = unsafe {
XXH64_update(
view.xxh_state.cast::<XXH64_state_t>(),
op.cast(),
decoded_size,
)
};
}
if decoded_size != 0 {
op = unsafe { op.add(decoded_size) };
}
ip = unsafe { ip.add(c_block_size) };
remaining -= c_block_size;
if block.last_block != 0 {
break;
}
}
let params = unsafe { field::<ZSTD_FrameHeader>(view.f_params) };
let decoded = (op as usize).wrapping_sub(ostart as usize);
if params.frame_content_size != ZSTD_CONTENTSIZE_UNKNOWN
&& decoded as u64 != params.frame_content_size
{
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if params.checksum_flag != 0 {
if remaining < 4 {
return ERROR(ZstdErrorCode::ChecksumWrong);
}
if unsafe { field::<c_int>(view.force_ignore_checksum) } == ZSTD_D_VALIDATE_CHECKSUM {
let calculated = unsafe { XXH64_digest(view.xxh_state.cast::<XXH64_state_t>()) } as u32;
let read = unsafe { MEM_readLE32(ip.cast()) };
if calculated != read {
return ERROR(ZstdErrorCode::ChecksumWrong);
}
}
ip = unsafe { ip.add(4) };
remaining -= 4;
}
unsafe {
ZSTD_rust_dctx_trace_end(
dctx,
decoded as u64,
(ip as usize).wrapping_sub(istart as usize) as u64,
0,
);
}
*src_ptr = ip;
*src_size_ptr = remaining;
decoded
}
unsafe fn decompress_multi_frame(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
mut dst_capacity: usize,
src: *const c_void,
mut src_size: usize,
mut dict: *const c_void,
mut dict_size: usize,
ddict: *const ZSTD_DDict,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
if !ddict.is_null() {
dict = unsafe { ZSTD_DDict_dictContent(ddict) };
dict_size = unsafe { ZSTD_DDict_dictSize(ddict) };
}
let dst_start = dst.cast::<u8>();
let mut output = dst_start;
let mut input = src.cast::<u8>();
let mut more_than_one_frame = false;
let starting_input = frame_header_prefix(unsafe { field::<c_int>(view.format) });
while src_size >= starting_input {
if unsafe { field::<c_int>(view.format) } == ZSTD_F_ZSTD1
&& unsafe { ZSTD_rust_legacy_is(input.cast(), src_size) } != 0
{
let frame_size =
unsafe { ZSTD_rust_legacy_find_compressed_size(input.cast(), src_size) };
if ERR_isError(frame_size) {
return frame_size;
}
if unsafe { field::<usize>(view.static_size) } != 0 {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
if frame_size > src_size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let decoded = unsafe {
ZSTD_rust_legacy_decompress(
output.cast(),
dst_capacity,
input.cast(),
frame_size,
dict,
dict_size,
)
};
if ERR_isError(decoded) {
return decoded;
}
let expected = unsafe { ZSTD_getFrameContentSize(input.cast(), src_size) };
if expected == ZSTD_CONTENTSIZE_ERROR
|| (expected != ZSTD_CONTENTSIZE_UNKNOWN && expected != decoded as u64)
{
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if decoded > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if decoded != 0 {
output = unsafe { output.add(decoded) };
}
dst_capacity -= decoded;
input = unsafe { input.add(frame_size) };
src_size -= frame_size;
continue;
}
if unsafe { field::<c_int>(view.format) } == ZSTD_F_ZSTD1 && src_size >= ZSTD_FRAMEIDSIZE {
let magic = unsafe { MEM_readLE32(input.cast()) };
if magic & ZSTD_MAGIC_SKIPPABLE_MASK == ZSTD_MAGIC_SKIPPABLE_START {
let size = unsafe { read_skippable_frame_size(input, src_size) };
if ERR_isError(size) {
return size;
}
input = unsafe { input.add(size) };
src_size -= size;
continue;
}
}
let init = if !ddict.is_null() {
unsafe { ZSTD_decompressBegin_usingDDict(dctx, ddict) }
} else {
unsafe { ZSTD_decompressBegin_usingDict(dctx, dict, dict_size) }
};
if ERR_isError(init) {
return init;
}
unsafe { ZSTD_checkContinuity(dctx, output.cast(), dst_capacity) };
let decoded = unsafe {
decompress_frame(
dctx,
&view,
output.cast(),
dst_capacity,
&mut input,
&mut src_size,
)
};
if decoded == ERROR(ZstdErrorCode::PrefixUnknown) && more_than_one_frame {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if ERR_isError(decoded) {
return decoded;
}
if decoded > dst_capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if decoded != 0 {
output = unsafe { output.add(decoded) };
}
dst_capacity -= decoded;
more_than_one_frame = true;
}
if src_size != 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
(output as usize).wrapping_sub(dst_start as usize)
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_insertBlock(
dctx: *mut ZSTD_DCtx,
block_start: *const c_void,
block_size: usize,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe {
ZSTD_checkContinuity(dctx, block_start, block_size);
let view = dctx_view(dctx);
set_pointer(
view.previous_dst_end,
const_ptr_add(block_start.cast(), block_size),
);
}
block_size
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompress_usingDict(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
dict: *const c_void,
dict_size: usize,
) -> usize {
unsafe {
decompress_multi_frame(
dctx,
dst,
dst_capacity,
src,
src_size,
dict,
dict_size,
ptr::null(),
)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompress_usingDDict(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
ddict: *const ZSTD_DDict,
) -> usize {
unsafe {
decompress_multi_frame(
dctx,
dst,
dst_capacity,
src,
src_size,
ptr::null(),
0,
ddict,
)
}
}
unsafe fn get_ddict(view: &ZSTD_rustDctxView) -> *const ZSTD_DDict {
match unsafe { field::<c_int>(view.dict_uses) } {
ZSTD_DONT_USE => {
unsafe { clear_dict(view) };
ptr::null()
}
ZSTD_USE_INDEFINITELY => unsafe { dctx_ddict(view) },
ZSTD_USE_ONCE => {
unsafe { set_field(view.dict_uses, ZSTD_DONT_USE) };
unsafe { dctx_ddict(view) }
}
_ => {
unsafe { clear_dict(view) };
ptr::null()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressDCtx(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
let ddict = unsafe { get_ddict(&view) };
unsafe { ZSTD_decompress_usingDDict(dctx, dst, dst_capacity, src, src_size, ddict) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompress(
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
) -> usize {
if unsafe { ZSTD_rust_heapmode() } < 1 {
return unsafe { ZSTD_rust_decompress_stack(dst, dst_capacity, src, src_size) };
}
let dctx = unsafe { ZSTD_createDCtx() };
if dctx.is_null() {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
let result = unsafe { ZSTD_decompressDCtx(dctx, dst, dst_capacity, src, src_size) };
let _ = unsafe { ZSTD_freeDCtx(dctx) };
result
}
/*-**************************************
* Advanced bufferless decompression
****************************************/
#[inline]
unsafe fn next_src_size_with_input_size(view: &ZSTD_rustDctxView, input_size: usize) -> usize {
let stage = unsafe { field::<c_int>(view.stage) };
if (stage == ZSTDDS_DECOMPRESS_BLOCK || stage == ZSTDDS_DECOMPRESS_LAST_BLOCK)
&& unsafe { field::<c_int>(view.b_type) } == BT_RAW
{
let expected = unsafe { field::<usize>(view.expected) };
return max(1, min(input_size, expected));
}
unsafe { field::<usize>(view.expected) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_nextSrcSizeToDecompress(dctx: *mut ZSTD_DCtx) -> usize {
if dctx.is_null() {
return 0;
}
let view = unsafe { dctx_view(dctx) };
unsafe { field(view.expected) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_nextInputType(dctx: *mut ZSTD_DCtx) -> c_int {
if dctx.is_null() {
return ZSTD_NIT_FRAME_HEADER;
}
let view = unsafe { dctx_view(dctx) };
match unsafe { field::<c_int>(view.stage) } {
ZSTDDS_GET_FRAME_HEADER_SIZE | ZSTDDS_DECODE_FRAME_HEADER => ZSTD_NIT_FRAME_HEADER,
ZSTDDS_DECODE_BLOCK_HEADER => ZSTD_NIT_BLOCK_HEADER,
ZSTDDS_DECOMPRESS_BLOCK => ZSTD_NIT_BLOCK,
ZSTDDS_DECOMPRESS_LAST_BLOCK => ZSTD_NIT_LAST_BLOCK,
ZSTDDS_CHECK_CHECKSUM => ZSTD_NIT_CHECKSUM,
ZSTDDS_DECODE_SKIPPABLE_HEADER | ZSTDDS_SKIP_FRAME => ZSTD_NIT_SKIPPABLE_FRAME,
_ => ZSTD_NIT_FRAME_HEADER,
}
}
#[inline]
unsafe fn is_skip_frame(view: &ZSTD_rustDctxView) -> bool {
(unsafe { field::<c_int>(view.stage) }) == ZSTDDS_SKIP_FRAME
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressContinue(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
if src_size != unsafe { next_src_size_with_input_size(&view, src_size) } {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
unsafe { ZSTD_checkContinuity(dctx, dst.cast(), dst_capacity) };
let processed = unsafe { field::<u64>(view.processed_c_size) }.wrapping_add(src_size as u64);
unsafe { set_field(view.processed_c_size, processed) };
match unsafe { field::<c_int>(view.stage) } {
ZSTDDS_GET_FRAME_HEADER_SIZE => {
let format = unsafe { field::<c_int>(view.format) };
let header_buffer = view.header_buffer.cast::<u8>();
if format == ZSTD_F_ZSTD1
&& src_size >= ZSTD_FRAMEIDSIZE
&& unsafe { MEM_readLE32(src) } & ZSTD_MAGIC_SKIPPABLE_MASK
== ZSTD_MAGIC_SKIPPABLE_START
{
unsafe { copy_bytes(header_buffer, src.cast(), src_size) };
unsafe {
set_field(view.expected, ZSTD_SKIPPABLEHEADERSIZE - src_size);
set_field(view.stage, ZSTDDS_DECODE_SKIPPABLE_HEADER);
}
return 0;
}
let header_size = unsafe { frame_header_size_internal(src.cast(), src_size, format) };
if ERR_isError(header_size) {
return header_size;
}
unsafe {
copy_bytes(header_buffer, src.cast(), src_size);
set_field(view.header_size, header_size);
set_field(view.expected, header_size - src_size);
set_field(view.stage, ZSTDDS_DECODE_FRAME_HEADER);
}
0
}
ZSTDDS_DECODE_FRAME_HEADER => {
let header_size = unsafe { field::<usize>(view.header_size) };
let offset = header_size - src_size;
unsafe {
copy_bytes(
view.header_buffer.cast::<u8>().add(offset),
src.cast(),
src_size,
);
}
let result =
unsafe { decode_frame_header(&view, view.header_buffer.cast(), header_size) };
if ERR_isError(result) {
return result;
}
unsafe {
set_field(view.expected, ZSTD_BLOCKHEADERSIZE);
set_field(view.stage, ZSTDDS_DECODE_BLOCK_HEADER);
}
0
}
ZSTDDS_DECODE_BLOCK_HEADER => {
let mut block = BlockProperties::default();
let c_block_size = unsafe {
crate::zstd_decompress_block::ZSTD_getcBlockSize(
src,
ZSTD_BLOCKHEADERSIZE,
(&mut block as *mut BlockProperties).cast(),
)
};
if ERR_isError(c_block_size) {
return c_block_size;
}
let params = unsafe { field::<ZSTD_FrameHeader>(view.f_params) };
if c_block_size > params.block_size_max as usize {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
unsafe {
set_field(view.expected, c_block_size);
set_field(view.b_type, block.block_type);
set_field(view.rle_size, block.orig_size as usize);
}
if c_block_size != 0 {
unsafe {
set_field(
view.stage,
if block.last_block != 0 {
ZSTDDS_DECOMPRESS_LAST_BLOCK
} else {
ZSTDDS_DECOMPRESS_BLOCK
},
);
}
return 0;
}
unsafe {
if block.last_block != 0 {
if params.checksum_flag != 0 {
set_field(view.expected, 4usize);
set_field(view.stage, ZSTDDS_CHECK_CHECKSUM);
} else {
set_field(view.expected, 0usize);
set_field(view.stage, ZSTDDS_GET_FRAME_HEADER_SIZE);
}
} else {
set_field(view.expected, ZSTD_BLOCKHEADERSIZE);
set_field(view.stage, ZSTDDS_DECODE_BLOCK_HEADER);
}
}
0
}
ZSTDDS_DECOMPRESS_BLOCK | ZSTDDS_DECOMPRESS_LAST_BLOCK => {
let stage = unsafe { field::<c_int>(view.stage) };
let b_type = unsafe { field::<c_int>(view.b_type) };
let decoded = match b_type {
BT_COMPRESSED => {
let result = unsafe {
ZSTD_decompressBlock_internal(dctx, dst, dst_capacity, src, src_size, 1)
};
unsafe { set_field(view.expected, 0usize) };
result
}
BT_RAW => {
let result =
unsafe { copy_raw_block(dst.cast(), dst_capacity, src.cast(), src_size) };
if ERR_isError(result) {
return result;
}
let expected = unsafe { field::<usize>(view.expected) } - result;
unsafe { set_field(view.expected, expected) };
result
}
BT_RLE => {
if src_size == 0 {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
let result = unsafe {
set_rle_block(
dst.cast(),
dst_capacity,
*src.cast::<u8>(),
field::<usize>(view.rle_size),
)
};
unsafe { set_field(view.expected, 0usize) };
result
}
_ => return ERROR(ZstdErrorCode::CorruptionDetected),
};
if ERR_isError(decoded) {
return decoded;
}
let params = unsafe { field::<ZSTD_FrameHeader>(view.f_params) };
if decoded > params.block_size_max as usize {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
let decoded_total =
unsafe { field::<u64>(view.decoded_size) }.wrapping_add(decoded as u64);
unsafe {
set_field(view.decoded_size, decoded_total);
if field::<u32>(view.validate_checksum) != 0 {
let _ = XXH64_update(view.xxh_state.cast::<XXH64_state_t>(), dst, decoded);
}
set_pointer(view.previous_dst_end, const_ptr_add(dst.cast(), decoded));
}
if unsafe { field::<usize>(view.expected) } != 0 {
return decoded;
}
if stage == ZSTDDS_DECOMPRESS_LAST_BLOCK {
if params.frame_content_size != ZSTD_CONTENTSIZE_UNKNOWN
&& decoded_total != params.frame_content_size
{
return ERROR(ZstdErrorCode::CorruptionDetected);
}
unsafe {
if params.checksum_flag != 0 {
set_field(view.expected, 4usize);
set_field(view.stage, ZSTDDS_CHECK_CHECKSUM);
} else {
ZSTD_rust_dctx_trace_end(
dctx,
decoded_total,
field::<u64>(view.processed_c_size),
1,
);
set_field(view.expected, 0usize);
set_field(view.stage, ZSTDDS_GET_FRAME_HEADER_SIZE);
}
}
} else {
unsafe {
set_field(view.stage, ZSTDDS_DECODE_BLOCK_HEADER);
set_field(view.expected, ZSTD_BLOCKHEADERSIZE);
}
}
decoded
}
ZSTDDS_CHECK_CHECKSUM => {
if src_size != 4 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if unsafe { field::<u32>(view.validate_checksum) } != 0 {
let calculated =
unsafe { XXH64_digest(view.xxh_state.cast::<XXH64_state_t>()) } as u32;
let read = unsafe { MEM_readLE32(src) };
if calculated != read {
return ERROR(ZstdErrorCode::ChecksumWrong);
}
}
unsafe {
ZSTD_rust_dctx_trace_end(
dctx,
field::<u64>(view.decoded_size),
field::<u64>(view.processed_c_size),
1,
);
set_field(view.expected, 0usize);
set_field(view.stage, ZSTDDS_GET_FRAME_HEADER_SIZE);
}
0
}
ZSTDDS_DECODE_SKIPPABLE_HEADER => {
if src_size > ZSTD_SKIPPABLEHEADERSIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
unsafe {
let header = view.header_buffer.cast::<u8>();
copy_bytes(
header.add(ZSTD_SKIPPABLEHEADERSIZE - src_size),
src.cast(),
src_size,
);
set_field(
view.expected,
MEM_readLE32(header.add(ZSTD_FRAMEIDSIZE).cast()) as usize,
);
set_field(view.stage, ZSTDDS_SKIP_FRAME);
}
0
}
ZSTDDS_SKIP_FRAME => {
unsafe {
set_field(view.expected, 0usize);
set_field(view.stage, ZSTDDS_GET_FRAME_HEADER_SIZE);
}
0
}
_ => ERROR(ZstdErrorCode::Generic),
}
}
/*-**************************************
* Streaming context management
****************************************/
#[no_mangle]
pub unsafe extern "C" fn ZSTD_createDStream() -> *mut ZSTD_DStream {
unsafe { ZSTD_createDCtx() }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_initStaticDStream(
workspace: *mut c_void,
workspace_size: usize,
) -> *mut ZSTD_DStream {
unsafe { ZSTD_initStaticDCtx(workspace, workspace_size) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_createDStream_advanced(
custom_mem: ZSTD_customMem,
) -> *mut ZSTD_DStream {
unsafe { ZSTD_createDCtx_advanced(custom_mem) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_freeDStream(zds: *mut ZSTD_DStream) -> usize {
unsafe { ZSTD_freeDCtx(zds) }
}
#[no_mangle]
pub extern "C" fn ZSTD_DStreamInSize() -> usize {
ZSTD_BLOCKSIZE_MAX + ZSTD_BLOCKHEADERSIZE
}
#[no_mangle]
pub extern "C" fn ZSTD_DStreamOutSize() -> usize {
ZSTD_BLOCKSIZE_MAX
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_loadDictionary_advanced(
dctx: *mut ZSTD_DCtx,
dict: *const c_void,
dict_size: usize,
dict_load_method: c_int,
dict_content_type: c_int,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
if unsafe { field::<c_int>(view.stream_stage) } != ZDSS_INIT {
return ERROR(ZstdErrorCode::StageWrong);
}
unsafe { clear_dict(&view) };
if !dict.is_null() && dict_size != 0 {
let ddict = unsafe {
ZSTD_rust_create_ddict(
dict,
dict_size,
dict_load_method,
dict_content_type,
dctx_custom_mem(&view),
)
};
if ddict.is_null() {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
unsafe {
set_dctx_ddict_local(&view, ddict);
set_dctx_ddict(&view, ddict);
set_field(view.dict_uses, ZSTD_USE_INDEFINITELY);
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_loadDictionary_byReference(
dctx: *mut ZSTD_DCtx,
dict: *const c_void,
dict_size: usize,
) -> usize {
unsafe {
ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dict_size, ZSTD_DLM_BY_REF, ZSTD_DCT_AUTO)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_loadDictionary(
dctx: *mut ZSTD_DCtx,
dict: *const c_void,
dict_size: usize,
) -> usize {
unsafe {
ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dict_size, ZSTD_DLM_BY_COPY, ZSTD_DCT_AUTO)
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_refPrefix_advanced(
dctx: *mut ZSTD_DCtx,
prefix: *const c_void,
prefix_size: usize,
dict_content_type: c_int,
) -> usize {
let result = unsafe {
ZSTD_DCtx_loadDictionary_advanced(
dctx,
prefix,
prefix_size,
ZSTD_DLM_BY_REF,
dict_content_type,
)
};
if ERR_isError(result) {
return result;
}
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
unsafe { set_field(view.dict_uses, ZSTD_USE_ONCE) };
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_refPrefix(
dctx: *mut ZSTD_DCtx,
prefix: *const c_void,
prefix_size: usize,
) -> usize {
unsafe { ZSTD_DCtx_refPrefix_advanced(dctx, prefix, prefix_size, ZSTD_DCT_RAW_CONTENT) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_initDStream_usingDict(
zds: *mut ZSTD_DStream,
dict: *const c_void,
dict_size: usize,
) -> usize {
let result = unsafe { ZSTD_DCtx_reset(zds, ZSTD_RESET_SESSION_ONLY) };
if ERR_isError(result) {
return result;
}
let result = unsafe { ZSTD_DCtx_loadDictionary(zds, dict, dict_size) };
if ERR_isError(result) {
return result;
}
if zds.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(zds) };
frame_header_prefix(unsafe { field::<c_int>(view.format) })
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_initDStream(zds: *mut ZSTD_DStream) -> usize {
let result = unsafe { ZSTD_DCtx_reset(zds, ZSTD_RESET_SESSION_ONLY) };
if ERR_isError(result) {
return result;
}
let result = unsafe { ZSTD_DCtx_refDDict(zds, ptr::null()) };
if ERR_isError(result) {
return result;
}
if zds.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(zds) };
frame_header_prefix(unsafe { field::<c_int>(view.format) })
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_initDStream_usingDDict(
zds: *mut ZSTD_DStream,
ddict: *const ZSTD_DDict,
) -> usize {
let result = unsafe { ZSTD_DCtx_reset(zds, ZSTD_RESET_SESSION_ONLY) };
if ERR_isError(result) {
return result;
}
let result = unsafe { ZSTD_DCtx_refDDict(zds, ddict) };
if ERR_isError(result) {
return result;
}
if zds.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(zds) };
frame_header_prefix(unsafe { field::<c_int>(view.format) })
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_resetDStream(zds: *mut ZSTD_DStream) -> usize {
let result = unsafe { ZSTD_DCtx_reset(zds, ZSTD_RESET_SESSION_ONLY) };
if ERR_isError(result) {
return result;
}
if zds.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(zds) };
frame_header_prefix(unsafe { field::<c_int>(view.format) })
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_refDDict(
dctx: *mut ZSTD_DCtx,
ddict: *const ZSTD_DDict,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
if unsafe { field::<c_int>(view.stream_stage) } != ZDSS_INIT {
return ERROR(ZstdErrorCode::StageWrong);
}
unsafe { clear_dict(&view) };
if ddict.is_null() {
return 0;
}
unsafe {
set_dctx_ddict(&view, ddict);
set_field(view.dict_uses, ZSTD_USE_INDEFINITELY);
}
if unsafe { field::<c_int>(view.ref_multiple_ddicts) } == ZSTD_RMD_REF_MULTIPLE_DDICTS {
let mut set: *mut DDictHashSet = unsafe { field(view.ddict_set) };
if set.is_null() {
if unsafe { field::<usize>(view.static_size) } != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
set = unsafe { ddict_hashset_create(dctx_custom_mem(&view)) };
if set.is_null() {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
unsafe { set_field(view.ddict_set, set) };
}
let result = unsafe { ddict_hashset_add(set, ddict, dctx_custom_mem(&view)) };
if ERR_isError(result) {
return result;
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_setMaxWindowSize(
dctx: *mut ZSTD_DCtx,
max_window_size: usize,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
let bounds = ZSTD_dParam_getBounds(ZSTD_D_WINDOW_LOG_MAX);
let minimum = 1usize << bounds.lower_bound;
let maximum = 1usize << bounds.upper_bound;
if unsafe { field::<c_int>(view.stream_stage) } != ZDSS_INIT {
return ERROR(ZstdErrorCode::StageWrong);
}
if max_window_size < minimum || max_window_size > maximum {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.max_window_size, max_window_size) };
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_setFormat(dctx: *mut ZSTD_DCtx, format: c_int) -> usize {
unsafe { ZSTD_DCtx_setParameter(dctx, ZSTD_D_FORMAT, format) }
}
#[no_mangle]
pub extern "C" fn ZSTD_dParam_getBounds(param: c_int) -> ZSTD_bounds {
match param {
ZSTD_D_WINDOW_LOG_MAX => ZSTD_bounds {
error: 0,
lower_bound: ZSTD_WINDOWLOG_ABSOLUTEMIN as c_int,
upper_bound: window_log_max() as c_int,
},
ZSTD_D_FORMAT => ZSTD_bounds {
error: 0,
lower_bound: ZSTD_F_ZSTD1,
upper_bound: ZSTD_F_ZSTD1_MAGICLESS,
},
ZSTD_D_STABLE_OUT_BUFFER => ZSTD_bounds {
error: 0,
lower_bound: ZSTD_BM_BUFFERED,
upper_bound: ZSTD_BM_STABLE,
},
ZSTD_D_FORCE_IGNORE_CHECKSUM => ZSTD_bounds {
error: 0,
lower_bound: ZSTD_D_VALIDATE_CHECKSUM,
upper_bound: ZSTD_D_IGNORE_CHECKSUM,
},
ZSTD_D_REF_MULTIPLE_DDICTS => ZSTD_bounds {
error: 0,
lower_bound: ZSTD_RMD_REF_SINGLE_DDICT,
upper_bound: ZSTD_RMD_REF_MULTIPLE_DDICTS,
},
ZSTD_D_DISABLE_HUFFMAN_ASSEMBLY => ZSTD_bounds {
error: 0,
lower_bound: 0,
upper_bound: 1,
},
ZSTD_D_MAX_BLOCK_SIZE => ZSTD_bounds {
error: 0,
lower_bound: ZSTD_BLOCKSIZE_MAX_MIN as c_int,
upper_bound: ZSTD_BLOCKSIZE_MAX as c_int,
},
_ => ZSTD_bounds {
error: ERROR(ZstdErrorCode::ParameterUnsupported),
lower_bound: 0,
upper_bound: 0,
},
}
}
#[inline]
fn dparam_within_bounds(param: c_int, value: c_int) -> bool {
let bounds = ZSTD_dParam_getBounds(param);
!ERR_isError(bounds.error) && value >= bounds.lower_bound && value <= bounds.upper_bound
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_getParameter(
dctx: *mut ZSTD_DCtx,
param: c_int,
value: *mut c_int,
) -> usize {
if dctx.is_null() || value.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
let result = match param {
ZSTD_D_WINDOW_LOG_MAX => {
let window = unsafe { field::<usize>(view.max_window_size) } as u32;
(u32::BITS - 1 - window.leading_zeros()) as c_int
}
ZSTD_D_FORMAT => unsafe { field::<c_int>(view.format) },
ZSTD_D_STABLE_OUT_BUFFER => unsafe { field::<c_int>(view.out_buffer_mode) },
ZSTD_D_FORCE_IGNORE_CHECKSUM => unsafe { field::<c_int>(view.force_ignore_checksum) },
ZSTD_D_REF_MULTIPLE_DDICTS => unsafe { field::<c_int>(view.ref_multiple_ddicts) },
ZSTD_D_DISABLE_HUFFMAN_ASSEMBLY => unsafe { field::<c_int>(view.disable_huf_asm) },
ZSTD_D_MAX_BLOCK_SIZE => unsafe { field::<c_int>(view.max_block_size_param) },
_ => return ERROR(ZstdErrorCode::ParameterUnsupported),
};
unsafe { value.write(result) };
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_setParameter(
dctx: *mut ZSTD_DCtx,
param: c_int,
mut value: c_int,
) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
if unsafe { field::<c_int>(view.stream_stage) } != ZDSS_INIT {
return ERROR(ZstdErrorCode::StageWrong);
}
match param {
ZSTD_D_WINDOW_LOG_MAX => {
if value == 0 {
value = ZSTD_WINDOWLOG_LIMIT_DEFAULT as c_int;
}
if !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.max_window_size, 1usize << value) };
}
ZSTD_D_FORMAT => {
if !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.format, value) };
}
ZSTD_D_STABLE_OUT_BUFFER => {
if !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.out_buffer_mode, value) };
}
ZSTD_D_FORCE_IGNORE_CHECKSUM => {
if !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.force_ignore_checksum, value) };
}
ZSTD_D_REF_MULTIPLE_DDICTS => {
if !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
if unsafe { field::<usize>(view.static_size) } != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
unsafe { set_field(view.ref_multiple_ddicts, value) };
}
ZSTD_D_DISABLE_HUFFMAN_ASSEMBLY => {
if !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.disable_huf_asm, c_int::from(value != 0)) };
}
ZSTD_D_MAX_BLOCK_SIZE => {
if value != 0 && !dparam_within_bounds(param, value) {
return ERROR(ZstdErrorCode::ParameterOutOfBound);
}
unsafe { set_field(view.max_block_size_param, value) };
}
_ => return ERROR(ZstdErrorCode::ParameterUnsupported),
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_DCtx_reset(dctx: *mut ZSTD_DCtx, reset: c_int) -> usize {
if dctx.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let view = unsafe { dctx_view(dctx) };
if reset == ZSTD_RESET_SESSION_ONLY || reset == ZSTD_RESET_SESSION_AND_PARAMETERS {
unsafe {
set_field(view.stream_stage, ZDSS_INIT);
set_field(view.no_forward_progress, 0 as c_int);
set_field(view.is_frame_decompression, 1 as c_int);
}
}
if reset == ZSTD_RESET_PARAMETERS || reset == ZSTD_RESET_SESSION_AND_PARAMETERS {
if unsafe { field::<c_int>(view.stream_stage) } != ZDSS_INIT {
return ERROR(ZstdErrorCode::StageWrong);
}
unsafe {
clear_dict(&view);
reset_parameters(&view);
}
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_sizeof_DStream(dctx: *const ZSTD_DStream) -> usize {
unsafe { ZSTD_sizeof_DCtx(dctx) }
}
unsafe fn decoding_buffer_size_internal(
window_size: u64,
frame_content_size: u64,
block_size_max: usize,
) -> usize {
let block_size = min(
min(window_size, ZSTD_BLOCKSIZE_MAX as u64) as usize,
block_size_max,
);
let needed_ring = window_size
.wrapping_add((block_size as u64).wrapping_mul(2))
.wrapping_add((WILDCOPY_OVERLENGTH as u64).wrapping_mul(2));
let needed = min(frame_content_size, needed_ring);
let result = needed as usize;
if result as u64 != needed {
return ERROR(ZstdErrorCode::FrameParameterWindowTooLarge);
}
result
}
#[no_mangle]
pub extern "C" fn ZSTD_decodingBufferSize_min(window_size: u64, frame_content_size: u64) -> usize {
unsafe { decoding_buffer_size_internal(window_size, frame_content_size, ZSTD_BLOCKSIZE_MAX) }
}
#[no_mangle]
pub extern "C" fn ZSTD_estimateDStreamSize(window_size: usize) -> usize {
let block_size = min(window_size, ZSTD_BLOCKSIZE_MAX);
let out_size = ZSTD_decodingBufferSize_min(window_size as u64, ZSTD_CONTENTSIZE_UNKNOWN);
ZSTD_estimateDCtxSize()
.wrapping_add(block_size)
.wrapping_add(out_size)
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_estimateDStreamSize_fromFrame(
src: *const c_void,
src_size: usize,
) -> usize {
let mut zfh = ZSTD_FrameHeader::default();
let result = unsafe { ZSTD_getFrameHeader(&mut zfh, src, src_size) };
if ERR_isError(result) {
return result;
}
if result != 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if zfh.window_size > (1u64 << window_log_max()) {
return ERROR(ZstdErrorCode::FrameParameterWindowTooLarge);
}
ZSTD_estimateDStreamSize(zfh.window_size as usize)
}
#[inline]
unsafe fn dctx_is_overflow(
view: &ZSTD_rustDctxView,
needed_in_size: usize,
needed_out_size: usize,
) -> bool {
let current = unsafe { field::<usize>(view.in_buff_size) }
.wrapping_add(unsafe { field::<usize>(view.out_buff_size) });
let needed = needed_in_size
.wrapping_add(needed_out_size)
.wrapping_mul(ZSTD_WORKSPACETOOLARGE_FACTOR);
current >= needed
}
#[inline]
unsafe fn update_oversized_duration(
view: &ZSTD_rustDctxView,
needed_in_size: usize,
needed_out_size: usize,
) {
let duration = if unsafe { dctx_is_overflow(view, needed_in_size, needed_out_size) } {
unsafe { field::<usize>(view.oversized_duration) }.wrapping_add(1)
} else {
0
};
unsafe { set_field(view.oversized_duration, duration) };
}
#[inline]
unsafe fn oversized_too_long(view: &ZSTD_rustDctxView) -> bool {
(unsafe { field::<usize>(view.oversized_duration) }) >= ZSTD_WORKSPACETOOLARGE_MAXDURATION
}
unsafe fn check_out_buffer(view: &ZSTD_rustDctxView, output: &ZSTD_outBuffer) -> usize {
if unsafe { field::<c_int>(view.out_buffer_mode) } != ZSTD_BM_STABLE
|| unsafe { field::<c_int>(view.stream_stage) } == ZDSS_INIT
{
return 0;
}
let expected = unsafe { field::<ZSTD_outBuffer>(view.expected_out_buffer) };
if expected.dst == output.dst && expected.size == output.size && expected.pos == output.pos {
0
} else {
ERROR(ZstdErrorCode::DstBufferWrong)
}
}
/// Invoke the bufferless state machine from the streaming adapter and translate
/// its output into either the rolling internal buffer or the stable user buffer.
unsafe fn decompress_continue_stream(
dctx: *mut ZSTD_DCtx,
view: &ZSTD_rustDctxView,
op: &mut *mut u8,
oend: *mut u8,
src: *const u8,
src_size: usize,
) -> usize {
let skip = unsafe { is_skip_frame(view) };
if unsafe { field::<c_int>(view.out_buffer_mode) } == ZSTD_BM_BUFFERED {
let out_start = unsafe { field::<usize>(view.out_start) };
let out_size = unsafe { field::<usize>(view.out_buff_size) };
let dst_size = if skip {
0
} else {
out_size.wrapping_sub(out_start)
};
let out_buff = unsafe { get_mut_pointer(view.out_buff) };
let decoded = unsafe {
ZSTD_decompressContinue(
dctx,
out_buff.wrapping_add(out_start).cast(),
dst_size,
src.cast(),
src_size,
)
};
if ERR_isError(decoded) {
return decoded;
}
unsafe {
if decoded == 0 && !skip {
set_field(view.stream_stage, ZDSS_READ);
} else {
set_field(view.out_end, out_start.wrapping_add(decoded));
set_field(view.stream_stage, ZDSS_FLUSH);
}
}
} else {
let dst_size = (oend as usize).wrapping_sub(*op as usize);
let decoded =
unsafe { ZSTD_decompressContinue(dctx, (*op).cast(), dst_size, src.cast(), src_size) };
if ERR_isError(decoded) {
return decoded;
}
if decoded != 0 {
*op = unsafe { (*op).add(decoded) };
}
unsafe { set_field(view.stream_stage, ZDSS_READ) };
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressStream(
zds: *mut ZSTD_DStream,
output: *mut ZSTD_outBuffer,
input: *mut ZSTD_inBuffer,
) -> usize {
if zds.is_null() || output.is_null() || input.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let output_ref = unsafe { &mut *output };
let input_ref = unsafe { &mut *input };
if input_ref.pos > input_ref.size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if output_ref.pos > output_ref.size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let view = unsafe { dctx_view(zds) };
let src = input_ref.src.cast::<u8>();
let istart = src.wrapping_add(input_ref.pos);
let iend = src.wrapping_add(input_ref.size);
let mut ip = istart;
let dst = output_ref.dst.cast::<u8>();
let ostart = dst.wrapping_add(output_ref.pos);
let oend = dst.wrapping_add(output_ref.size);
let mut op = ostart;
let mut some_more_work = true;
let output_check = unsafe { check_out_buffer(&view, output_ref) };
if ERR_isError(output_check) {
return output_check;
}
while some_more_work {
match unsafe { field::<c_int>(view.stream_stage) } {
ZDSS_INIT => {
unsafe {
set_field(view.stream_stage, ZDSS_LOAD_HEADER);
set_field(view.lh_size, 0usize);
set_field(view.in_pos, 0usize);
set_field(view.out_start, 0usize);
set_field(view.out_end, 0usize);
if !view.legacy_version.is_null() {
set_field(view.legacy_version, 0u32);
}
set_field(view.hostage_byte, 0u32);
set_field(view.expected_out_buffer, *output_ref);
}
continue;
}
ZDSS_LOAD_HEADER => {
if !view.legacy_version.is_null()
&& unsafe { field::<u32>(view.legacy_version) } != 0
{
let ddict = unsafe { dctx_ddict(&view) };
let dict = if ddict.is_null() {
ptr::null()
} else {
unsafe { ZSTD_DDict_dictContent(ddict) }
};
let dict_size = if ddict.is_null() {
0
} else {
unsafe { ZSTD_DDict_dictSize(ddict) }
};
return unsafe {
ZSTD_rust_legacy_decompress_stream(zds, output, input, dict, dict_size)
};
}
let lh_size = unsafe { field::<usize>(view.lh_size) };
let header_result = unsafe {
ZSTD_getFrameHeader_advanced(
get_frame_header_ptr(&view),
view.header_buffer.cast(),
lh_size,
field::<c_int>(view.format),
)
};
if unsafe { field::<c_int>(view.ref_multiple_ddicts) }
== ZSTD_RMD_REF_MULTIPLE_DDICTS
&& !unsafe { field::<*mut DDictHashSet>(view.ddict_set) }.is_null()
{
unsafe { select_frame_ddict(&view) };
}
if ERR_isError(header_result) {
let available = (iend as usize).wrapping_sub(istart as usize);
if unsafe { ZSTD_rust_legacy_is(istart.cast(), available) } != 0 {
if unsafe { field::<usize>(view.static_size) } != 0 {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
let ddict = unsafe { get_ddict(&view) };
let dict = if ddict.is_null() {
ptr::null()
} else {
unsafe { ZSTD_DDict_dictContent(ddict) }
};
let dict_size = if ddict.is_null() {
0
} else {
unsafe { ZSTD_DDict_dictSize(ddict) }
};
return unsafe {
ZSTD_rust_legacy_decompress_stream(zds, output, input, dict, dict_size)
};
}
return header_result;
}
if header_result != 0 {
let to_load = header_result - lh_size;
let remaining_input = (iend as usize).wrapping_sub(ip as usize);
if to_load > remaining_input {
if remaining_input != 0 {
unsafe {
copy_bytes(
view.header_buffer.cast::<u8>().add(lh_size),
ip,
remaining_input,
);
set_field(view.lh_size, lh_size + remaining_input);
}
}
input_ref.pos = input_ref.size;
let check = unsafe {
ZSTD_getFrameHeader_advanced(
get_frame_header_ptr(&view),
view.header_buffer.cast(),
field::<usize>(view.lh_size),
field::<c_int>(view.format),
)
};
if ERR_isError(check) {
return check;
}
let minimum = max(
frame_header_min(unsafe { field::<c_int>(view.format) }),
header_result,
);
return minimum
.wrapping_sub(unsafe { field::<usize>(view.lh_size) })
.wrapping_add(ZSTD_BLOCKHEADERSIZE);
}
unsafe {
copy_bytes(view.header_buffer.cast::<u8>().add(lh_size), ip, to_load);
set_field(view.lh_size, header_result);
}
ip = unsafe { ip.add(to_load) };
continue;
}
let params = unsafe { field::<ZSTD_FrameHeader>(view.f_params) };
let available = (iend as usize).wrapping_sub(istart as usize);
if params.frame_content_size != ZSTD_CONTENTSIZE_UNKNOWN
&& params.frame_type != ZSTD_SKIPPABLE_FRAME
&& (oend as usize).wrapping_sub(op as usize)
>= params.frame_content_size as usize
{
let frame_size = unsafe {
find_frame_size_info(istart, available, field::<c_int>(view.format))
.compressed_size
};
if frame_size <= available {
let ddict = unsafe { get_ddict(&view) };
let decoded = unsafe {
ZSTD_decompress_usingDDict(
zds,
op.cast(),
(oend as usize).wrapping_sub(op as usize),
istart.cast(),
frame_size,
ddict,
)
};
if ERR_isError(decoded) {
return decoded;
}
ip = unsafe { istart.add(frame_size) };
if decoded != 0 {
op = unsafe { op.add(decoded) };
}
unsafe {
set_field(view.expected, 0usize);
set_field(view.stream_stage, ZDSS_INIT);
}
some_more_work = false;
continue;
}
}
if unsafe { field::<c_int>(view.out_buffer_mode) } == ZSTD_BM_STABLE
&& params.frame_type != ZSTD_SKIPPABLE_FRAME
&& params.frame_content_size != ZSTD_CONTENTSIZE_UNKNOWN
&& (oend as usize).wrapping_sub(op as usize)
< params.frame_content_size as usize
{
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let ddict = unsafe { get_ddict(&view) };
let begin = unsafe { ZSTD_decompressBegin_usingDDict(zds, ddict) };
if ERR_isError(begin) {
return begin;
}
let format = unsafe { field::<c_int>(view.format) };
if format == ZSTD_F_ZSTD1
&& unsafe { MEM_readLE32(view.header_buffer) } & ZSTD_MAGIC_SKIPPABLE_MASK
== ZSTD_MAGIC_SKIPPABLE_START
{
unsafe {
set_field(
view.expected,
MEM_readLE32(
view.header_buffer.cast::<u8>().add(ZSTD_FRAMEIDSIZE).cast(),
) as usize,
);
set_field(view.stage, ZSTDDS_SKIP_FRAME);
}
} else {
let result = unsafe {
decode_frame_header(
&view,
view.header_buffer.cast(),
field::<usize>(view.lh_size),
)
};
if ERR_isError(result) {
return result;
}
unsafe {
set_field(view.expected, ZSTD_BLOCKHEADERSIZE);
set_field(view.stage, ZSTDDS_DECODE_BLOCK_HEADER);
}
}
let mut frame_params = unsafe { field::<ZSTD_FrameHeader>(view.f_params) };
frame_params.window_size =
max(frame_params.window_size, 1u64 << ZSTD_WINDOWLOG_ABSOLUTEMIN);
if frame_params.window_size > unsafe { field::<usize>(view.max_window_size) } as u64
{
return ERROR(ZstdErrorCode::FrameParameterWindowTooLarge);
}
let max_block_size_param = unsafe { field::<c_int>(view.max_block_size_param) };
if max_block_size_param != 0 {
frame_params.block_size_max =
min(frame_params.block_size_max, max_block_size_param as c_uint);
}
unsafe { set_field(view.f_params, frame_params) };
let needed_in_size = max(frame_params.block_size_max as usize, 4);
let needed_out_size =
if unsafe { field::<c_int>(view.out_buffer_mode) } == ZSTD_BM_BUFFERED {
let size = unsafe {
decoding_buffer_size_internal(
frame_params.window_size,
frame_params.frame_content_size,
frame_params.block_size_max as usize,
)
};
if ERR_isError(size) {
return size;
}
size
} else {
0
};
unsafe { update_oversized_duration(&view, needed_in_size, needed_out_size) };
let too_small = unsafe { field::<usize>(view.in_buff_size) } < needed_in_size
|| unsafe { field::<usize>(view.out_buff_size) } < needed_out_size;
let too_large = unsafe { oversized_too_long(&view) };
if too_small || too_large {
let buffer_size = needed_in_size.wrapping_add(needed_out_size);
if unsafe { field::<usize>(view.static_size) } != 0 {
let static_size = unsafe { field::<usize>(view.static_size) };
if buffer_size > static_size.wrapping_sub(view.dctx_size) {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
} else {
unsafe {
ZSTD_rust_custom_free(
get_mut_pointer(view.in_buff).cast(),
dctx_custom_mem(&view),
);
set_field(view.in_buff_size, 0usize);
set_field(view.out_buff_size, 0usize);
}
let allocation =
unsafe { ZSTD_rust_custom_malloc(buffer_size, dctx_custom_mem(&view)) };
if allocation.is_null() {
return ERROR(ZstdErrorCode::MemoryAllocation);
}
unsafe { set_mut_pointer(view.in_buff, allocation.cast()) };
}
let in_buff = unsafe { get_mut_pointer(view.in_buff) };
unsafe {
set_field(view.in_buff_size, needed_in_size);
set_mut_pointer(view.out_buff, in_buff.wrapping_add(needed_in_size));
set_field(view.out_buff_size, needed_out_size);
}
}
unsafe { set_field(view.stream_stage, ZDSS_READ) };
continue;
}
ZDSS_READ => {
let available = (iend as usize).wrapping_sub(ip as usize);
let needed = unsafe { next_src_size_with_input_size(&view, available) };
if needed == 0 {
unsafe { set_field(view.stream_stage, ZDSS_INIT) };
some_more_work = false;
continue;
}
if available >= needed {
let result = unsafe {
decompress_continue_stream(zds, &view, &mut op, oend, ip, needed)
};
if ERR_isError(result) {
return result;
}
ip = unsafe { ip.add(needed) };
continue;
}
if ip == iend {
some_more_work = false;
continue;
}
unsafe { set_field(view.stream_stage, ZDSS_LOAD) };
continue;
}
ZDSS_LOAD => {
let needed = unsafe { field::<usize>(view.expected) };
let in_pos = unsafe { field::<usize>(view.in_pos) };
let to_load = needed.wrapping_sub(in_pos);
let skip = unsafe { is_skip_frame(&view) };
let available = (iend as usize).wrapping_sub(ip as usize);
let loaded = if skip {
min(to_load, available)
} else {
let in_size = unsafe { field::<usize>(view.in_buff_size) };
if to_load > in_size.wrapping_sub(in_pos) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
unsafe {
limit_copy(
get_mut_pointer(view.in_buff).wrapping_add(in_pos),
to_load,
ip,
available,
)
}
};
if loaded != 0 {
ip = unsafe { ip.add(loaded) };
unsafe { set_field(view.in_pos, in_pos + loaded) };
}
if loaded < to_load {
some_more_work = false;
continue;
}
unsafe { set_field(view.in_pos, 0usize) };
let result = unsafe {
decompress_continue_stream(
zds,
&view,
&mut op,
oend,
get_mut_pointer(view.in_buff).cast(),
needed,
)
};
if ERR_isError(result) {
return result;
}
continue;
}
ZDSS_FLUSH => {
let out_start = unsafe { field::<usize>(view.out_start) };
let out_end = unsafe { field::<usize>(view.out_end) };
let to_flush = out_end.wrapping_sub(out_start);
let flushed = unsafe {
limit_copy(
op,
(oend as usize).wrapping_sub(op as usize),
get_mut_pointer(view.out_buff).wrapping_add(out_start),
to_flush,
)
};
if flushed != 0 {
op = unsafe { op.add(flushed) };
}
let new_out_start = out_start + flushed;
unsafe { set_field(view.out_start, new_out_start) };
if flushed == to_flush {
unsafe {
set_field(view.stream_stage, ZDSS_READ);
let frame_params = field::<ZSTD_FrameHeader>(view.f_params);
if field::<usize>(view.out_buff_size)
< frame_params.frame_content_size as usize
&& new_out_start + frame_params.block_size_max as usize
> field::<usize>(view.out_buff_size)
{
set_field(view.out_start, 0usize);
set_field(view.out_end, 0usize);
}
}
continue;
}
some_more_work = false;
continue;
}
_ => return ERROR(ZstdErrorCode::Generic),
}
}
input_ref.pos = (ip as usize).wrapping_sub(src as usize);
output_ref.pos = (op as usize).wrapping_sub(dst as usize);
unsafe { set_field(view.expected_out_buffer, *output_ref) };
if ip == istart && op == ostart {
let stalled = unsafe { field::<c_int>(view.no_forward_progress) } + 1;
unsafe { set_field(view.no_forward_progress, stalled) };
if stalled >= unsafe { ZSTD_rust_no_forward_progress_max() } {
if op == oend {
return ERROR(ZstdErrorCode::NoForwardProgressDestFull);
}
if ip == iend {
return ERROR(ZstdErrorCode::NoForwardProgressInputEmpty);
}
return ERROR(ZstdErrorCode::Generic);
}
} else {
unsafe { set_field(view.no_forward_progress, 0 as c_int) };
}
let mut hint = unsafe { field::<usize>(view.expected) };
if hint == 0 {
if unsafe { field::<usize>(view.out_end) } == unsafe { field::<usize>(view.out_start) } {
if unsafe { field::<u32>(view.hostage_byte) } != 0 {
if input_ref.pos >= input_ref.size {
unsafe { set_field(view.stream_stage, ZDSS_READ) };
return 1;
}
input_ref.pos += 1;
}
return 0;
}
if unsafe { field::<u32>(view.hostage_byte) } == 0 {
if input_ref.pos == 0 {
return ERROR(ZstdErrorCode::Generic);
}
input_ref.pos -= 1;
unsafe { set_field(view.hostage_byte, 1u32) };
}
return 1;
}
if unsafe { ZSTD_nextInputType(zds) } == ZSTD_NIT_BLOCK {
hint = hint.wrapping_add(ZSTD_BLOCKHEADERSIZE);
}
let in_pos = unsafe { field::<usize>(view.in_pos) };
if in_pos > hint {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
hint - in_pos
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_decompressStream_simpleArgs(
dctx: *mut ZSTD_DCtx,
dst: *mut c_void,
dst_capacity: usize,
dst_pos: *mut usize,
src: *const c_void,
src_size: usize,
src_pos: *mut usize,
) -> usize {
if dst_pos.is_null() || src_pos.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
let mut output = ZSTD_outBuffer {
dst,
size: dst_capacity,
pos: unsafe { dst_pos.read() },
};
let mut input = ZSTD_inBuffer {
src,
size: src_size,
pos: unsafe { src_pos.read() },
};
let result = unsafe { ZSTD_decompressStream(dctx, &mut output, &mut input) };
unsafe {
dst_pos.write(output.pos);
src_pos.write(input.pos);
}
result
}
+1062
View File
@@ -0,0 +1,1062 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
//! Long distance matching.
//!
//! The C translation unit owns opaque compression-context dispatch and exports
//! the immutable gear table. This module owns gear splitting, LDM table
//! maintenance, raw-sequence generation, and raw-sequence consumption.
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use crate::mem::{MEM_64bits, MEM_isLittleEndian, MEM_read16, MEM_read32, MEM_readST};
use crate::xxhash::XXH64;
use std::ffi::c_void;
use std::mem::size_of;
use std::os::raw::c_int;
const LDM_BATCH_SIZE: usize = 64;
const LDM_BUCKET_SIZE_LOG: u32 = 4;
const LDM_MIN_MATCH_LENGTH: u32 = 64;
const HASH_READ_SIZE: usize = 8;
const ZSTD_REP_NUM: usize = 3;
const ZSTD_WINDOW_START_INDEX: u32 = 2;
#[repr(C)]
#[derive(Clone, Copy)]
struct LdmEntry {
offset: u32,
checksum: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct RawSeq {
offset: u32,
lit_length: u32,
match_length: u32,
}
#[repr(C)]
struct RawSeqStore {
seq: *mut RawSeq,
pos: usize,
pos_in_sequence: usize,
size: usize,
capacity: usize,
}
#[repr(C)]
struct LdmParams {
enable_ldm: c_int,
hash_log: u32,
bucket_size_log: u32,
min_match_length: u32,
hash_rate_log: u32,
window_log: u32,
}
#[repr(C)]
struct LdmWindow {
next_src: *const u8,
base: *const u8,
dict_base: *const u8,
dict_limit: u32,
low_limit: u32,
nb_overflow_corrections: u32,
}
#[derive(Clone, Copy)]
struct RollingHashState {
rolling: u64,
stop_mask: u64,
}
#[derive(Clone, Copy)]
struct MatchCandidate {
split: *const u8,
hash: u32,
checksum: u32,
bucket: *mut LdmEntry,
}
const EMPTY_CANDIDATE: MatchCandidate = MatchCandidate {
split: std::ptr::null(),
hash: 0,
checksum: 0,
bucket: std::ptr::null_mut(),
};
unsafe extern "C" {
fn ZSTD_ldm_rust_gearTable() -> *const u64;
fn ZSTD_ldm_rust_prepareBlock(context: *mut c_void, anchor: *const c_void);
fn ZSTD_ldm_rust_compressLiterals(
context: *mut c_void,
seq_store: *mut c_void,
reps: *mut u32,
src: *const c_void,
src_size: usize,
) -> usize;
fn ZSTD_ldm_rust_storeSeq(
seq_store: *mut c_void,
lit_length: usize,
literals: *const c_void,
lit_limit: *const c_void,
off_base: u32,
match_length: usize,
);
fn ZSTD_ldm_rust_setLdmSeqStore(context: *mut c_void, raw_seq_store: *const c_void);
}
#[inline]
fn ptr_lt(left: *const u8, right: *const u8) -> bool {
(left as usize) < (right as usize)
}
#[inline]
fn ptr_gt(left: *const u8, right: *const u8) -> bool {
(left as usize) > (right as usize)
}
#[inline]
unsafe fn index_from(base: *const u8, ptr: *const u8) -> u32 {
unsafe { ptr.offset_from(base) as u32 }
}
#[inline]
fn common_bytes(word: usize) -> usize {
let zeros = if MEM_isLittleEndian() {
word.trailing_zeros()
} else {
word.leading_zeros()
};
(zeros / 8) as usize
}
unsafe fn count(mut input: *const u8, mut matched: *const u8, input_limit: *const u8) -> usize {
let input_start = input;
let word_size = size_of::<usize>();
while unsafe { input_limit.offset_from(input) as usize } >= word_size {
let diff =
unsafe { MEM_readST(matched.cast::<c_void>()) ^ MEM_readST(input.cast::<c_void>()) };
if diff != 0 {
return unsafe { input.offset_from(input_start) as usize } + common_bytes(diff);
}
input = input.wrapping_add(word_size);
matched = matched.wrapping_add(word_size);
}
if MEM_64bits()
&& unsafe { input_limit.offset_from(input) as usize } >= 4
&& unsafe { MEM_read32(matched.cast::<c_void>()) == MEM_read32(input.cast::<c_void>()) }
{
input = input.wrapping_add(4);
matched = matched.wrapping_add(4);
}
if unsafe { input_limit.offset_from(input) as usize } >= 2
&& unsafe { MEM_read16(matched.cast::<c_void>()) == MEM_read16(input.cast::<c_void>()) }
{
input = input.wrapping_add(2);
matched = matched.wrapping_add(2);
}
if ptr_lt(input, input_limit) && unsafe { *input == *matched } {
input = input.wrapping_add(1);
}
unsafe { input.offset_from(input_start) as usize }
}
unsafe fn count_2segments(
input: *const u8,
matched: *const u8,
input_end: *const u8,
match_end: *const u8,
input_start: *const u8,
) -> usize {
let match_remaining = unsafe { match_end.offset_from(matched) as usize };
let input_remaining = unsafe { input_end.offset_from(input) as usize };
let first_end = input.wrapping_add(match_remaining.min(input_remaining));
let first_count = unsafe { count(input, matched, first_end) };
if matched.wrapping_add(first_count) != match_end {
return first_count;
}
first_count + unsafe { count(input.wrapping_add(first_count), input_start, input_end) }
}
#[inline]
fn bounded(lower: u32, value: u32, upper: u32) -> u32 {
value.max(lower).min(upper)
}
#[inline]
unsafe fn ldm_bucket(hash_table: *mut LdmEntry, hash: u32, bucket_size_log: u32) -> *mut LdmEntry {
unsafe { hash_table.add((hash as usize) << bucket_size_log) }
}
unsafe fn ldm_insert_entry(
hash_table: *mut LdmEntry,
bucket_offsets: *mut u8,
hash: u32,
entry: LdmEntry,
bucket_size_log: u32,
) {
let offset = unsafe { *bucket_offsets.add(hash as usize) };
let bucket = unsafe { ldm_bucket(hash_table, hash, bucket_size_log) };
unsafe { *bucket.add(offset as usize) = entry };
unsafe {
*bucket_offsets.add(hash as usize) =
offset.wrapping_add(1) & ((1u32.wrapping_shl(bucket_size_log)).wrapping_sub(1) as u8)
};
}
fn gear_init(params: &LdmParams) -> RollingHashState {
let max_bits_in_mask = params.min_match_length.min(64);
let hash_rate_log = params.hash_rate_log;
let stop_mask = if hash_rate_log > 0 && hash_rate_log <= max_bits_in_mask {
((1u64 << hash_rate_log) - 1) << (max_bits_in_mask - hash_rate_log)
} else {
(1u64 << hash_rate_log) - 1
};
RollingHashState {
/* C assigns `~(U32)0`, which is a 32-bit all-ones value. */
rolling: u32::MAX as u64,
stop_mask,
}
}
/*
* This intentionally leaves `state.rolling` unchanged: the reference C
* routine computes a local hash but never writes it back to the state.
*/
unsafe fn gear_reset(_state: &mut RollingHashState, _data: *const u8, _min_match_length: usize) {}
unsafe fn gear_feed(
state: &mut RollingHashState,
gear_table: *const u64,
data: *const u8,
size: usize,
splits: &mut [usize; LDM_BATCH_SIZE],
num_splits: &mut usize,
) -> usize {
let mut hash = state.rolling;
let mut n = 0usize;
while n + 3 < size {
for _ in 0..4 {
hash = hash
.wrapping_shl(1)
.wrapping_add(unsafe { *gear_table.add(*data.add(n) as usize) });
n += 1;
if (hash & state.stop_mask) == 0 {
splits[*num_splits] = n;
*num_splits += 1;
if *num_splits == LDM_BATCH_SIZE {
state.rolling = hash;
return n;
}
}
}
}
while n < size {
hash = hash
.wrapping_shl(1)
.wrapping_add(unsafe { *gear_table.add(*data.add(n) as usize) });
n += 1;
if (hash & state.stop_mask) == 0 {
splits[*num_splits] = n;
*num_splits += 1;
if *num_splits == LDM_BATCH_SIZE {
state.rolling = hash;
return n;
}
}
}
state.rolling = hash;
n
}
unsafe fn count_backwards_match(
mut input: *const u8,
anchor: *const u8,
mut matched: *const u8,
match_base: *const u8,
) -> usize {
let mut match_length = 0usize;
while ptr_gt(input, anchor)
&& ptr_gt(matched, match_base)
&& unsafe { *input.wrapping_sub(1) == *matched.wrapping_sub(1) }
{
input = input.wrapping_sub(1);
matched = matched.wrapping_sub(1);
match_length += 1;
}
match_length
}
unsafe fn count_backwards_match_2segments(
input: *const u8,
anchor: *const u8,
matched: *const u8,
match_base: *const u8,
ext_dict_start: *const u8,
ext_dict_end: *const u8,
) -> usize {
let match_length = unsafe { count_backwards_match(input, anchor, matched, match_base) };
if matched.wrapping_sub(match_length) != match_base || match_base == ext_dict_start {
return match_length;
}
match_length
+ unsafe {
count_backwards_match(
input.wrapping_sub(match_length),
anchor,
ext_dict_end,
ext_dict_start,
)
}
}
fn window_has_ext_dict(window: &LdmWindow) -> bool {
window.low_limit < window.dict_limit
}
fn window_can_overflow_correct(
window: &LdmWindow,
cycle_log: u32,
max_dist: u32,
loaded_dict_end: u32,
src: *const u8,
) -> bool {
let cycle_size = 1u32.wrapping_shl(cycle_log);
let current = unsafe { index_from(window.base, src) };
let min_index = cycle_size
.wrapping_add(max_dist.max(cycle_size))
.wrapping_add(ZSTD_WINDOW_START_INDEX);
let adjustment = window.nb_overflow_corrections.wrapping_add(1);
let adjusted = min_index.wrapping_mul(adjustment).max(min_index);
let index_large_enough = current > adjusted;
let dictionary_invalidated = current > max_dist.wrapping_add(loaded_dict_end);
index_large_enough && dictionary_invalidated
}
fn window_needs_overflow_correction(
window: &LdmWindow,
cycle_log: u32,
max_dist: u32,
loaded_dict_end: u32,
src: *const u8,
src_end: *const u8,
overflow_correct_frequently: bool,
) -> bool {
if overflow_correct_frequently
&& window_can_overflow_correct(window, cycle_log, max_dist, loaded_dict_end, src)
{
return true;
}
let current = unsafe { index_from(window.base, src_end) };
let current_max = if size_of::<usize>() == 8 {
3500u32 * (1 << 20)
} else {
2000u32 * (1 << 20)
};
current > current_max
}
unsafe fn window_correct_overflow(
window: &mut LdmWindow,
cycle_log: u32,
max_dist: u32,
src: *const u8,
) -> u32 {
let cycle_size = 1u32.wrapping_shl(cycle_log);
let cycle_mask = cycle_size.wrapping_sub(1);
let current = unsafe { index_from(window.base, src) };
let current_cycle = current & cycle_mask;
let current_cycle_correction = if current_cycle < ZSTD_WINDOW_START_INDEX {
cycle_size.max(ZSTD_WINDOW_START_INDEX)
} else {
0
};
let new_current = current_cycle
.wrapping_add(current_cycle_correction)
.wrapping_add(max_dist.max(cycle_size));
let correction = current.wrapping_sub(new_current);
window.base = window.base.wrapping_add(correction as usize);
window.dict_base = window.dict_base.wrapping_add(correction as usize);
if window.low_limit < correction.wrapping_add(ZSTD_WINDOW_START_INDEX) {
window.low_limit = ZSTD_WINDOW_START_INDEX;
} else {
window.low_limit = window.low_limit.wrapping_sub(correction);
}
if window.dict_limit < correction.wrapping_add(ZSTD_WINDOW_START_INDEX) {
window.dict_limit = ZSTD_WINDOW_START_INDEX;
} else {
window.dict_limit = window.dict_limit.wrapping_sub(correction);
}
window.nb_overflow_corrections = window.nb_overflow_corrections.wrapping_add(1);
correction
}
fn window_enforce_max_dist(
window: &mut LdmWindow,
block_end: *const u8,
max_dist: u32,
loaded_dict_end: &mut u32,
) {
let block_end_index = unsafe { index_from(window.base, block_end) };
if block_end_index > max_dist.wrapping_add(*loaded_dict_end) {
let new_low_limit = block_end_index.wrapping_sub(max_dist);
if window.low_limit < new_low_limit {
window.low_limit = new_low_limit;
}
if window.dict_limit < window.low_limit {
window.dict_limit = window.low_limit;
}
*loaded_dict_end = 0;
}
}
unsafe fn reduce_table(table: *mut LdmEntry, size: u32, reducer_value: u32) {
for index in 0..size as usize {
let entry = unsafe { table.add(index) };
if unsafe { (*entry).offset < reducer_value } {
unsafe { (*entry).offset = 0 };
} else {
unsafe { (*entry).offset = (*entry).offset.wrapping_sub(reducer_value) };
}
}
}
unsafe fn generate_sequences_internal(
hash_table: *mut LdmEntry,
bucket_offsets: *mut u8,
window: &LdmWindow,
raw_seq_store: *mut RawSeqStore,
params: &LdmParams,
gear_table: *const u64,
src: *const u8,
src_size: usize,
) -> usize {
let ext_dict = window_has_ext_dict(window);
let min_match_length = params.min_match_length as usize;
let entries_per_bucket = 1usize << params.bucket_size_log;
let hbits = params.hash_log.wrapping_sub(params.bucket_size_log);
let dict_limit = window.dict_limit;
let lowest_index = if ext_dict {
window.low_limit
} else {
dict_limit
};
let base = window.base;
let dict_base = window.dict_base;
let dict_start = dict_base.wrapping_add(lowest_index as usize);
let dict_end = dict_base.wrapping_add(dict_limit as usize);
let low_prefix_ptr = base.wrapping_add(dict_limit as usize);
let iend = src.wrapping_add(src_size);
let ilimit = iend.wrapping_sub(HASH_READ_SIZE);
let mut anchor = src;
let mut ip = src;
if src_size < min_match_length {
return src_size;
}
let mut hash_state = gear_init(params);
unsafe { gear_reset(&mut hash_state, ip, min_match_length) };
ip = ip.wrapping_add(min_match_length);
while ptr_lt(ip, ilimit) {
let mut splits = [0usize; LDM_BATCH_SIZE];
let mut num_splits = 0usize;
let hashed = unsafe {
gear_feed(
&mut hash_state,
gear_table,
ip,
ilimit.offset_from(ip) as usize,
&mut splits,
&mut num_splits,
)
};
let mut candidates = [EMPTY_CANDIDATE; LDM_BATCH_SIZE];
for index in 0..num_splits {
let split = ip
.wrapping_add(splits[index])
.wrapping_sub(min_match_length);
let xxhash = unsafe { XXH64(split.cast::<c_void>(), min_match_length, 0) };
let hash = (xxhash as u32) & ((1u32 << hbits) - 1);
candidates[index] = MatchCandidate {
split,
hash,
checksum: (xxhash >> 32) as u32,
bucket: unsafe { ldm_bucket(hash_table, hash, params.bucket_size_log) },
};
}
for candidate in candidates.iter().take(num_splits) {
let split = candidate.split;
let new_entry = LdmEntry {
offset: unsafe { index_from(base, split) },
checksum: candidate.checksum,
};
if ptr_lt(split, anchor) {
unsafe {
ldm_insert_entry(
hash_table,
bucket_offsets,
candidate.hash,
new_entry,
params.bucket_size_log,
)
};
continue;
}
let mut forward_match_length = 0usize;
let mut backward_match_length = 0usize;
let mut best_match_length = 0usize;
let mut best_offset = None;
for entry_index in 0..entries_per_bucket {
let entry = unsafe { *candidate.bucket.add(entry_index) };
if entry.checksum != candidate.checksum || entry.offset <= lowest_index {
continue;
}
let (current_forward, current_backward) = if ext_dict {
let match_base = if entry.offset < dict_limit {
dict_base
} else {
base
};
let matched = match_base.wrapping_add(entry.offset as usize);
let match_end = if entry.offset < dict_limit {
dict_end
} else {
iend
};
let low_match = if entry.offset < dict_limit {
dict_start
} else {
low_prefix_ptr
};
let forward =
unsafe { count_2segments(split, matched, iend, match_end, low_prefix_ptr) };
if forward < min_match_length {
continue;
}
let backward = unsafe {
count_backwards_match_2segments(
split, anchor, matched, low_match, dict_start, dict_end,
)
};
(forward, backward)
} else {
let matched = base.wrapping_add(entry.offset as usize);
let forward = unsafe { count(split, matched, iend) };
if forward < min_match_length {
continue;
}
let backward =
unsafe { count_backwards_match(split, anchor, matched, low_prefix_ptr) };
(forward, backward)
};
let total = current_forward + current_backward;
if total > best_match_length {
best_match_length = total;
forward_match_length = current_forward;
backward_match_length = current_backward;
best_offset = Some(entry.offset);
}
}
let Some(best_offset) = best_offset else {
unsafe {
ldm_insert_entry(
hash_table,
bucket_offsets,
candidate.hash,
new_entry,
params.bucket_size_log,
)
};
continue;
};
let raw_seq_store = unsafe { &mut *raw_seq_store };
if raw_seq_store.size == raw_seq_store.capacity {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let sequence = unsafe { raw_seq_store.seq.add(raw_seq_store.size) };
unsafe {
(*sequence).lit_length = split
.wrapping_sub(backward_match_length)
.offset_from(anchor) as u32;
(*sequence).match_length = (forward_match_length + backward_match_length) as u32;
(*sequence).offset = index_from(base, split).wrapping_sub(best_offset);
}
raw_seq_store.size += 1;
unsafe {
ldm_insert_entry(
hash_table,
bucket_offsets,
candidate.hash,
new_entry,
params.bucket_size_log,
)
};
anchor = split.wrapping_add(forward_match_length);
if ptr_gt(anchor, ip.wrapping_add(hashed)) {
unsafe {
gear_reset(
&mut hash_state,
anchor.wrapping_sub(min_match_length),
min_match_length,
)
};
ip = anchor.wrapping_sub(hashed);
break;
}
}
ip = ip.wrapping_add(hashed);
}
unsafe { iend.offset_from(anchor) as usize }
}
/// Rust implementation called by the C ABI wrapper for parameter adjustment.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_adjustParameters(
params: *mut c_void,
window_log: u32,
strategy: c_int,
hash_log_max: u32,
bucket_size_log_max: u32,
btultra: c_int,
) {
let params = unsafe { &mut *params.cast::<LdmParams>() };
params.window_log = window_log;
if params.hash_rate_log == 0 {
if params.hash_log > 0 {
if params.window_log > params.hash_log {
params.hash_rate_log = params.window_log - params.hash_log;
}
} else {
params.hash_rate_log = 7u32.wrapping_sub((strategy / 3) as u32);
}
}
if params.hash_log == 0 {
params.hash_log = bounded(
6,
params.window_log.wrapping_sub(params.hash_rate_log),
hash_log_max,
);
}
if params.min_match_length == 0 {
params.min_match_length = LDM_MIN_MATCH_LENGTH;
if strategy >= btultra {
params.min_match_length /= 2;
}
}
if params.bucket_size_log == 0 {
params.bucket_size_log = bounded(LDM_BUCKET_SIZE_LOG, strategy as u32, bucket_size_log_max);
}
params.bucket_size_log = params.bucket_size_log.min(params.hash_log);
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_getTableSize(
params: *const c_void,
enable_ldm: c_int,
redzone_size: usize,
) -> usize {
let params = unsafe { &*params.cast::<LdmParams>() };
let hash_size = 1usize << params.hash_log;
let bucket_log = params.bucket_size_log.min(params.hash_log);
let bucket_size = 1usize << (params.hash_log - bucket_log);
let alloc_size = |size: usize| {
if size == 0 {
0
} else {
size + 2 * redzone_size
}
};
if enable_ldm != 0 {
alloc_size(bucket_size) + alloc_size(hash_size * size_of::<LdmEntry>())
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_getMaxNbSeq(
params: *const c_void,
enable_ldm: c_int,
max_chunk_size: usize,
) -> usize {
let params = unsafe { &*params.cast::<LdmParams>() };
if enable_ldm != 0 {
max_chunk_size / params.min_match_length as usize
} else {
0
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_fillHashTable(
hash_table: *mut c_void,
bucket_offsets: *mut u8,
base: *const u8,
mut input: *const u8,
input_end: *const u8,
params: *const c_void,
) {
let hash_table = hash_table.cast::<LdmEntry>();
let params = unsafe { &*params.cast::<LdmParams>() };
let min_match_length = params.min_match_length as usize;
let hbits = params.hash_log.wrapping_sub(params.bucket_size_log);
let input_start = input;
let gear_table = unsafe { ZSTD_ldm_rust_gearTable() };
let mut hash_state = gear_init(params);
while ptr_lt(input, input_end) {
let mut splits = [0usize; LDM_BATCH_SIZE];
let mut num_splits = 0usize;
let hashed = unsafe {
gear_feed(
&mut hash_state,
gear_table,
input,
input_end.offset_from(input) as usize,
&mut splits,
&mut num_splits,
)
};
for split_index in splits.iter().take(num_splits) {
if input.wrapping_add(*split_index) >= input_start.wrapping_add(min_match_length) {
let split = input
.wrapping_add(*split_index)
.wrapping_sub(min_match_length);
let xxhash = unsafe { XXH64(split.cast::<c_void>(), min_match_length, 0) };
let hash = (xxhash as u32) & ((1u32 << hbits) - 1);
unsafe {
ldm_insert_entry(
hash_table,
bucket_offsets,
hash,
LdmEntry {
offset: index_from(base, split),
checksum: (xxhash >> 32) as u32,
},
params.bucket_size_log,
)
};
}
}
input = input.wrapping_add(hashed);
}
}
/// Rust implementation called by the C ABI wrapper for LDM sequence generation.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_generateSequences(
hash_table: *mut c_void,
bucket_offsets: *mut u8,
window: *mut c_void,
loaded_dict_end: *mut u32,
raw_seq_store: *mut c_void,
params: *const c_void,
src: *const c_void,
src_size: usize,
overflow_correct_frequently: c_int,
) -> usize {
let hash_table = hash_table.cast::<LdmEntry>();
let params = unsafe { &*params.cast::<LdmParams>() };
let window = unsafe { &mut *window.cast::<LdmWindow>() };
let raw_seq_store = raw_seq_store.cast::<RawSeqStore>();
let max_dist = 1u32.wrapping_shl(params.window_log);
let input = src.cast::<u8>();
let input_end = input.wrapping_add(src_size);
const MAX_CHUNK_SIZE: usize = 1 << 20;
let num_chunks =
src_size / MAX_CHUNK_SIZE + usize::from(!src_size.is_multiple_of(MAX_CHUNK_SIZE));
let mut leftover_size = 0usize;
let gear_table = unsafe { ZSTD_ldm_rust_gearTable() };
for chunk in 0..num_chunks {
let chunk_start = input.wrapping_add(chunk * MAX_CHUNK_SIZE);
let remaining = unsafe { input_end.offset_from(chunk_start) as usize };
let chunk_end = if remaining < MAX_CHUNK_SIZE {
input_end
} else {
chunk_start.wrapping_add(MAX_CHUNK_SIZE)
};
let chunk_size = unsafe { chunk_end.offset_from(chunk_start) as usize };
let raw_seq_store_ref = unsafe { &mut *raw_seq_store };
if raw_seq_store_ref.size >= raw_seq_store_ref.capacity {
break;
}
let previous_size = raw_seq_store_ref.size;
let loaded = unsafe { &mut *loaded_dict_end };
if window_needs_overflow_correction(
window,
0,
max_dist,
*loaded,
chunk_start,
chunk_end,
overflow_correct_frequently != 0,
) {
let hash_size = 1u32.wrapping_shl(params.hash_log);
let correction = unsafe { window_correct_overflow(window, 0, max_dist, chunk_start) };
unsafe { reduce_table(hash_table, hash_size, correction) };
*loaded = 0;
}
window_enforce_max_dist(window, chunk_end, max_dist, loaded);
let leftover = unsafe {
generate_sequences_internal(
hash_table,
bucket_offsets,
window,
raw_seq_store,
params,
gear_table,
chunk_start,
chunk_size,
)
};
if ERR_isError(leftover) {
return leftover;
}
let raw_seq_store_ref = unsafe { &mut *raw_seq_store };
if previous_size < raw_seq_store_ref.size {
unsafe {
(*raw_seq_store_ref.seq.add(previous_size)).lit_length =
(*raw_seq_store_ref.seq.add(previous_size))
.lit_length
.wrapping_add(leftover_size as u32)
};
leftover_size = leftover;
} else {
leftover_size += chunk_size;
}
}
0
}
unsafe fn skip_sequences(raw_seq_store: *mut RawSeqStore, mut src_size: usize, min_match: u32) {
let raw_seq_store = unsafe { &mut *raw_seq_store };
while src_size > 0 && raw_seq_store.pos < raw_seq_store.size {
let sequence = unsafe { raw_seq_store.seq.add(raw_seq_store.pos) };
if src_size <= unsafe { (*sequence).lit_length as usize } {
unsafe {
(*sequence).lit_length = (*sequence).lit_length.wrapping_sub(src_size as u32)
};
return;
}
src_size -= unsafe { (*sequence).lit_length as usize };
unsafe { (*sequence).lit_length = 0 };
if src_size < unsafe { (*sequence).match_length as usize } {
unsafe {
(*sequence).match_length = (*sequence).match_length.wrapping_sub(src_size as u32)
};
if unsafe { (*sequence).match_length < min_match } {
if raw_seq_store.pos + 1 < raw_seq_store.size {
unsafe {
(*sequence.add(1)).lit_length = (*sequence.add(1))
.lit_length
.wrapping_add((*sequence).match_length)
};
}
raw_seq_store.pos += 1;
}
return;
}
src_size -= unsafe { (*sequence).match_length as usize };
unsafe { (*sequence).match_length = 0 };
raw_seq_store.pos += 1;
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_skipSequences(
raw_seq_store: *mut c_void,
src_size: usize,
min_match: u32,
) {
unsafe { skip_sequences(raw_seq_store.cast::<RawSeqStore>(), src_size, min_match) };
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_skipRawSeqStoreBytes(
raw_seq_store: *mut c_void,
nb_bytes: usize,
) {
let raw_seq_store = unsafe { &mut *raw_seq_store.cast::<RawSeqStore>() };
let mut current_position = raw_seq_store.pos_in_sequence.wrapping_add(nb_bytes) as u32;
while current_position != 0 && raw_seq_store.pos < raw_seq_store.size {
let sequence = unsafe { *raw_seq_store.seq.add(raw_seq_store.pos) };
if current_position >= sequence.lit_length.wrapping_add(sequence.match_length) {
current_position = current_position
.wrapping_sub(sequence.lit_length)
.wrapping_sub(sequence.match_length);
raw_seq_store.pos += 1;
} else {
raw_seq_store.pos_in_sequence = current_position as usize;
break;
}
}
if current_position == 0 || raw_seq_store.pos == raw_seq_store.size {
raw_seq_store.pos_in_sequence = 0;
}
}
unsafe fn maybe_split_sequence(
raw_seq_store: *mut RawSeqStore,
remaining: u32,
min_match: u32,
) -> RawSeq {
let raw_seq_store_ref = unsafe { &mut *raw_seq_store };
let mut sequence = unsafe { *raw_seq_store_ref.seq.add(raw_seq_store_ref.pos) };
if remaining >= sequence.lit_length.wrapping_add(sequence.match_length) {
raw_seq_store_ref.pos += 1;
return sequence;
}
if remaining <= sequence.lit_length {
sequence.offset = 0;
} else {
sequence.match_length = remaining.wrapping_sub(sequence.lit_length);
if sequence.match_length < min_match {
sequence.offset = 0;
}
}
unsafe { skip_sequences(raw_seq_store, remaining as usize, min_match) };
sequence
}
/// Rust implementation called by the C ABI wrapper for LDM block integration.
#[no_mangle]
pub unsafe extern "C" fn ZSTD_rust_ldm_blockCompress(
raw_seq_store: *mut c_void,
block_context: *mut c_void,
seq_store: *mut c_void,
reps: *mut u32,
src: *const c_void,
src_size: usize,
min_match: u32,
use_optimal_parser: c_int,
) -> usize {
let raw_seq_store = raw_seq_store.cast::<RawSeqStore>();
let input = src.cast::<u8>();
let input_end = input.wrapping_add(src_size);
if use_optimal_parser != 0 {
unsafe { ZSTD_ldm_rust_setLdmSeqStore(block_context, raw_seq_store.cast::<c_void>()) };
let last_literals = unsafe {
ZSTD_ldm_rust_compressLiterals(block_context, seq_store, reps, src, src_size)
};
unsafe { ZSTD_rust_ldm_skipRawSeqStoreBytes(raw_seq_store.cast::<c_void>(), src_size) };
return last_literals;
}
let mut input_position = input;
while unsafe { (*raw_seq_store).pos < (*raw_seq_store).size }
&& ptr_lt(input_position, input_end)
{
let sequence = unsafe {
maybe_split_sequence(
raw_seq_store,
input_end.offset_from(input_position) as u32,
min_match,
)
};
if sequence.offset == 0 {
break;
}
unsafe { ZSTD_ldm_rust_prepareBlock(block_context, input_position.cast::<c_void>()) };
let new_lit_length = unsafe {
ZSTD_ldm_rust_compressLiterals(
block_context,
seq_store,
reps,
input_position.cast::<c_void>(),
sequence.lit_length as usize,
)
};
input_position = input_position.wrapping_add(sequence.lit_length as usize);
unsafe {
*reps.add(2) = *reps.add(1);
*reps.add(1) = *reps;
*reps = sequence.offset;
ZSTD_ldm_rust_storeSeq(
seq_store,
new_lit_length,
input_position.wrapping_sub(new_lit_length).cast::<c_void>(),
input_end.cast::<c_void>(),
sequence.offset.wrapping_add(ZSTD_REP_NUM as u32),
sequence.match_length as usize,
);
}
input_position = input_position.wrapping_add(sequence.match_length as usize);
}
unsafe { ZSTD_ldm_rust_prepareBlock(block_context, input_position.cast::<c_void>()) };
unsafe {
ZSTD_ldm_rust_compressLiterals(
block_context,
seq_store,
reps,
input_position.cast::<c_void>(),
input_end.offset_from(input_position) as usize,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parameter_defaults_follow_the_c_rules() {
let mut params = LdmParams {
enable_ldm: 1,
hash_log: 0,
bucket_size_log: 0,
min_match_length: 0,
hash_rate_log: 0,
window_log: 0,
};
unsafe {
ZSTD_rust_ldm_adjustParameters(
(&mut params as *mut LdmParams).cast::<c_void>(),
20,
3,
30,
8,
8,
)
};
assert_eq!(params.window_log, 20);
assert_eq!(params.hash_rate_log, 6);
assert_eq!(params.hash_log, 14);
assert_eq!(params.bucket_size_log, 4);
assert_eq!(params.min_match_length, 64);
}
#[test]
fn raw_sequence_skipping_merges_short_tail_matches() {
let mut sequences = [
RawSeq {
offset: 8,
lit_length: 2,
match_length: 10,
},
RawSeq {
offset: 9,
lit_length: 1,
match_length: 12,
},
];
let mut store = RawSeqStore {
seq: sequences.as_mut_ptr(),
pos: 0,
pos_in_sequence: 0,
size: sequences.len(),
capacity: sequences.len(),
};
unsafe { skip_sequences(&mut store, 10, 4) };
assert_eq!(store.pos, 1);
assert_eq!(sequences[1].lit_length, 3);
}
}