diff --git a/lib/compress/zstd_ldm.c b/lib/compress/zstd_ldm.c index 070551cad..c7e1d558c 100644 --- a/lib/compress/zstd_ldm.c +++ b/lib/compress/zstd_ldm.c @@ -8,265 +8,225 @@ * 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 "../common/debug.h" -#include "../common/xxhash.h" -#include "zstd_fast.h" /* ZSTD_fillHashTable() */ -#include "zstd_double_fast.h" /* ZSTD_fillDoubleHashTable() */ +#include "zstd_fast.h" +#include "zstd_double_fast.h" #include "zstd_ldm_geartab.h" -#define LDM_BUCKET_SIZE_LOG 4 -#define LDM_MIN_MATCH_LENGTH 64 -#define LDM_HASH_RLOG 7 +typedef struct { + U32 offset; + U32 checksum; +} ZSTD_rust_ldm_entry_layout; typedef struct { - U64 rolling; - U64 stopMask; -} ldmRollingHashState_t; + U32 offset; + U32 litLength; + U32 matchLength; +} ZSTD_rust_raw_seq_layout; -/** ZSTD_ldm_gear_init(): - * - * Initializes the rolling hash state such that it will honor the - * settings in params. */ -static void ZSTD_ldm_gear_init(ldmRollingHashState_t* state, ldmParams_t const* params) +typedef struct { + rawSeq* seq; + size_t pos; + size_t posInSequence; + 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); - unsigned hashRateLog = params->hashRateLog; + return ZSTD_ldm_gearTab; +} - state->rolling = ~(U32)0; +typedef struct { + ZSTD_MatchState_t* ms; + ZSTD_BlockCompressor_f blockCompressor; +} ZSTD_rust_ldm_block_context; - /* 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; +static void ZSTD_rust_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); } } -/** ZSTD_ldm_gear_reset() - * Feeds [data, data + minMatchLength) into the hash without registering any - * splits. This effectively resets the hash state. This is used when skipping - * over data, either at the beginning of a block, or skipping sections. - */ -static void ZSTD_ldm_gear_reset(ldmRollingHashState_t* state, - BYTE const* data, size_t minMatchLength) +static void ZSTD_rust_ldm_fillFastTables(ZSTD_MatchState_t* ms, const BYTE* end) { - U64 hash = state->rolling; - size_t n = 0; - -#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(): - * - * 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: - ZSTD_fillHashTable(ms, iend, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); + ZSTD_fillHashTable(ms, end, ZSTD_dtlm_fast, ZSTD_tfp_forCCtx); break; - case ZSTD_dfast: #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 - assert(0); /* shouldn't be called: cparams should've been adjusted. */ + assert(0); #endif break; - case ZSTD_greedy: case ZSTD_lazy: case ZSTD_lazy2: @@ -276,406 +236,95 @@ static size_t ZSTD_ldm_fillFastTables(ZSTD_MatchState_t* ms, case ZSTD_btultra2: break; 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( + ¶ms, params.enableLdm == ZSTD_ps_enable, redzoneSize); +} + +size_t ZSTD_ldm_getMaxNbSeq(ldmParams_t params, size_t maxChunkSize) +{ + return ZSTD_rust_ldm_getMaxNbSeq( + ¶ms, params.enableLdm == ZSTD_ps_enable, maxChunkSize); } void ZSTD_ldm_fillHashTable( - ldmState_t* ldmState, const BYTE* ip, - const BYTE* iend, ldmParams_t const* params) + ldmState_t* ldmState, const BYTE* ip, + const BYTE* iend, ldmParams_t const* params) { - U32 const minMatchLength = params->minMatchLength; - U32 const bucketSizeLog = params->bucketSizeLog; - U32 const hBits = params->hashLog - bucketSizeLog; - 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; - } + ZSTD_rust_ldm_fillHashTable( + ldmState->hashTable, ldmState->bucketOffsets, ldmState->window.base, + ip, iend, params); } size_t ZSTD_ldm_generateSequences( ldmState_t* ldmState, RawSeqStore_t* sequences, ldmParams_t const* params, void const* src, size_t srcSize) { - U32 const maxDist = 1U << params->windowLog; - 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(ldmState->window.nextSrc >= (const BYTE*)src + srcSize); assert(sequences->pos <= sequences->size); assert(sequences->size <= sequences->capacity); - for (chunk = 0; chunk < nbChunks && sequences->size < sequences->capacity; ++chunk) { - BYTE const* const chunkStart = istart + chunk * kMaxChunkSize; - size_t const remaining = (size_t)(iend - chunkStart); - BYTE const *const chunkEnd = - (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; + return ZSTD_rust_ldm_generateSequences( + ldmState->hashTable, ldmState->bucketOffsets, &ldmState->window, + &ldmState->loadedDictEnd, sequences, params, src, srcSize, + ZSTD_WINDOW_OVERFLOW_CORRECT_FREQUENTLY); } -void -ZSTD_ldm_skipSequences(RawSeqStore_t* rawSeqStore, size_t srcSize, U32 const minMatch) +void ZSTD_ldm_skipSequences(RawSeqStore_t* rawSeqStore, size_t srcSize, + U32 const minMatch) { - while (srcSize > 0 && rawSeqStore->pos < rawSeqStore->size) { - 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++; - } + ZSTD_rust_ldm_skipSequences(rawSeqStore, srcSize, minMatch); } -/** - * 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) +void ZSTD_ldm_skipRawSeqStoreBytes(RawSeqStore_t* rawSeqStore, size_t nbBytes) { - rawSeq sequence = rawSeqStore->seq[rawSeqStore->pos]; - 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; - } + ZSTD_rust_ldm_skipRawSeqStoreBytes(rawSeqStore, nbBytes); } size_t ZSTD_ldm_blockCompress(RawSeqStore_t* rawSeqStore, @@ -683,63 +332,12 @@ size_t ZSTD_ldm_blockCompress(RawSeqStore_t* rawSeqStore, ZSTD_ParamSwitch_e useRowMatchFinder, void const* src, size_t srcSize) { - const ZSTD_compressionParameters* const cParams = &ms->cParams; - unsigned const minMatch = cParams->minMatch; - ZSTD_BlockCompressor_f const blockCompressor = - ZSTD_selectBlockCompressor(cParams->strategy, useRowMatchFinder, ZSTD_matchState_dictMode(ms)); - /* Input bounds */ - BYTE const* const istart = (BYTE const*)src; - BYTE const* const iend = istart + srcSize; - /* Input positions */ - 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); + ZSTD_rust_ldm_block_context context; + context.ms = ms; + context.blockCompressor = ZSTD_selectBlockCompressor( + ms->cParams.strategy, useRowMatchFinder, ZSTD_matchState_dictMode(ms)); + assert(context.blockCompressor != NULL); + return ZSTD_rust_ldm_blockCompress( + rawSeqStore, &context, seqStore, rep, src, srcSize, + ms->cParams.minMatch, ms->cParams.strategy >= ZSTD_btopt); } diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 9eb98327e..521f2bb84 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -1,925 +1,336 @@ /* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * All rights reserved. + * Decoder orchestration is implemented in rust/src/zstd_decompress.rs. * - * This source code is licensed under both the BSD-style license (found in the - * LICENSE file in the root directory of this source tree) and the GPLv2 (found - * in the COPYING file in the root directory of this source tree). - * You may select, at your option, one of the above-listed licenses. + * `ZSTD_DCtx_s` is intentionally still allocated and laid out by C: its + * optional members vary with the build configuration and the block decoder + * shares that object. This translation unit is therefore a deliberately + * 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" +#include "../common/allocations.h" +#include "../common/error_private.h" +#include "../common/mem.h" +#include "../common/zstd_internal.h" +#include "zstd_decompress_internal.h" +#include "zstd_ddict.h" -/* *************************************************************** -* Tuning parameters -*****************************************************************/ -/*! - * 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. - */ +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) +# include "../legacy/zstd_legacy.h" +#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. */ #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 +/* 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; -/*-******************************************************* -* 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/zstd_internal.h" /* blockProperties_t */ -#include "../common/mem.h" /* low level memory routines */ -#include "../common/bits.h" /* ZSTD_highbit32 */ -#define FSE_STATIC_LINKING_ONLY -#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 */ +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); -#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1) -# include "../legacy/zstd_legacy.h" +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 - - - -/************************************* - * Multiple DDicts Hashset internals * - *************************************/ - -#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; + 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 - dctx->noForwardProgress = 0; - dctx->oversizedDuration = 0; - dctx->isFrameDecompression = 1; + 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 dctx->bmi2 = ZSTD_cpuSupportsBmi2(); -#endif - dctx->ddictSet = NULL; - ZSTD_DCtx_resetParameters(dctx); -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - dctx->dictContentEndForFuzzing = NULL; +#else + (void)dctx; #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; - - 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; + return ZSTD_MAXWINDOWSIZE_DEFAULT; } -static ZSTD_DCtx* ZSTD_createDCtx_internal(ZSTD_customMem customMem) { - 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; - } +int ZSTD_rust_no_forward_progress_max(void) +{ + return ZSTD_NO_FORWARD_PROGRESS_MAX; } -ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem) +int ZSTD_rust_heapmode(void) { - return ZSTD_createDCtx_internal(customMem); + return ZSTD_HEAPMODE; } -ZSTD_DCtx* ZSTD_createDCtx(void) +size_t ZSTD_rust_decompress_stack(void* dst, size_t dstCapacity, + const void* src, size_t srcSize) { - DEBUGLOG(3, "ZSTD_createDCtx"); - return ZSTD_createDCtx_internal(ZSTD_defaultCMem); -} - -static void ZSTD_clearDict(ZSTD_DCtx* dctx) -{ - 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); +#if ZSTD_HEAPMODE >= 1 + (void)dst; + (void)dstCapacity; + (void)src; + (void)srcSize; + return ERROR(GENERIC); +#else + ZSTD_DCtx dctx; + ZSTD_DCtx* const initialized = ZSTD_initStaticDCtx(&dctx, sizeof(dctx)); + if (initialized == NULL) return ERROR(memory_allocation); + /* This is a stack DCtx, not a user-provided static workspace. Keep the + * original heapmode=0 semantics so legacy decoding is permitted and no + * static-context allocation restrictions leak into the one-shot API. */ + initialized->staticSize = 0; + return ZSTD_decompressDCtx(initialized, dst, dstCapacity, src, srcSize); #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) +void* ZSTD_rust_custom_malloc(size_t size, ZSTD_customMem customMem) { - size_t const toCopy = (size_t)((char*)(&dstDCtx->inBuff) - (char*)dstDCtx); - ZSTD_memcpy(dstDCtx, srcDCtx, toCopy); /* no need to copy workspace */ + return ZSTD_customMalloc(size, customMem); } -/* 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) +void* ZSTD_rust_custom_calloc(size_t size, ZSTD_customMem customMem) { - 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; + return ZSTD_customCalloc(size, customMem); +} + +void ZSTD_rust_custom_free(void* allocation, ZSTD_customMem customMem) +{ + 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 - 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) -{ - RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, ""); - - { U32 const magicNumber = MEM_readLE32(src); - size_t skippableFrameSize = readSkippableFrameSize(src, srcSize); - size_t skippableContentSize = skippableFrameSize - ZSTD_SKIPPABLEHEADERSIZE; - - /* check input validity */ - RETURN_ERROR_IF(!ZSTD_isSkippableFrame(src, srcSize), frameParameter_unsupported, ""); - RETURN_ERROR_IF(skippableFrameSize < ZSTD_SKIPPABLEHEADERSIZE || skippableFrameSize > srcSize, srcSize_wrong, ""); - RETURN_ERROR_IF(skippableContentSize > dstCapacity, dstSize_tooSmall, ""); - - /* deliver payload */ - if (skippableContentSize > 0 && dst != NULL) - ZSTD_memcpy(dst, (const BYTE *)src + ZSTD_SKIPPABLEHEADERSIZE, skippableContentSize); - if (magicVariant != NULL) - *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 - 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) -{ - ZSTD_frameSizeInfo frameSizeInfo; - 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) -{ - ZSTD_frameSizeInfo frameSizeInfo; - ZSTD_memset(&frameSizeInfo, 0, sizeof(ZSTD_frameSizeInfo)); - -#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) - if (format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) - return ZSTD_findFrameSizeInfoLegacy(src, srcSize); -#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; - remainingSize -= zfh.headerSize; - - /* 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) +void ZSTD_rust_dctx_trace_end(ZSTD_DCtx* dctx, U64 uncompressedSize, + U64 compressedSize, int streaming) { #if ZSTD_TRACE 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 } - -/*! 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) +unsigned ZSTD_rust_legacy_is(const void* src, size_t srcSize) { - 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 (dctx->format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) { - size_t decodedSize; - size_t const frameSize = ZSTD_findFrameCompressedSizeLegacy(src, srcSize); - if (ZSTD_isError(frameSize)) return frameSize; - RETURN_ERROR_IF(dctx->staticSize, memory_allocation, - "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 - - 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, - void* dst, size_t dstCapacity, - const void* src, size_t srcSize, - const void* dict, size_t dictSize) -{ - return ZSTD_decompressMultiFrame(dctx, dst, dstCapacity, src, srcSize, dict, dictSize, NULL); -} - - -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); + return ZSTD_isLegacy(src, srcSize); #else - size_t const hSize = HUF_readDTableX2_wksp(entropy->hufTable, - dictPtr, (size_t)(dictEnd - dictPtr), - workspace, workspaceSize, /* flags */ 0); + (void)src; + (void)srcSize; + return 0; #endif - RETURN_ERROR_IF(HUF_isError(hSize), dictionary_corrupted, ""); - dictPtr += hSize; - } - - { short offcodeNCount[MaxOff+1]; - unsigned offcodeMaxValue = MaxOff, offcodeLog; - size_t const offcodeHeaderSize = FSE_readNCount(offcodeNCount, &offcodeMaxValue, &offcodeLog, dictPtr, (size_t)(dictEnd-dictPtr)); - RETURN_ERROR_IF(FSE_isError(offcodeHeaderSize), dictionary_corrupted, ""); - 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) +unsigned long long ZSTD_rust_legacy_get_decompressed_size(const void* src, size_t srcSize) { - 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; +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) + return ZSTD_getDecompressedSize_legacy(src, srcSize); +#else + (void)src; + (void)srcSize; + return 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) +size_t ZSTD_rust_legacy_find_compressed_size(const void* src, size_t srcSize) { - 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) - zds->legacyVersion = 0; +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) + return ZSTD_findFrameCompressedSizeLegacy(src, srcSize); +#else + (void)src; + (void)srcSize; + return ERROR(prefix_unknown); #endif - zds->hostageByte = 0; - zds->expectedOutBuffer = *output; - ZSTD_FALLTHROUGH; - - 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; - } } -#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) - U32 const legacyVersion = ZSTD_isLegacy(istart, iend-istart); - if (legacyVersion) { - ZSTD_DDict const* const ddict = ZSTD_getDDict(zds); - const void* const dict = ddict ? ZSTD_DDict_dictContent(ddict) : NULL; - size_t const dictSize = ddict ? ZSTD_DDict_dictSize(ddict) : 0; - DEBUGLOG(5, "ZSTD_decompressStream: detected legacy version v0.%u", legacyVersion); - RETURN_ERROR_IF(zds->staticSize, memory_allocation, - "legacy support is incompatible with static dctx"); - FORWARD_IF_ERROR(ZSTD_initLegacyStream(&zds->legacyContext, - zds->previousLegacyVersion, legacyVersion, - dict, dictSize), ""); - zds->legacyVersion = zds->previousLegacyVersion = legacyVersion; - { size_t const hint = ZSTD_decompressLegacyStream(zds->legacyContext, legacyVersion, output, input); - if (hint==0) zds->streamStage = zdss_init; /* or stay in stage zdss_loadHeader */ - return hint; - } } -#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) */ - 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; - size_t const flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize); - - op = op ? op + flushedSize : op; - - 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) +size_t ZSTD_rust_legacy_frame_size_info(const void* src, size_t srcSize, + size_t* compressedSize, + unsigned long long* decompressedBound, + size_t* nbBlocks) { - 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; +#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* dict, size_t dictSize) +{ +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) + return ZSTD_decompressLegacy(dst, dstCapacity, src, srcSize, dict, dictSize); +#else + (void)dst; + (void)dstCapacity; + (void)src; + (void)srcSize; + (void)dict; + (void)dictSize; + return ERROR(prefix_unknown); +#endif +} + +size_t ZSTD_rust_legacy_decompress_stream(ZSTD_DCtx* dctx, + ZSTD_outBuffer* output, + ZSTD_inBuffer* input, + const void* dict, size_t dictSize) +{ +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) + size_t hint; + if (dctx->legacyVersion) { + hint = ZSTD_decompressLegacyStream(dctx->legacyContext, + dctx->legacyVersion, output, input); + if (hint == 0) dctx->streamStage = zdss_init; + return hint; } + { + const char* const istart = input->pos != 0 + ? (const char*)input->src + input->pos + : (const char*)input->src; + size_t const inputSize = input->size - input->pos; + U32 const legacyVersion = ZSTD_isLegacy(istart, inputSize); + if (!legacyVersion) return ERROR(prefix_unknown); + if (dctx->staticSize) return ERROR(memory_allocation); + FORWARD_IF_ERROR(ZSTD_initLegacyStream(&dctx->legacyContext, + dctx->previousLegacyVersion, + legacyVersion, dict, dictSize), ""); + dctx->legacyVersion = dctx->previousLegacyVersion = legacyVersion; + hint = ZSTD_decompressLegacyStream(dctx->legacyContext, legacyVersion, + output, input); + if (hint == 0) dctx->streamStage = zdss_init; + return hint; + } +#else + (void)dctx; + (void)output; + (void)input; + (void)dict; + (void)dictSize; + return ERROR(prefix_unknown); +#endif +} + +void ZSTD_rust_legacy_free_stream(ZSTD_DCtx* dctx) +{ +#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) + if (dctx->legacyContext) { + ZSTD_freeLegacyStreamContext(dctx->legacyContext, + dctx->previousLegacyVersion); + dctx->legacyContext = NULL; + } +#else + (void)dctx; +#endif } diff --git a/programs/Makefile b/programs/Makefile index 48d229223..a18a5076a 100644 --- a/programs/Makefile +++ b/programs/Makefile @@ -27,8 +27,12 @@ include $(LIBZSTD_MK_DIR)/libzstd.mk CARGO ?= cargo RUST_DIR := ../rust 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 \ $(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. # 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_STATICLIB_32 := $(RUST_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_rs.a 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),) RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE) endif @@ -77,6 +82,63 @@ $(RUST_STATICLIB): $(RUST_SOURCES) $(RUST_STATICLIB_32): $(RUST_SOURCES) $(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 # direct-source variants below do not. Give their C outputs an independent # mode stamp so they cannot outlive a different Rust HUF archive selection. @@ -192,6 +254,9 @@ ifeq ($(BACKTRACE), 1) endif endif +RUST_LIBRARY_LINK ?= $(RUST_STATICLIB) +RUST_CLI_LINK ?= $(RUST_CLI_STATICLIB) + SET_CACHE_DIRECTORY = \ +$(MAKE) --no-print-directory $@ \ BUILD_DIR=obj/$(HASH_DIR) \ @@ -199,6 +264,8 @@ SET_CACHE_DIRECTORY = \ CFLAGS="$(CFLAGS)" \ LDFLAGS="$(LDFLAGS)" \ LDLIBS="$(LDLIBS)" \ + RUST_LIBRARY_LINK="$(RUST_LIBRARY_LINK)" \ + RUST_CLI_LINK="$(RUST_CLI_LINK)" \ ZSTD_ALL_SRC="$(ZSTD_ALL_SRC)" @@ -227,7 +294,7 @@ else # BUILD_DIR is defined 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 "$(ZLIB_MSG)" @echo "$(LZMA_MSG)" @@ -267,14 +334,14 @@ zstd32 : CPPFLAGS += -DZSTD_LEGACY_SUPPORT=$(ZSTD_LEGACY_SUPPORT) ifneq (,$(filter Windows%,$(OS))) zstd32 : $(RES32_FILE) 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) ## zstd-nolegacy: same scope as zstd, with removed support of legacy formats CLEAN += zstd-nolegacy zstd-nolegacy : LDFLAGS += $(THREAD_LD) $(ZLIBLD) $(LZMALD) $(LZ4LD) $(DEBUGFLAGS_LD) 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) .PHONY: zstd-nomt @@ -300,6 +367,7 @@ zstd-noxz : zstd zstd-dll : LDFLAGS+= -L$(LIB_BINDIR) zstd-dll : LDLIBS += -lzstd zstd-dll : ZSTDLIB_LOCAL_SRC = xxhash.c pool.c threading.c +zstd-dll : RUST_LIBRARY_LINK = 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. 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-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) 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) 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) ## zstd-dictBuilder: executable supporting dictionary creation and compression (only) 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) RUST_DIRECT_LINK_TARGETS := zstd32 zstd-nolegacy zstd-small zstd-frugal \ diff --git a/programs/fileio.c b/programs/fileio.c index 0ecca40d2..c5d599b3c 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -501,6 +501,11 @@ void FIO_setNbFilesTotal(FIO_ctx_t* const fCtx, int 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) { size_t i = 0; for ( ; i < filenames->tableSize; ++i) { diff --git a/programs/fileio.h b/programs/fileio.h index cb53ef537..58047c2ba 100644 --- a/programs/fileio.h +++ b/programs/fileio.h @@ -105,6 +105,7 @@ void FIO_setMMapDict(FIO_prefs_t* const prefs, ZSTD_ParamSwitch_e value); /* FIO_ctx_t functions */ 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_determineHasStdinInput(FIO_ctx_t* const fCtx, const FileNamesTable* const filenames); diff --git a/programs/zstdcli.c b/programs/zstdcli.c index 83d9b881e..97113d884 100644 --- a/programs/zstdcli.c +++ b/programs/zstdcli.c @@ -8,1651 +8,19 @@ * You may select, at your option, one of the above-listed licenses. */ -/*-************************************ -* Dependencies -**************************************/ -#include "platform.h" /* PLATFORM_POSIX_VERSION */ -#include "util.h" /* UTIL_HAS_CREATEFILELIST, UTIL_createFileList, UTIL_isConsole */ -#include /* getenv */ -#include /* strcmp, strlen */ -#include /* fprintf(), stdin, stdout, stderr */ -#include /* assert */ +/* The CLI parser and control flow live in rust/src/zstd_cli.rs. Keep this + * translation unit as the stable C entry point used by program launchers. */ +#include "../lib/zstd.h" -#include "fileio.h" /* stdinmark, stdoutmark, ZSTD_EXTENSION */ -#ifndef ZSTD_NOBENCH -# 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" +int ZSTD_rust_cli_main(int argCount, const char* const argv[]); +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) +const char* ZSTD_rust_cli_expected_version(void) { - if (strcmp(ZSTD_VERSION_STRING, ZSTD_versionString())) { - 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); - } + return ZSTD_VERSION_STRING; } - -/*! 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(¶ms, 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(¶ms, 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 argNb, - 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 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; ufileNames[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; + return ZSTD_rust_cli_main(argCount, argv); } diff --git a/rust/README.md b/rust/README.md index c128afa98..62c4bddc2 100644 --- a/rust/README.md +++ b/rust/README.md @@ -37,6 +37,8 @@ zstd ABI: including row-based and dictionary search variants. - `zstd_opt_tree` maintains the binary-tree index used by optimal matching; 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 - `threading` provides platform pthread wrappers required by zstd headers. - `pool` implements the bounded worker pool used by multithreaded compression. @@ -45,11 +47,20 @@ zstd ABI: - Block decompression - `zstd_decompress_block` decodes literal and sequence sections, maintains 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, -legacy, and CLI translation units are still C. They must move before the -rewrite is complete. Keeping that boundary explicit prevents a passing hybrid -build from being mistaken for the final all-Rust result. +The optimal block matcher, high-level frame compression, dictionary-building, +legacy decoding callbacks, and the CLI file-I/O backend are still C. They must +move before the rewrite is complete. Keeping that boundary explicit prevents a +passing hybrid build from being mistaken for the final all-Rust result. ## Compatibility boundary @@ -79,6 +90,16 @@ cargo test --all-targets 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 the narrow target for the component being migrated. For example: diff --git a/rust/cli/Cargo.lock b/rust/cli/Cargo.lock new file mode 100644 index 000000000..dc58719bc --- /dev/null +++ b/rust/cli/Cargo.lock @@ -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" diff --git a/rust/cli/Cargo.toml b/rust/cli/Cargo.toml new file mode 100644 index 000000000..99830b3f5 --- /dev/null +++ b/rust/cli/Cargo.toml @@ -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 = [] diff --git a/rust/cli/src/lib.rs b/rust/cli/src/lib.rs new file mode 100644 index 000000000..458d3ef36 --- /dev/null +++ b/rust/cli/src/lib.rs @@ -0,0 +1,2 @@ +#[path = "../../src/zstd_cli.rs"] +mod zstd_cli; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index efbafe9b8..7e4f015b2 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -32,6 +32,8 @@ pub mod zstd_compress_superblock; #[cfg(feature = "decompression")] pub mod zstd_ddict; #[cfg(feature = "decompression")] +pub mod zstd_decompress; +#[cfg(feature = "decompression")] pub mod zstd_decompress_block; #[cfg(feature = "compression")] pub mod zstd_double_fast; @@ -40,6 +42,8 @@ pub mod zstd_fast; #[cfg(feature = "compression")] pub mod zstd_lazy; #[cfg(feature = "compression")] +pub mod zstd_ldm; +#[cfg(feature = "compression")] pub mod zstd_opt_tree; #[cfg(feature = "compression")] pub mod zstd_presplit; diff --git a/rust/src/zstd_cli.rs b/rust/src/zstd_cli.rs new file mode 100644 index 000000000..df6c801fd --- /dev/null +++ b/rust/src/zstd_cli.rs @@ -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), + Help { advanced: bool }, + Version { quiet: bool }, +} + +#[derive(Debug)] +struct Cli { + operation: Operation, + inputs: Vec, + output: Option, + dictionary: Option, + level: i32, + ultra: bool, + display_level: i32, + force: bool, + force_stdout: bool, + remove_source: bool, + checksum: Option, + sparse: Option, + pass_through: Option, + content_size: i32, + dict_id: Option, + async_io: Option, + mmap_dict: i32, + progress: i32, + workers: Option, + block_size: Option, + mem_limit: Option, + ldm: bool, + ldm_hash_log: Option, + ldm_min_match: Option, + ldm_bucket_size_log: Option, + ldm_hash_rate_log: Option, + overlap_log: Option, + adapt: bool, + adapt_min: Option, + adapt_max: Option, + rsyncable: bool, + stream_src_size: Option, + target_cblock_size: Option, + src_size_hint: Option, + literal_compression: Option, + row_match_finder: i32, + exclude_compressed: bool, + compression_params: ZSTD_compressionParameters, + unsupported_program: Option, +} + +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::new(value).map_err(|_| format!("argument contains an interior NUL: {value:?}")) +} + +fn os_cstring(value: &OsStr) -> Result { + #[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::().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::() { + 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 { + 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 { + 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::() + .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 { + 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 { + value + .parse::() + .map_err(|_| format!("invalid {name}: {value:?}")) +} + +fn parse_worker_count(value: &str) -> Result { + 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 { + 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 { + *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 { + 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 { + 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, 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, 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) -> Result { + 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 { + 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) -> 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, 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![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")); + } +} diff --git a/rust/src/zstd_decompress.rs b/rust/src/zstd_decompress.rs new file mode 100644 index 000000000..95509ec00 --- /dev/null +++ b/rust/src/zstd_decompress.rs @@ -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, + custom_free: Option, + 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(slot: *mut c_void) -> T { + unsafe { slot.cast::().read() } +} + +#[inline] +unsafe fn set_field(slot: *mut c_void, value: T) { + unsafe { slot.cast::().write(value) } +} + +unsafe fn dctx_view(dctx: *mut ZSTD_DCtx) -> ZSTD_rustDctxView { + let mut view = MaybeUninit::::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::(), 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::(), + 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::(); + 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::(); + 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::(); + 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::(); + 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::(); + 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::().add(8) }; + let dict_end = unsafe { dict.cast::().add(dict_size) }; + let workspace = entropy.cast::(); + let workspace_size = size_of::() + * ((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 { + 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::(), + 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::(), 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::(), custom_mem) } + .cast::(); + 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::()); + 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::()); + 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::(view.in_buff_size) }) + .wrapping_add(unsafe { field::(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::(); + 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::().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::(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::()); + 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::(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::(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::(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::(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::(), 0); + } + let processed = field::(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::(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::().add(dict_size).cast::() }; + unsafe { + set_field( + view.ddict_is_cold, + c_int::from(get_pointer(view.dict_end).cast::() != 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::().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::(); + /* `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::(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::(view.max_block_size_param) }; + if max_block_size_param != 0 { + let mut params = unsafe { field::(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::(view.validate_checksum) } != 0 { + let _ = unsafe { + XXH64_update( + view.xxh_state.cast::(), + 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::(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::(view.force_ignore_checksum) } == ZSTD_D_VALIDATE_CHECKSUM { + let calculated = unsafe { XXH64_digest(view.xxh_state.cast::()) } 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::(); + let mut output = dst_start; + let mut input = src.cast::(); + let mut more_than_one_frame = false; + let starting_input = frame_header_prefix(unsafe { field::(view.format) }); + + while src_size >= starting_input { + if unsafe { field::(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::(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::(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::(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::(view.stage) }; + if (stage == ZSTDDS_DECOMPRESS_BLOCK || stage == ZSTDDS_DECOMPRESS_LAST_BLOCK) + && unsafe { field::(view.b_type) } == BT_RAW + { + let expected = unsafe { field::(view.expected) }; + return max(1, min(input_size, expected)); + } + unsafe { field::(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::(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::(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::(view.processed_c_size) }.wrapping_add(src_size as u64); + unsafe { set_field(view.processed_c_size, processed) }; + + match unsafe { field::(view.stage) } { + ZSTDDS_GET_FRAME_HEADER_SIZE => { + let format = unsafe { field::(view.format) }; + let header_buffer = view.header_buffer.cast::(); + 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::(view.header_size) }; + let offset = header_size - src_size; + unsafe { + copy_bytes( + view.header_buffer.cast::().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::(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::(view.stage) }; + let b_type = unsafe { field::(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::(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::(), + field::(view.rle_size), + ) + }; + unsafe { set_field(view.expected, 0usize) }; + result + } + _ => return ERROR(ZstdErrorCode::CorruptionDetected), + }; + if ERR_isError(decoded) { + return decoded; + } + let params = unsafe { field::(view.f_params) }; + if decoded > params.block_size_max as usize { + return ERROR(ZstdErrorCode::CorruptionDetected); + } + let decoded_total = + unsafe { field::(view.decoded_size) }.wrapping_add(decoded as u64); + unsafe { + set_field(view.decoded_size, decoded_total); + if field::(view.validate_checksum) != 0 { + let _ = XXH64_update(view.xxh_state.cast::(), dst, decoded); + } + set_pointer(view.previous_dst_end, const_ptr_add(dst.cast(), decoded)); + } + if unsafe { field::(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::(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::(view.validate_checksum) } != 0 { + let calculated = + unsafe { XXH64_digest(view.xxh_state.cast::()) } as u32; + let read = unsafe { MEM_readLE32(src) }; + if calculated != read { + return ERROR(ZstdErrorCode::ChecksumWrong); + } + } + unsafe { + ZSTD_rust_dctx_trace_end( + dctx, + field::(view.decoded_size), + field::(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::(); + 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::(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::(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::(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::(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::(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::(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::(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::(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::(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::(view.max_window_size) } as u32; + (u32::BITS - 1 - window.leading_zeros()) as c_int + } + ZSTD_D_FORMAT => unsafe { field::(view.format) }, + ZSTD_D_STABLE_OUT_BUFFER => unsafe { field::(view.out_buffer_mode) }, + ZSTD_D_FORCE_IGNORE_CHECKSUM => unsafe { field::(view.force_ignore_checksum) }, + ZSTD_D_REF_MULTIPLE_DDICTS => unsafe { field::(view.ref_multiple_ddicts) }, + ZSTD_D_DISABLE_HUFFMAN_ASSEMBLY => unsafe { field::(view.disable_huf_asm) }, + ZSTD_D_MAX_BLOCK_SIZE => unsafe { field::(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::(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::(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::(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::(view.in_buff_size) } + .wrapping_add(unsafe { field::(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::(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::(view.oversized_duration) }) >= ZSTD_WORKSPACETOOLARGE_MAXDURATION +} + +unsafe fn check_out_buffer(view: &ZSTD_rustDctxView, output: &ZSTD_outBuffer) -> usize { + if unsafe { field::(view.out_buffer_mode) } != ZSTD_BM_STABLE + || unsafe { field::(view.stream_stage) } == ZDSS_INIT + { + return 0; + } + let expected = unsafe { field::(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::(view.out_buffer_mode) } == ZSTD_BM_BUFFERED { + let out_start = unsafe { field::(view.out_start) }; + let out_size = unsafe { field::(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::(); + 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::(); + 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::(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::(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::(view.lh_size) }; + let header_result = unsafe { + ZSTD_getFrameHeader_advanced( + get_frame_header_ptr(&view), + view.header_buffer.cast(), + lh_size, + field::(view.format), + ) + }; + if unsafe { field::(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::(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::().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::(view.lh_size), + field::(view.format), + ) + }; + if ERR_isError(check) { + return check; + } + let minimum = max( + frame_header_min(unsafe { field::(view.format) }), + header_result, + ); + return minimum + .wrapping_sub(unsafe { field::(view.lh_size) }) + .wrapping_add(ZSTD_BLOCKHEADERSIZE); + } + unsafe { + copy_bytes(view.header_buffer.cast::().add(lh_size), ip, to_load); + set_field(view.lh_size, header_result); + } + ip = unsafe { ip.add(to_load) }; + continue; + } + + let params = unsafe { field::(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::(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::(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::(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::().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::(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::(view.f_params) }; + frame_params.window_size = + max(frame_params.window_size, 1u64 << ZSTD_WINDOWLOG_ABSOLUTEMIN); + if frame_params.window_size > unsafe { field::(view.max_window_size) } as u64 + { + return ERROR(ZstdErrorCode::FrameParameterWindowTooLarge); + } + let max_block_size_param = unsafe { field::(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::(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::(view.in_buff_size) } < needed_in_size + || unsafe { field::(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::(view.static_size) } != 0 { + let static_size = unsafe { field::(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::(view.expected) }; + let in_pos = unsafe { field::(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::(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::(view.out_start) }; + let out_end = unsafe { field::(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::(view.f_params); + if field::(view.out_buff_size) + < frame_params.frame_content_size as usize + && new_out_start + frame_params.block_size_max as usize + > field::(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::(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::(view.expected) }; + if hint == 0 { + if unsafe { field::(view.out_end) } == unsafe { field::(view.out_start) } { + if unsafe { field::(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::(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::(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 +} diff --git a/rust/src/zstd_ldm.rs b/rust/src/zstd_ldm.rs new file mode 100644 index 000000000..3edade89d --- /dev/null +++ b/rust/src/zstd_ldm.rs @@ -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::(); + while unsafe { input_limit.offset_from(input) as usize } >= word_size { + let diff = + unsafe { MEM_readST(matched.cast::()) ^ MEM_readST(input.cast::()) }; + 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::()) == MEM_read32(input.cast::()) } + { + 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::()) == MEM_read16(input.cast::()) } + { + 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::() == 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::(), 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::() }; + 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::() }; + 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::()) + } 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::() }; + 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::(); + let params = unsafe { &*params.cast::() }; + 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::(), 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::(); + let params = unsafe { &*params.cast::() }; + let window = unsafe { &mut *window.cast::() }; + let raw_seq_store = raw_seq_store.cast::(); + let max_dist = 1u32.wrapping_shl(params.window_log); + let input = src.cast::(); + 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::(), 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::() }; + 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::(); + let input = src.cast::(); + let input_end = input.wrapping_add(src_size); + if use_optimal_parser != 0 { + unsafe { ZSTD_ldm_rust_setLdmSeqStore(block_context, raw_seq_store.cast::()) }; + 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::(), 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::()) }; + let new_lit_length = unsafe { + ZSTD_ldm_rust_compressLiterals( + block_context, + seq_store, + reps, + input_position.cast::(), + 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::(), + input_end.cast::(), + 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::()) }; + unsafe { + ZSTD_ldm_rust_compressLiterals( + block_context, + seq_store, + reps, + input_position.cast::(), + 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::(), + 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); + } +}