diff --git a/lib/dictBuilder/zdict.c b/lib/dictBuilder/zdict.c index d5e60a4da..a56748b4e 100644 --- a/lib/dictBuilder/zdict.c +++ b/lib/dictBuilder/zdict.c @@ -8,1126 +8,13 @@ * You may select, at your option, one of the above-listed licenses. */ - -/*-************************************** -* Tuning parameters -****************************************/ -#define MINRATIO 4 /* minimum nb of apparition to be selected in dictionary */ -#define ZDICT_MAX_SAMPLES_SIZE (2000U << 20) -#define ZDICT_MIN_SAMPLES_SIZE (ZDICT_CONTENTSIZE_MIN * MINRATIO) - - -/*-************************************** -* Compiler Options -****************************************/ -/* Unix Large Files support (>4GB) */ -#define _FILE_OFFSET_BITS 64 -#if (defined(__sun__) && (!defined(__LP64__))) /* Sun Solaris 32-bits requires specific definitions */ -# ifndef _LARGEFILE_SOURCE -# define _LARGEFILE_SOURCE -# endif -#elif ! defined(__LP64__) /* No point defining Large file for 64 bit */ -# ifndef _LARGEFILE64_SOURCE -# define _LARGEFILE64_SOURCE -# endif -#endif - - -/*-************************************* -* Dependencies -***************************************/ -#include /* malloc, free */ -#include /* memset */ -#include /* fprintf, fopen, ftello64 */ -#include /* clock */ - +/* + * The public ZDICT implementation lives in rust/src/dict_builder_zdict.rs. + * Keep this translation unit as the C ABI anchor used by the existing build + * descriptions and by users which include the static-only declarations. + */ #ifndef ZDICT_STATIC_LINKING_ONLY # define ZDICT_STATIC_LINKING_ONLY #endif -#include "../common/mem.h" /* read */ -#include "../common/fse.h" /* FSE_normalizeCount, FSE_writeNCount */ -#include "../common/huf.h" /* HUF_buildCTable, HUF_writeCTable */ -#include "../common/zstd_internal.h" /* includes zstd.h */ -#include "../common/xxhash.h" /* XXH64 */ -#include "../compress/zstd_compress_internal.h" /* ZSTD_loadCEntropy() */ #include "../zdict.h" -#include "divsufsort.h" -#include "../common/bits.h" /* ZSTD_NbCommonBytes */ - - -/*-************************************* -* Constants -***************************************/ -#define KB *(1 <<10) -#define MB *(1 <<20) -#define GB *(1U<<30) - -#define DICTLISTSIZE_DEFAULT 10000 - -#define NOISELENGTH 32 - -static const U32 g_selectivity_default = 9; - - -/*-************************************* -* Console display -***************************************/ -#undef DISPLAY -#define DISPLAY(...) do { fprintf(stderr, __VA_ARGS__); fflush( stderr ); } while (0) -#undef DISPLAYLEVEL -#define DISPLAYLEVEL(l, ...) do { if (notificationLevel>=l) { DISPLAY(__VA_ARGS__); } } while (0) /* 0 : no display; 1: errors; 2: default; 3: details; 4: debug */ - -static clock_t ZDICT_clockSpan(clock_t nPrevious) { return clock() - nPrevious; } - -static void ZDICT_printHex(const void* ptr, size_t length) -{ - const BYTE* const b = (const BYTE*)ptr; - size_t u; - for (u=0; u126) c = '.'; /* non-printable char */ - DISPLAY("%c", c); - } -} - - -/*-******************************************************** -* Helper functions -**********************************************************/ -unsigned ZDICT_isError(size_t errorCode) { return ERR_isError(errorCode); } - -const char* ZDICT_getErrorName(size_t errorCode) { return ERR_getErrorName(errorCode); } - -unsigned ZDICT_getDictID(const void* dictBuffer, size_t dictSize) -{ - if (dictSize < 8) return 0; - if (MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return 0; - return MEM_readLE32((const char*)dictBuffer + 4); -} - -size_t ZDICT_getDictHeaderSize(const void* dictBuffer, size_t dictSize) -{ - size_t headerSize; - if (dictSize <= 8 || MEM_readLE32(dictBuffer) != ZSTD_MAGIC_DICTIONARY) return ERROR(dictionary_corrupted); - - { ZSTD_compressedBlockState_t* bs = (ZSTD_compressedBlockState_t*)malloc(sizeof(ZSTD_compressedBlockState_t)); - U32* wksp = (U32*)malloc(HUF_WORKSPACE_SIZE); - if (!bs || !wksp) { - headerSize = ERROR(memory_allocation); - } else { - ZSTD_reset_compressedBlockState(bs); - headerSize = ZSTD_loadCEntropy(bs, wksp, dictBuffer, dictSize); - } - - free(bs); - free(wksp); - } - - return headerSize; -} - -/*-******************************************************** -* Dictionary training functions -**********************************************************/ -/*! ZDICT_count() : - Count the nb of common bytes between 2 pointers. - Note : this function presumes end of buffer followed by noisy guard band. -*/ -static size_t ZDICT_count(const void* pIn, const void* pMatch) -{ - const char* const pStart = (const char*)pIn; - for (;;) { - size_t const diff = MEM_readST(pMatch) ^ MEM_readST(pIn); - if (!diff) { - pIn = (const char*)pIn+sizeof(size_t); - pMatch = (const char*)pMatch+sizeof(size_t); - continue; - } - pIn = (const char*)pIn+ZSTD_NbCommonBytes(diff); - return (size_t)((const char*)pIn - pStart); - } -} - - -typedef struct { - U32 pos; - U32 length; - U32 savings; -} dictItem; - -static void ZDICT_initDictItem(dictItem* d) -{ - d->pos = 1; - d->length = 0; - d->savings = (U32)(-1); -} - - -#define LLIMIT 64 /* heuristic determined experimentally */ -#define MINMATCHLENGTH 7 /* heuristic determined experimentally */ -static dictItem ZDICT_analyzePos( - BYTE* doneMarks, - const int* suffix, U32 start, - const void* buffer, U32 minRatio, U32 notificationLevel) -{ - U32 lengthList[LLIMIT] = {0}; - U32 cumulLength[LLIMIT] = {0}; - U32 savings[LLIMIT] = {0}; - const BYTE* b = (const BYTE*)buffer; - size_t maxLength = LLIMIT; - size_t pos = (size_t)suffix[start]; - U32 end = start; - dictItem solution; - - /* init */ - memset(&solution, 0, sizeof(solution)); - doneMarks[pos] = 1; - - /* trivial repetition cases */ - if ( (MEM_read16(b+pos+0) == MEM_read16(b+pos+2)) - ||(MEM_read16(b+pos+1) == MEM_read16(b+pos+3)) - ||(MEM_read16(b+pos+2) == MEM_read16(b+pos+4)) ) { - /* skip and mark segment */ - U16 const pattern16 = MEM_read16(b+pos+4); - U32 u, patternEnd = 6; - while (MEM_read16(b+pos+patternEnd) == pattern16) patternEnd+=2 ; - if (b[pos+patternEnd] == b[pos+patternEnd-1]) patternEnd++; - for (u=1; u= MINMATCHLENGTH); - } - - /* look backward */ - { size_t length; - do { - length = ZDICT_count(b + pos, b + *(suffix+start-1)); - if (length >=MINMATCHLENGTH) start--; - } while(length >= MINMATCHLENGTH); - } - - /* exit if not found a minimum nb of repetitions */ - if (end-start < minRatio) { - U32 idx; - for(idx=start; idx= %i at pos %7u ", (unsigned)(end-start), MINMATCHLENGTH, (unsigned)pos); - DISPLAYLEVEL(4, "\n"); - - for (mml = MINMATCHLENGTH ; ; mml++) { - BYTE currentChar = 0; - U32 currentCount = 0; - U32 currentID = refinedStart; - U32 id; - U32 selectedCount = 0; - U32 selectedID = currentID; - for (id =refinedStart; id < refinedEnd; id++) { - if (b[suffix[id] + mml] != currentChar) { - if (currentCount > selectedCount) { - selectedCount = currentCount; - selectedID = currentID; - } - currentID = id; - currentChar = b[ suffix[id] + mml]; - currentCount = 0; - } - currentCount ++; - } - if (currentCount > selectedCount) { /* for last */ - selectedCount = currentCount; - selectedID = currentID; - } - - if (selectedCount < minRatio) - break; - refinedStart = selectedID; - refinedEnd = refinedStart + selectedCount; - } - - /* evaluate gain based on new dict */ - start = refinedStart; - pos = suffix[refinedStart]; - end = start; - memset(lengthList, 0, sizeof(lengthList)); - - /* look forward */ - { size_t length; - do { - end++; - length = ZDICT_count(b + pos, b + suffix[end]); - if (length >= LLIMIT) length = LLIMIT-1; - lengthList[length]++; - } while (length >=MINMATCHLENGTH); - } - - /* look backward */ - { size_t length = MINMATCHLENGTH; - while ((length >= MINMATCHLENGTH) & (start > 0)) { - length = ZDICT_count(b + pos, b + suffix[start - 1]); - if (length >= LLIMIT) length = LLIMIT - 1; - lengthList[length]++; - if (length >= MINMATCHLENGTH) start--; - } - } - - /* largest useful length */ - memset(cumulLength, 0, sizeof(cumulLength)); - cumulLength[maxLength-1] = lengthList[maxLength-1]; - for (i=(int)(maxLength-2); i>=0; i--) - cumulLength[i] = cumulLength[i+1] + lengthList[i]; - - for (i=LLIMIT-1; i>=MINMATCHLENGTH; i--) if (cumulLength[i]>=minRatio) break; - maxLength = i; - - /* reduce maxLength in case of final into repetitive data */ - { U32 l = (U32)maxLength; - BYTE const c = b[pos + maxLength-1]; - while (b[pos+l-2]==c) l--; - maxLength = l; - } - if (maxLength < MINMATCHLENGTH) return solution; /* skip : no long-enough solution */ - - /* calculate savings */ - savings[5] = 0; - for (i=MINMATCHLENGTH; i<=(int)maxLength; i++) - savings[i] = savings[i-1] + (lengthList[i] * (i-3)); - - DISPLAYLEVEL(4, "Selected dict at position %u, of length %u : saves %u (ratio: %.2f) \n", - (unsigned)pos, (unsigned)maxLength, (unsigned)savings[maxLength], (double)savings[maxLength] / (double)maxLength); - - solution.pos = (U32)pos; - solution.length = (U32)maxLength; - solution.savings = savings[maxLength]; - - /* mark positions done */ - { U32 id; - for (id=start; id solution.length) length = solution.length; - } - pEnd = (U32)(testedPos + length); - for (p=testedPos; ppos; - const U32 eltEnd = elt.pos + elt.length; - const char* const buf = (const char*) buffer; - - /* tail overlap */ - U32 u; for (u=1; u elt.pos) && (table[u].pos <= eltEnd)) { /* overlap, existing > new */ - /* append */ - U32 const addedLength = table[u].pos - elt.pos; - table[u].length += addedLength; - table[u].pos = elt.pos; - table[u].savings += elt.savings * addedLength / elt.length; /* rough approx */ - table[u].savings += elt.length / 8; /* rough approx bonus */ - elt = table[u]; - /* sort : improve rank */ - while ((u>1) && (table[u-1].savings < elt.savings)) - table[u] = table[u-1], u--; - table[u] = elt; - return u; - } } - - /* front overlap */ - for (u=1; u= elt.pos) && (table[u].pos < elt.pos)) { /* overlap, existing < new */ - /* append */ - int const addedLength = (int)eltEnd - (int)(table[u].pos + table[u].length); - table[u].savings += elt.length / 8; /* rough approx bonus */ - if (addedLength > 0) { /* otherwise, elt fully included into existing */ - table[u].length += addedLength; - table[u].savings += elt.savings * addedLength / elt.length; /* rough approx */ - } - /* sort : improve rank */ - elt = table[u]; - while ((u>1) && (table[u-1].savings < elt.savings)) - table[u] = table[u-1], u--; - table[u] = elt; - return u; - } - - if (MEM_read64(buf + table[u].pos) == MEM_read64(buf + elt.pos + 1)) { - if (isIncluded(buf + table[u].pos, buf + elt.pos + 1, table[u].length)) { - size_t const addedLength = MAX( (int)elt.length - (int)table[u].length , 1 ); - table[u].pos = elt.pos; - table[u].savings += (U32)(elt.savings * addedLength / elt.length); - table[u].length = MIN(elt.length, table[u].length + 1); - return u; - } - } - } - - return 0; -} - - -static void ZDICT_removeDictItem(dictItem* table, U32 id) -{ - /* convention : table[0].pos stores nb of elts */ - U32 const max = table[0].pos; - U32 u; - if (!id) return; /* protection, should never happen */ - for (u=id; upos--; -} - - -static void ZDICT_insertDictItem(dictItem* table, U32 maxSize, dictItem elt, const void* buffer) -{ - /* merge if possible */ - U32 mergeId = ZDICT_tryMerge(table, elt, 0, buffer); - if (mergeId) { - U32 newMerge = 1; - while (newMerge) { - newMerge = ZDICT_tryMerge(table, table[mergeId], mergeId, buffer); - if (newMerge) ZDICT_removeDictItem(table, mergeId); - mergeId = newMerge; - } - return; - } - - /* insert */ - { U32 current; - U32 nextElt = table->pos; - if (nextElt >= maxSize) nextElt = maxSize-1; - current = nextElt-1; - while (table[current].savings < elt.savings) { - table[current+1] = table[current]; - current--; - } - table[current+1] = elt; - table->pos = nextElt+1; - } -} - - -static U32 ZDICT_dictSize(const dictItem* dictList) -{ - U32 u, dictSize = 0; - for (u=1; u=l) { \ - if (ZDICT_clockSpan(displayClock) > refreshRate) { \ - displayClock = clock(); \ - DISPLAY(__VA_ARGS__); \ - } \ - if (notificationLevel>=4) fflush(stderr); \ - } \ - } while (0) - - /* init */ - DISPLAYLEVEL(2, "\r%70s\r", ""); /* clean display line */ - if (!suffix0 || !reverseSuffix || !doneMarks || !filePos) { - result = ERROR(memory_allocation); - goto _cleanup; - } - if (minRatio < MINRATIO) minRatio = MINRATIO; - memset(doneMarks, 0, bufferSize+16); - - /* limit sample set size (divsufsort limitation)*/ - if (bufferSize > ZDICT_MAX_SAMPLES_SIZE) DISPLAYLEVEL(3, "sample set too large : reduced to %u MB ...\n", (unsigned)(ZDICT_MAX_SAMPLES_SIZE>>20)); - while (bufferSize > ZDICT_MAX_SAMPLES_SIZE) bufferSize -= fileSizes[--nbFiles]; - - /* sort */ - DISPLAYLEVEL(2, "sorting %u files of total size %u MB ...\n", nbFiles, (unsigned)(bufferSize>>20)); - { int const divSuftSortResult = divsufsort((const unsigned char*)buffer, suffix, (int)bufferSize, 0); - if (divSuftSortResult != 0) { result = ERROR(GENERIC); goto _cleanup; } - } - suffix[bufferSize] = (int)bufferSize; /* leads into noise */ - suffix0[0] = (int)bufferSize; /* leads into noise */ - /* build reverse suffix sort */ - { size_t pos; - for (pos=0; pos < bufferSize; pos++) - reverseSuffix[suffix[pos]] = (U32)pos; - /* note filePos tracks borders between samples. - It's not used at this stage, but planned to become useful in a later update */ - filePos[0] = 0; - for (pos=1; pos> 21); - } -} - - -typedef struct -{ - ZSTD_CDict* dict; /* dictionary */ - ZSTD_CCtx* zc; /* working context */ - void* workPlace; /* must be ZSTD_BLOCKSIZE_MAX allocated */ -} EStats_ress_t; - -#define MAXREPOFFSET 1024 - -static void ZDICT_countEStats(EStats_ress_t esr, const ZSTD_parameters* params, - unsigned* countLit, unsigned* offsetcodeCount, unsigned* matchlengthCount, unsigned* litlengthCount, U32* repOffsets, - const void* src, size_t srcSize, - U32 notificationLevel) -{ - size_t const blockSizeMax = MIN (ZSTD_BLOCKSIZE_MAX, 1 << params->cParams.windowLog); - size_t cSize; - - if (srcSize > blockSizeMax) srcSize = blockSizeMax; /* protection vs large samples */ - { size_t const errorCode = ZSTD_compressBegin_usingCDict_deprecated(esr.zc, esr.dict); - if (ZSTD_isError(errorCode)) { DISPLAYLEVEL(1, "warning : ZSTD_compressBegin_usingCDict failed \n"); return; } - - } - cSize = ZSTD_compressBlock_deprecated(esr.zc, esr.workPlace, ZSTD_BLOCKSIZE_MAX, src, srcSize); - if (ZSTD_isError(cSize)) { DISPLAYLEVEL(3, "warning : could not compress sample size %u \n", (unsigned)srcSize); return; } - - if (cSize) { /* if == 0; block is not compressible */ - const SeqStore_t* const seqStorePtr = ZSTD_getSeqStore(esr.zc); - - /* literals stats */ - { const BYTE* bytePtr; - for(bytePtr = seqStorePtr->litStart; bytePtr < seqStorePtr->lit; bytePtr++) - countLit[*bytePtr]++; - } - - /* seqStats */ - { U32 const nbSeq = (U32)(seqStorePtr->sequences - seqStorePtr->sequencesStart); - ZSTD_seqToCodes(seqStorePtr); - - { const BYTE* codePtr = seqStorePtr->ofCode; - U32 u; - for (u=0; umlCode; - U32 u; - for (u=0; ullCode; - U32 u; - for (u=0; u= 2) { /* rep offsets */ - const SeqDef* const seq = seqStorePtr->sequencesStart; - U32 offset1 = seq[0].offBase - ZSTD_REP_NUM; - U32 offset2 = seq[1].offBase - ZSTD_REP_NUM; - if (offset1 >= MAXREPOFFSET) offset1 = 0; - if (offset2 >= MAXREPOFFSET) offset2 = 0; - repOffsets[offset1] += 3; - repOffsets[offset2] += 1; - } } } -} - -static size_t ZDICT_totalSampleSize(const size_t* fileSizes, unsigned nbFiles) -{ - size_t total=0; - unsigned u; - for (u=0; u0; u--) { - offsetCount_t tmp; - if (table[u-1].count >= table[u].count) break; - tmp = table[u-1]; - table[u-1] = table[u]; - table[u] = tmp; - } -} - -/* ZDICT_flatLit() : - * rewrite `countLit` to contain a mostly flat but still compressible distribution of literals. - * necessary to avoid generating a non-compressible distribution that HUF_writeCTable() cannot encode. - */ -static void ZDICT_flatLit(unsigned* countLit) -{ - int u; - for (u=1; u<256; u++) countLit[u] = 2; - countLit[0] = 4; - countLit[253] = 1; - countLit[254] = 1; -} - -#define OFFCODE_MAX 30 /* only applicable to first block */ -static size_t ZDICT_analyzeEntropy(void* dstBuffer, size_t maxDstSize, - int compressionLevel, - const void* srcBuffer, const size_t* fileSizes, unsigned nbFiles, - const void* dictBuffer, size_t dictBufferSize, - unsigned notificationLevel) -{ - unsigned countLit[256]; - HUF_CREATE_STATIC_CTABLE(hufTable, 255); - unsigned offcodeCount[OFFCODE_MAX+1]; - short offcodeNCount[OFFCODE_MAX+1]; - U32 offcodeMax = ZSTD_highbit32((U32)(dictBufferSize + 128 KB)); - unsigned matchLengthCount[MaxML+1]; - short matchLengthNCount[MaxML+1]; - unsigned litLengthCount[MaxLL+1]; - short litLengthNCount[MaxLL+1]; - U32 repOffset[MAXREPOFFSET]; - offsetCount_t bestRepOffset[ZSTD_REP_NUM+1]; - EStats_ress_t esr = { NULL, NULL, NULL }; - ZSTD_parameters params; - U32 u, huffLog = 11, Offlog = OffFSELog, mlLog = MLFSELog, llLog = LLFSELog, total; - size_t pos = 0, errorCode; - size_t eSize = 0; - size_t const totalSrcSize = ZDICT_totalSampleSize(fileSizes, nbFiles); - size_t const averageSampleSize = totalSrcSize / (nbFiles + !nbFiles); - BYTE* dstPtr = (BYTE*)dstBuffer; - U32 wksp[HUF_CTABLE_WORKSPACE_SIZE_U32]; - - /* init */ - DEBUGLOG(4, "ZDICT_analyzeEntropy"); - if (offcodeMax>OFFCODE_MAX) { eSize = ERROR(dictionaryCreation_failed); goto _cleanup; } /* too large dictionary */ - for (u=0; u<256; u++) countLit[u] = 1; /* any character must be described */ - for (u=0; u<=offcodeMax; u++) offcodeCount[u] = 1; - for (u=0; u<=MaxML; u++) matchLengthCount[u] = 1; - for (u=0; u<=MaxLL; u++) litLengthCount[u] = 1; - memset(repOffset, 0, sizeof(repOffset)); - repOffset[1] = repOffset[4] = repOffset[8] = 1; - memset(bestRepOffset, 0, sizeof(bestRepOffset)); - if (compressionLevel==0) compressionLevel = ZSTD_CLEVEL_DEFAULT; - params = ZSTD_getParams(compressionLevel, averageSampleSize, dictBufferSize); - - esr.dict = ZSTD_createCDict_advanced(dictBuffer, dictBufferSize, ZSTD_dlm_byRef, ZSTD_dct_rawContent, params.cParams, ZSTD_defaultCMem); - esr.zc = ZSTD_createCCtx(); - esr.workPlace = malloc(ZSTD_BLOCKSIZE_MAX); - if (!esr.dict || !esr.zc || !esr.workPlace) { - eSize = ERROR(memory_allocation); - DISPLAYLEVEL(1, "Not enough memory \n"); - goto _cleanup; - } - - /* collect stats on all samples */ - for (u=0; u= 4) { - /* writeStats */ - DISPLAYLEVEL(4, "Offset Code Frequencies : \n"); - for (u=0; u<=offcodeMax; u++) { - DISPLAYLEVEL(4, "%2u :%7u \n", u, offcodeCount[u]); - } } - - /* analyze, build stats, starting with literals */ - { size_t maxNbBits = HUF_buildCTable_wksp(hufTable, countLit, 255, huffLog, wksp, sizeof(wksp)); - if (HUF_isError(maxNbBits)) { - eSize = maxNbBits; - DISPLAYLEVEL(1, " HUF_buildCTable error \n"); - goto _cleanup; - } - if (maxNbBits==8) { /* not compressible : will fail on HUF_writeCTable() */ - DISPLAYLEVEL(2, "warning : pathological dataset : literals are not compressible : samples are noisy or too regular \n"); - ZDICT_flatLit(countLit); /* replace distribution by a fake "mostly flat but still compressible" distribution, that HUF_writeCTable() can encode */ - maxNbBits = HUF_buildCTable_wksp(hufTable, countLit, 255, huffLog, wksp, sizeof(wksp)); - assert(maxNbBits==9); - } - huffLog = (U32)maxNbBits; - } - - /* looking for most common first offsets */ - { U32 offset; - for (offset=1; offset dictBufferCapacity) { - dictContentSize = dictBufferCapacity - hSize; - } - - /* Pad the dictionary content with zeros if it is too small */ - if (dictContentSize < minContentSize) { - RETURN_ERROR_IF(hSize + minContentSize > dictBufferCapacity, dstSize_tooSmall, - "dictBufferCapacity too small to fit max repcode"); - paddingSize = minContentSize - dictContentSize; - } else { - paddingSize = 0; - } - - { - size_t const dictSize = hSize + paddingSize + dictContentSize; - - /* The dictionary consists of the header, optional padding, and the content. - * The padding comes before the content because the "best" position in the - * dictionary is the last byte. - */ - BYTE* const outDictHeader = (BYTE*)dictBuffer; - BYTE* const outDictPadding = outDictHeader + hSize; - BYTE* const outDictContent = outDictPadding + paddingSize; - - assert(dictSize <= dictBufferCapacity); - assert(outDictContent + dictContentSize == (BYTE*)dictBuffer + dictSize); - - /* First copy the customDictContent into its final location. - * `customDictContent` and `dictBuffer` may overlap, so we must - * do this before any other writes into the output buffer. - * Then copy the header & padding into the output buffer. - */ - memmove(outDictContent, customDictContent, dictContentSize); - memcpy(outDictHeader, header, hSize); - memset(outDictPadding, 0, paddingSize); - - return dictSize; - } -} - - -static size_t ZDICT_addEntropyTablesFromBuffer_advanced( - void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity, - const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, - ZDICT_params_t params) -{ - int const compressionLevel = (params.compressionLevel == 0) ? ZSTD_CLEVEL_DEFAULT : params.compressionLevel; - U32 const notificationLevel = params.notificationLevel; - size_t hSize = 8; - - /* calculate entropy tables */ - DISPLAYLEVEL(2, "\r%70s\r", ""); /* clean display line */ - DISPLAYLEVEL(2, "statistics ... \n"); - { size_t const eSize = ZDICT_analyzeEntropy((char*)dictBuffer+hSize, dictBufferCapacity-hSize, - compressionLevel, - samplesBuffer, samplesSizes, nbSamples, - (char*)dictBuffer + dictBufferCapacity - dictContentSize, dictContentSize, - notificationLevel); - if (ZDICT_isError(eSize)) return eSize; - hSize += eSize; - } - - /* add dictionary header (after entropy tables) */ - MEM_writeLE32(dictBuffer, ZSTD_MAGIC_DICTIONARY); - { U64 const randomID = XXH64((char*)dictBuffer + dictBufferCapacity - dictContentSize, dictContentSize, 0); - U32 const compliantID = (randomID % ((1U<<31)-32768)) + 32768; - U32 const dictID = params.dictID ? params.dictID : compliantID; - MEM_writeLE32((char*)dictBuffer+4, dictID); - } - - if (hSize + dictContentSize < dictBufferCapacity) - memmove((char*)dictBuffer + hSize, (char*)dictBuffer + dictBufferCapacity - dictContentSize, dictContentSize); - return MIN(dictBufferCapacity, hSize+dictContentSize); -} - -/*! ZDICT_trainFromBuffer_unsafe_legacy() : -* Warning : `samplesBuffer` must be followed by noisy guard band !!! -* @return : size of dictionary, or an error code which can be tested with ZDICT_isError() -*/ -static size_t ZDICT_trainFromBuffer_unsafe_legacy( - void* dictBuffer, size_t maxDictSize, - const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, - ZDICT_legacy_params_t params) -{ - U32 const dictListSize = MAX(MAX(DICTLISTSIZE_DEFAULT, nbSamples), (U32)(maxDictSize/16)); - dictItem* const dictList = (dictItem*)malloc(dictListSize * sizeof(*dictList)); - unsigned const selectivity = params.selectivityLevel == 0 ? g_selectivity_default : params.selectivityLevel; - unsigned const minRep = (selectivity > 30) ? MINRATIO : nbSamples >> selectivity; - size_t const targetDictSize = maxDictSize; - size_t const samplesBuffSize = ZDICT_totalSampleSize(samplesSizes, nbSamples); - size_t dictSize = 0; - U32 const notificationLevel = params.zParams.notificationLevel; - - /* checks */ - if (!dictList) return ERROR(memory_allocation); - if (maxDictSize < ZDICT_DICTSIZE_MIN) { free(dictList); return ERROR(dstSize_tooSmall); } /* requested dictionary size is too small */ - if (samplesBuffSize < ZDICT_MIN_SAMPLES_SIZE) { free(dictList); return ERROR(dictionaryCreation_failed); } /* not enough source to create dictionary */ - - /* init */ - ZDICT_initDictItem(dictList); - - /* build dictionary */ - ZDICT_trainBuffer_legacy(dictList, dictListSize, - samplesBuffer, samplesBuffSize, - samplesSizes, nbSamples, - minRep, notificationLevel); - - /* display best matches */ - if (params.zParams.notificationLevel>= 3) { - unsigned const nb = MIN(25, dictList[0].pos); - unsigned const dictContentSize = ZDICT_dictSize(dictList); - unsigned u; - DISPLAYLEVEL(3, "\n %u segments found, of total size %u \n", (unsigned)dictList[0].pos-1, dictContentSize); - DISPLAYLEVEL(3, "list %u best segments \n", nb-1); - for (u=1; u samplesBuffSize) || ((pos + length) > samplesBuffSize)) { - free(dictList); - return ERROR(GENERIC); /* should never happen */ - } - DISPLAYLEVEL(3, "%3u:%3u bytes at pos %8u, savings %7u bytes |", - u, length, pos, (unsigned)dictList[u].savings); - ZDICT_printHex((const char*)samplesBuffer+pos, printedLength); - DISPLAYLEVEL(3, "| \n"); - } } - - - /* create dictionary */ - { unsigned dictContentSize = ZDICT_dictSize(dictList); - if (dictContentSize < ZDICT_CONTENTSIZE_MIN) { free(dictList); return ERROR(dictionaryCreation_failed); } /* dictionary content too small */ - if (dictContentSize < targetDictSize/4) { - DISPLAYLEVEL(2, "! warning : selected content significantly smaller than requested (%u < %u) \n", dictContentSize, (unsigned)maxDictSize); - if (samplesBuffSize < 10 * targetDictSize) - DISPLAYLEVEL(2, "! consider increasing the number of samples (total size : %u MB)\n", (unsigned)(samplesBuffSize>>20)); - if (minRep > MINRATIO) { - DISPLAYLEVEL(2, "! consider increasing selectivity to produce larger dictionary (-s%u) \n", selectivity+1); - DISPLAYLEVEL(2, "! note : larger dictionaries are not necessarily better, test its efficiency on samples \n"); - } - } - - if ((dictContentSize > targetDictSize*3) && (nbSamples > 2*MINRATIO) && (selectivity>1)) { - unsigned proposedSelectivity = selectivity-1; - while ((nbSamples >> proposedSelectivity) <= MINRATIO) { proposedSelectivity--; } - DISPLAYLEVEL(2, "! note : calculated dictionary significantly larger than requested (%u > %u) \n", dictContentSize, (unsigned)maxDictSize); - DISPLAYLEVEL(2, "! consider increasing dictionary size, or produce denser dictionary (-s%u) \n", proposedSelectivity); - DISPLAYLEVEL(2, "! always test dictionary efficiency on real samples \n"); - } - - /* limit dictionary size */ - { U32 const max = dictList->pos; /* convention : nb of useful elts within dictList */ - U32 currentSize = 0; - U32 n; for (n=1; n targetDictSize) { currentSize -= dictList[n].length; break; } - } - dictList->pos = n; - dictContentSize = currentSize; - } - - /* build dict content */ - { U32 u; - BYTE* ptr = (BYTE*)dictBuffer + maxDictSize; - for (u=1; upos; u++) { - U32 l = dictList[u].length; - ptr -= l; - if (ptr<(BYTE*)dictBuffer) { free(dictList); return ERROR(GENERIC); } /* should not happen */ - memcpy(ptr, (const char*)samplesBuffer+dictList[u].pos, l); - } } - - dictSize = ZDICT_addEntropyTablesFromBuffer_advanced(dictBuffer, dictContentSize, maxDictSize, - samplesBuffer, samplesSizes, nbSamples, - params.zParams); - } - - /* clean up */ - free(dictList); - return dictSize; -} - - -/* ZDICT_trainFromBuffer_legacy() : - * issue : samplesBuffer need to be followed by a noisy guard band. - * work around : duplicate the buffer, and add the noise */ -size_t ZDICT_trainFromBuffer_legacy(void* dictBuffer, size_t dictBufferCapacity, - const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, - ZDICT_legacy_params_t params) -{ - size_t result; - void* newBuff; - size_t const sBuffSize = ZDICT_totalSampleSize(samplesSizes, nbSamples); - if (sBuffSize < ZDICT_MIN_SAMPLES_SIZE) return 0; /* not enough content => no dictionary */ - - newBuff = malloc(sBuffSize + NOISELENGTH); - if (!newBuff) return ERROR(memory_allocation); - - memcpy(newBuff, samplesBuffer, sBuffSize); - ZDICT_fillNoise((char*)newBuff + sBuffSize, NOISELENGTH); /* guard band, for end of buffer condition */ - - result = - ZDICT_trainFromBuffer_unsafe_legacy(dictBuffer, dictBufferCapacity, newBuff, - samplesSizes, nbSamples, params); - free(newBuff); - return result; -} - - -size_t ZDICT_trainFromBuffer(void* dictBuffer, size_t dictBufferCapacity, - const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples) -{ - ZDICT_fastCover_params_t params; - DEBUGLOG(3, "ZDICT_trainFromBuffer"); - memset(¶ms, 0, sizeof(params)); - params.d = 8; - params.steps = 4; - /* Use default level since no compression level information is available */ - params.zParams.compressionLevel = ZSTD_CLEVEL_DEFAULT; -#if defined(DEBUGLEVEL) && (DEBUGLEVEL>=1) - params.zParams.notificationLevel = DEBUGLEVEL; -#endif - return ZDICT_optimizeTrainFromBuffer_fastCover(dictBuffer, dictBufferCapacity, - samplesBuffer, samplesSizes, nbSamples, - ¶ms); -} - -size_t ZDICT_addEntropyTablesFromBuffer(void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity, - const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples) -{ - ZDICT_params_t params; - memset(¶ms, 0, sizeof(params)); - return ZDICT_addEntropyTablesFromBuffer_advanced(dictBuffer, dictContentSize, dictBufferCapacity, - samplesBuffer, samplesSizes, nbSamples, - params); -} diff --git a/rust/src/dict_builder_zdict.rs b/rust/src/dict_builder_zdict.rs new file mode 100644 index 000000000..b1d52210d --- /dev/null +++ b/rust/src/dict_builder_zdict.rs @@ -0,0 +1,1310 @@ +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::too_many_arguments)] + +//! Public dictionary-builder wrappers. +//! +//! This is the Rust translation of `lib/dictBuilder/zdict.c`. The COVER and +//! fastCOVER implementations remain separate translation units, but their +//! finalization calls, the legacy trainer, entropy-header construction, and +//! public helper functions all live here. Compression contexts remain opaque: +//! the statistics pass uses the existing C context API and only projects the +//! stable `SeqStore_t` leaf into Rust. + +use crate::bits::ZSTD_highbit32; +use crate::common::{LL_FSE_LOG, MAX_LL, MAX_ML, ML_FSE_LOG, OFF_FSE_LOG, ZSTD_REP_NUM}; +use crate::divsufsort::divsufsort; +use crate::errors::{ERR_getErrorName, ERR_isError, ZstdErrorCode, ERROR}; +use crate::fse_compress::{FSE_normalizeCount, FSE_writeNCount}; +use crate::huf_compress::{HUF_buildCTable_wksp, HUF_writeCTable_wksp}; +use crate::mem::{MEM_readLE32, MEM_writeLE32}; +use crate::xxhash::XXH64; +use crate::zstd_compress_params::{ZSTD_compressionParameters, ZSTD_parameters}; +use crate::zstd_compress_sequences::SeqDef; +use crate::zstd_compress_stats::{SeqStore_t, ZSTD_compressedBlockState_t, ZSTD_seqToCodes}; +use std::ffi::{c_char, c_void}; +use std::mem::{size_of, MaybeUninit}; +use std::os::raw::{c_int, c_uint}; +use std::ptr; + +const ZSTD_MAGIC_DICTIONARY: u32 = 0xEC30_A437; +const ZSTD_CLEVEL_DEFAULT: c_int = 3; +const ZSTD_BLOCKSIZE_MAX: usize = 1 << 17; +const HUF_WORKSPACE_SIZE: usize = (8 << 10) + 512; +const HUF_CTABLE_WORKSPACE_SIZE_U32: usize = 4 * (255 + 1) + 192; +const ZDICT_DICTSIZE_MIN: usize = 256; +const ZDICT_CONTENTSIZE_MIN: usize = 128; +const ZDICT_MAX_SAMPLES_SIZE: usize = 2000 << 20; +const ZDICT_MIN_SAMPLES_SIZE: usize = ZDICT_CONTENTSIZE_MIN * 4; +const DICTLISTSIZE_DEFAULT: usize = 10_000; +const NOISELENGTH: usize = 32; +const MINRATIO: usize = 4; +const LLIMIT: usize = 64; +const MINMATCHLENGTH: usize = 7; +const MAXREPOFFSET: usize = 1024; +const OFFCODE_MAX: usize = 30; + +const ZSTD_DLM_BY_REF: c_int = 1; +const ZSTD_DCT_RAW_CONTENT: c_int = 1; + +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); + +/// ABI-compatible `ZDICT_params_t` from `zdict.h`. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZDICT_params_t { + pub compressionLevel: c_int, + pub notificationLevel: c_uint, + pub dictID: c_uint, +} + +/// ABI-compatible `ZDICT_legacy_params_t` from `zdict.h`. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZDICT_legacy_params_t { + pub selectivityLevel: c_uint, + pub zParams: ZDICT_params_t, +} + +/// ABI-compatible fastCOVER parameters used by `ZDICT_trainFromBuffer()`. +/// +/// The type is local to this module because the public fastCOVER declaration +/// is still provided by the C header and implementation. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct ZDICT_fastCover_params_t { + k: c_uint, + d: c_uint, + f: c_uint, + steps: c_uint, + nbThreads: c_uint, + splitPoint: f64, + accel: c_uint, + shrinkDict: c_uint, + shrinkDictMaxRegression: c_uint, + zParams: ZDICT_params_t, +} + +type ZSTD_CCtx = c_void; +type ZSTD_CDict = c_void; + +#[repr(C)] +#[derive(Clone, Copy)] +struct ZSTD_customMem { + customAlloc: Option, + customFree: Option, + opaque: *mut c_void, +} + +const ZSTD_DEFAULT_CMEM: ZSTD_customMem = ZSTD_customMem { + customAlloc: None, + customFree: None, + opaque: ptr::null_mut(), +}; + +unsafe extern "C" { + fn ZSTD_loadCEntropy( + bs: *mut ZSTD_compressedBlockState_t, + workspace: *mut c_void, + dict: *const c_void, + dict_size: usize, + ) -> usize; + fn ZSTD_reset_compressedBlockState(bs: *mut ZSTD_compressedBlockState_t); + fn ZSTD_getParams( + compression_level: c_int, + estimated_src_size: u64, + dict_size: usize, + ) -> ZSTD_parameters; + fn ZSTD_createCDict_advanced( + dict: *const c_void, + dict_size: usize, + dict_load_method: c_int, + dict_content_type: c_int, + c_params: ZSTD_compressionParameters, + custom_mem: ZSTD_customMem, + ) -> *mut ZSTD_CDict; + fn ZSTD_freeCDict(cdict: *mut ZSTD_CDict) -> usize; + fn ZSTD_createCCtx() -> *mut ZSTD_CCtx; + fn ZSTD_freeCCtx(cctx: *mut ZSTD_CCtx) -> usize; + fn ZSTD_compressBegin_usingCDict_deprecated( + cctx: *mut ZSTD_CCtx, + cdict: *const ZSTD_CDict, + ) -> usize; + fn ZSTD_compressBlock_deprecated( + cctx: *mut ZSTD_CCtx, + dst: *mut c_void, + dst_capacity: usize, + src: *const c_void, + src_size: usize, + ) -> usize; + fn ZSTD_getSeqStore(cctx: *const ZSTD_CCtx) -> *const SeqStore_t; + fn ZDICT_optimizeTrainFromBuffer_fastCover( + dict_buffer: *mut c_void, + dict_buffer_capacity: usize, + samples_buffer: *const c_void, + samples_sizes: *const usize, + nb_samples: c_uint, + parameters: *mut ZDICT_fastCover_params_t, + ) -> usize; +} + +#[inline] +fn dictionary_error(code: ZstdErrorCode) -> usize { + ERROR(code) +} + +#[inline] +fn read_u16(data: &[u8], offset: usize) -> u16 { + u16::from_le_bytes([data[offset], data[offset + 1]]) +} + +#[inline] +fn count_common(data: &[u8], input: usize, matched: usize) -> usize { + let start = input; + let mut input = input; + let mut matched = matched; + while input < data.len() && matched < data.len() && data[input] == data[matched] { + input += 1; + matched += 1; + } + input - start +} + +#[inline] +fn suffix_at(suffix0: &[i32], c_index: isize) -> usize { + suffix0[(c_index + 1) as usize] as usize +} + +#[derive(Clone, Copy, Debug, Default)] +struct DictItem { + pos: u32, + length: u32, + savings: u32, +} + +#[inline] +fn init_dict_item(item: &mut DictItem) { + item.pos = 1; + item.length = 0; + item.savings = u32::MAX; +} + +fn analyze_pos( + done_marks: &mut [u8], + suffix0: &[i32], + start: usize, + data: &[u8], + min_ratio: usize, + _notification_level: c_uint, +) -> DictItem { + let mut length_list = [0u32; LLIMIT]; + let mut cumulative_length = [0u32; LLIMIT]; + let mut savings = [0u32; LLIMIT]; + let mut pos = suffix_at(suffix0, start as isize); + let mut end = start; + let mut solution = DictItem::default(); + + done_marks[pos] = 1; + + if read_u16(data, pos) == read_u16(data, pos + 2) + || read_u16(data, pos + 1) == read_u16(data, pos + 3) + || read_u16(data, pos + 2) == read_u16(data, pos + 4) + { + let pattern = read_u16(data, pos + 4); + let mut pattern_end = 6; + while read_u16(data, pos + pattern_end) == pattern { + pattern_end += 2; + } + if data[pos + pattern_end] == data[pos + pattern_end - 1] { + pattern_end += 1; + } + for offset in 1..pattern_end { + done_marks[pos + offset] = 1; + } + return solution; + } + + loop { + end += 1; + let length = count_common(data, pos, suffix_at(suffix0, end as isize)); + if length < MINMATCHLENGTH { + break; + } + } + + let mut start = start; + loop { + let length = count_common(data, pos, suffix_at(suffix0, start as isize - 1)); + if length < MINMATCHLENGTH { + break; + } + start -= 1; + } + + if end - start < min_ratio { + for index in start..end { + done_marks[suffix_at(suffix0, index as isize)] = 1; + } + return solution; + } + + let mut refined_start = start; + let mut refined_end = end; + let mut mml = MINMATCHLENGTH; + loop { + let mut current_char = 0u8; + let mut current_count = 0usize; + let mut current_id = refined_start; + let mut selected_count = 0usize; + let mut selected_id = current_id; + + for id in refined_start..refined_end { + let byte = data[suffix_at(suffix0, id as isize) + mml]; + if byte != current_char { + if current_count > selected_count { + selected_count = current_count; + selected_id = current_id; + } + current_id = id; + current_char = byte; + current_count = 0; + } + current_count += 1; + } + if current_count > selected_count { + selected_count = current_count; + selected_id = current_id; + } + if selected_count < min_ratio { + break; + } + refined_start = selected_id; + refined_end = refined_start + selected_count; + mml += 1; + } + + start = refined_start; + pos = suffix_at(suffix0, refined_start as isize); + end = start; + loop { + end += 1; + let original_length = count_common(data, pos, suffix_at(suffix0, end as isize)); + let length = original_length.min(LLIMIT - 1); + length_list[length] += 1; + if original_length < MINMATCHLENGTH { + break; + } + } + + let mut length = MINMATCHLENGTH; + while length >= MINMATCHLENGTH && start > 0 { + let original_length = count_common(data, pos, suffix_at(suffix0, start as isize - 1)); + length = original_length.min(LLIMIT - 1); + length_list[length] += 1; + if original_length >= MINMATCHLENGTH { + start -= 1; + } + } + + cumulative_length[LLIMIT - 1] = length_list[LLIMIT - 1]; + for index in (0..LLIMIT - 1).rev() { + cumulative_length[index] = cumulative_length[index + 1] + length_list[index]; + } + + let mut useful_length = MINMATCHLENGTH - 1; + for index in (MINMATCHLENGTH..LLIMIT).rev() { + if cumulative_length[index] >= min_ratio as u32 { + useful_length = index; + break; + } + } + let mut max_length = useful_length; + + let repeated = data[pos + max_length - 1]; + let mut reduced = max_length as u32; + while data[pos + reduced as usize - 2] == repeated { + reduced -= 1; + } + max_length = reduced as usize; + if max_length < MINMATCHLENGTH { + return solution; + } + + savings[5] = 0; + for index in MINMATCHLENGTH..=max_length { + savings[index] = + savings[index - 1].wrapping_add(length_list[index].wrapping_mul((index - 3) as u32)); + } + + solution.pos = pos as u32; + solution.length = max_length as u32; + solution.savings = savings[max_length]; + + for index in start..end { + let tested_pos = suffix_at(suffix0, index as isize); + let length = if tested_pos == pos { + solution.length as usize + } else { + count_common(data, pos, tested_pos).min(solution.length as usize) + }; + for mark in done_marks.iter_mut().skip(tested_pos).take(length) { + *mark = 1; + } + } + solution +} + +#[inline] +fn is_included(data: &[u8], input: usize, container: usize, length: usize) -> bool { + data[input..input + length] == data[container..container + length] +} + +fn resort_item(table: &mut [DictItem], mut index: usize) { + let item = table[index]; + while index > 1 && table[index - 1].savings < item.savings { + table[index] = table[index - 1]; + index -= 1; + } + table[index] = item; +} + +fn try_merge(table: &mut [DictItem], elt: DictItem, skip: usize, data: &[u8]) -> usize { + let table_size = table[0].pos as usize; + let elt_end = elt.pos as usize + elt.length as usize; + + for index in 1..table_size { + if index == skip { + continue; + } + let item_pos = table[index].pos as usize; + if item_pos > elt.pos as usize && item_pos <= elt_end { + let added = item_pos - elt.pos as usize; + table[index].length = table[index].length.wrapping_add(added as u32); + table[index].pos = elt.pos; + table[index].savings = table[index] + .savings + .wrapping_add(elt.savings.wrapping_mul(added as u32) / elt.length) + .wrapping_add(elt.length / 8); + resort_item(table, index); + return index; + } + } + + for index in 1..table_size { + if index == skip { + continue; + } + let item_pos = table[index].pos as usize; + let item_end = item_pos + table[index].length as usize; + if item_end >= elt.pos as usize && item_pos < elt.pos as usize { + let added = elt_end as isize - item_end as isize; + table[index].savings = table[index].savings.wrapping_add(elt.length / 8); + if added > 0 { + table[index].length = table[index].length.wrapping_add(added as u32); + table[index].savings = table[index] + .savings + .wrapping_add(elt.savings.wrapping_mul(added as u32) / elt.length); + } + resort_item(table, index); + return index; + } + + let left = item_pos; + let right = elt.pos as usize + 1; + if left + 8 <= data.len() + && right + 8 <= data.len() + && data[left..left + 8] == data[right..right + 8] + && is_included(data, left, right, table[index].length as usize) + { + let added = ((elt.length as isize - table[index].length as isize).max(1)) as usize; + table[index].pos = elt.pos; + table[index].savings = table[index] + .savings + .wrapping_add(elt.savings.wrapping_mul(added as u32) / elt.length); + table[index].length = + (elt.length as usize).min(table[index].length as usize + 1) as u32; + return index; + } + } + 0 +} + +fn remove_dict_item(table: &mut [DictItem], id: usize) { + if id == 0 { + return; + } + let max = table[0].pos as usize; + for index in id..max - 1 { + table[index] = table[index + 1]; + } + table[0].pos -= 1; +} + +fn insert_dict_item(table: &mut [DictItem], max_size: usize, elt: DictItem, data: &[u8]) { + let merge_id = try_merge(table, elt, 0, data); + if merge_id != 0 { + let mut merge = merge_id; + while merge != 0 { + let next = try_merge(table, table[merge], merge, data); + if next != 0 { + remove_dict_item(table, merge); + } + merge = next; + } + return; + } + + let mut next_elt = table[0].pos as usize; + if next_elt >= max_size { + next_elt = max_size - 1; + } + let mut current = next_elt - 1; + while table[current].savings < elt.savings { + table[current + 1] = table[current]; + current -= 1; + } + table[current + 1] = elt; + table[0].pos = (next_elt + 1) as u32; +} + +fn dict_size(table: &[DictItem]) -> usize { + (1..table[0].pos as usize) + .map(|index| table[index].length as usize) + .sum() +} + +fn fill_noise(buffer: &mut [u8]) { + let prime2 = 2_246_822_519u32; + let mut accumulator = 2_654_435_761u32; + for byte in buffer { + accumulator = accumulator.wrapping_mul(prime2); + *byte = (accumulator >> 21) as u8; + } +} + +fn total_sample_size(file_sizes: &[usize]) -> usize { + file_sizes + .iter() + .fold(0usize, |total, size| total.wrapping_add(*size)) +} + +fn train_buffer_legacy( + dict_list: &mut [DictItem], + buffer: &[u8], + file_sizes: &[usize], + min_ratio: usize, + notification_level: c_uint, +) -> usize { + let buffer_size = buffer.len() - NOISELENGTH; + let mut suffix0 = vec![0i32; buffer_size.saturating_add(2)]; + let mut reverse_suffix = vec![0u32; buffer_size]; + let mut done_marks = vec![0u8; buffer_size.saturating_add(16)]; + let mut file_pos = vec![0u32; file_sizes.len()]; + + let mut effective_buffer_size = buffer_size; + let mut effective_files = file_sizes.len(); + while effective_buffer_size > ZDICT_MAX_SAMPLES_SIZE { + if effective_files == 0 { + break; + } + effective_files -= 1; + effective_buffer_size -= file_sizes[effective_files]; + } + if effective_buffer_size > ZDICT_MAX_SAMPLES_SIZE { + eprintln!( + "sample set too large : reduced to {} MB ...", + ZDICT_MAX_SAMPLES_SIZE >> 20 + ); + } + if effective_files > 0 { + for index in 1..effective_files { + file_pos[index] = file_pos[index - 1].wrapping_add(file_sizes[index - 1] as u32); + } + } + + let result = unsafe { + divsufsort( + buffer.as_ptr(), + suffix0.as_mut_ptr().add(1), + effective_buffer_size as c_int, + 0, + ) + }; + if result != 0 { + return dictionary_error(ZstdErrorCode::Generic); + } + suffix0[0] = effective_buffer_size as i32; + suffix0[effective_buffer_size + 1] = effective_buffer_size as i32; + for position in 0..effective_buffer_size { + reverse_suffix[suffix_at(&suffix0, position as isize)] = position as u32; + } + + done_marks.fill(0); + let mut cursor = 0usize; + while cursor < effective_buffer_size { + if done_marks[cursor] != 0 { + cursor += 1; + continue; + } + let solution = analyze_pos( + &mut done_marks, + &suffix0, + reverse_suffix[cursor] as usize, + buffer, + min_ratio.max(MINRATIO), + notification_level, + ); + if solution.length == 0 { + cursor += 1; + continue; + } + insert_dict_item(dict_list, dict_list.len(), solution, buffer); + cursor += solution.length as usize; + } + 0 +} + +fn count_entropy_stats( + cdict: *mut ZSTD_CDict, + cctx: *mut ZSTD_CCtx, + workplace: &mut [u8], + params: &ZSTD_parameters, + count_lit: &mut [u32; 256], + offset_counts: &mut [u32; OFFCODE_MAX + 1], + match_counts: &mut [u32; MAX_ML + 1], + lit_counts: &mut [u32; MAX_LL + 1], + rep_offsets: &mut [u32; MAXREPOFFSET], + src: *const c_void, + mut src_size: usize, +) { + let block_size_max = ZSTD_BLOCKSIZE_MAX.min(1usize << params.cParams.windowLog); + src_size = src_size.min(block_size_max); + let begin = unsafe { ZSTD_compressBegin_usingCDict_deprecated(cctx, cdict) }; + if ERR_isError(begin) { + return; + } + let compressed = unsafe { + ZSTD_compressBlock_deprecated( + cctx, + workplace.as_mut_ptr().cast(), + workplace.len(), + src, + src_size, + ) + }; + if ERR_isError(compressed) || compressed == 0 { + return; + } + + let store = unsafe { &*ZSTD_getSeqStore(cctx) }; + let literal_count = unsafe { store.lit.offset_from(store.litStart) as usize }; + for byte in unsafe { std::slice::from_raw_parts(store.litStart, literal_count) } { + count_lit[*byte as usize] += 1; + } + + let nb_sequences = unsafe { store.sequences.offset_from(store.sequencesStart) as usize }; + unsafe { ZSTD_seqToCodes(store) }; + for index in 0..nb_sequences { + let of_code = unsafe { *store.ofCode.add(index) as usize }; + let ml_code = unsafe { *store.mlCode.add(index) as usize }; + let ll_code = unsafe { *store.llCode.add(index) as usize }; + if of_code <= OFFCODE_MAX { + offset_counts[of_code] += 1; + } + if ml_code <= MAX_ML { + match_counts[ml_code] += 1; + } + if ll_code <= MAX_LL { + lit_counts[ll_code] += 1; + } + } + if nb_sequences >= 2 { + let first = unsafe { &*store.sequencesStart.cast::() }; + let second = unsafe { &*store.sequencesStart.add(1).cast::() }; + let offset1 = first.offBase.wrapping_sub(ZSTD_REP_NUM as u32); + let offset2 = second.offBase.wrapping_sub(ZSTD_REP_NUM as u32); + rep_offsets[if offset1 < MAXREPOFFSET as u32 { + offset1 as usize + } else { + 0 + }] += 3; + rep_offsets[if offset2 < MAXREPOFFSET as u32 { + offset2 as usize + } else { + 0 + }] += 1; + } +} + +unsafe fn analyze_entropy( + dst_buffer: *mut u8, + max_dst_size: usize, + compression_level: c_int, + src_buffer: *const u8, + file_sizes: *const usize, + nb_files: c_uint, + dict_buffer: *const u8, + dict_buffer_size: usize, + _notification_level: c_uint, +) -> usize { + let offcode_max = ZSTD_highbit32((dict_buffer_size + (128 << 10)) as u32) as usize; + if offcode_max > OFFCODE_MAX { + return dictionary_error(ZstdErrorCode::DictionaryCreationFailed); + } + + let file_sizes = if nb_files == 0 { + &[][..] + } else { + unsafe { std::slice::from_raw_parts(file_sizes, nb_files as usize) } + }; + let total_src_size = total_sample_size(file_sizes); + let average_sample_size = total_src_size / (nb_files as usize + usize::from(nb_files == 0)); + let mut count_lit = [1u32; 256]; + let mut offset_counts = [0u32; OFFCODE_MAX + 1]; + offset_counts[..=offcode_max].fill(1); + let mut match_counts = [1u32; MAX_ML + 1]; + let mut lit_counts = [1u32; MAX_LL + 1]; + let mut rep_offsets = [0u32; MAXREPOFFSET]; + rep_offsets[1] = 1; + rep_offsets[4] = 1; + rep_offsets[8] = 1; + let level = if compression_level == 0 { + ZSTD_CLEVEL_DEFAULT + } else { + compression_level + }; + let params = unsafe { ZSTD_getParams(level, average_sample_size as u64, dict_buffer_size) }; + let cdict = unsafe { + ZSTD_createCDict_advanced( + dict_buffer.cast(), + dict_buffer_size, + ZSTD_DLM_BY_REF, + ZSTD_DCT_RAW_CONTENT, + params.cParams, + ZSTD_DEFAULT_CMEM, + ) + }; + let cctx = unsafe { ZSTD_createCCtx() }; + let mut workplace = vec![0u8; ZSTD_BLOCKSIZE_MAX]; + if cdict.is_null() || cctx.is_null() { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return dictionary_error(ZstdErrorCode::MemoryAllocation); + } + + let mut source_offset = 0usize; + for &sample_size in file_sizes { + count_entropy_stats( + cdict, + cctx, + &mut workplace, + ¶ms, + &mut count_lit, + &mut offset_counts, + &mut match_counts, + &mut lit_counts, + &mut rep_offsets, + unsafe { src_buffer.add(source_offset).cast() }, + sample_size, + ); + source_offset = source_offset.wrapping_add(sample_size); + } + + let mut huf_table = [0usize; 257]; + let mut huf_workspace = [0u32; HUF_CTABLE_WORKSPACE_SIZE_U32]; + let mut huff_log = 11u32; + let mut written = unsafe { + HUF_buildCTable_wksp( + huf_table.as_mut_ptr(), + count_lit.as_ptr(), + 255, + huff_log, + huf_workspace.as_mut_ptr().cast(), + size_of_val(&huf_workspace), + ) + }; + if ERR_isError(written) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return written; + } + if written == 8 { + for count in count_lit.iter_mut().skip(1) { + *count = 2; + } + count_lit[0] = 4; + count_lit[253] = 1; + count_lit[254] = 1; + written = unsafe { + HUF_buildCTable_wksp( + huf_table.as_mut_ptr(), + count_lit.as_ptr(), + 255, + huff_log, + huf_workspace.as_mut_ptr().cast(), + size_of_val(&huf_workspace), + ) + }; + if ERR_isError(written) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return written; + } + } + huff_log = written as u32; + + let mut offcode_ncount = [0i16; OFFCODE_MAX + 1]; + let mut match_ncount = [0i16; MAX_ML + 1]; + let mut lit_ncount = [0i16; MAX_LL + 1]; + let total = offset_counts[..=offcode_max] + .iter() + .fold(0usize, |sum, count| sum + *count as usize); + let mut off_log = OFF_FSE_LOG as u32; + let mut match_log = ML_FSE_LOG as u32; + let mut lit_log = LL_FSE_LOG as u32; + let normalized = unsafe { + FSE_normalizeCount( + offcode_ncount.as_mut_ptr(), + off_log, + offset_counts.as_ptr(), + total, + offcode_max as u32, + 1, + ) + }; + if ERR_isError(normalized) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return normalized; + } + off_log = normalized as u32; + let total = match_counts + .iter() + .fold(0usize, |sum, count| sum + *count as usize); + let normalized = unsafe { + FSE_normalizeCount( + match_ncount.as_mut_ptr(), + match_log, + match_counts.as_ptr(), + total, + MAX_ML as u32, + 1, + ) + }; + if ERR_isError(normalized) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return normalized; + } + match_log = normalized as u32; + let total = lit_counts + .iter() + .fold(0usize, |sum, count| sum + *count as usize); + let normalized = unsafe { + FSE_normalizeCount( + lit_ncount.as_mut_ptr(), + lit_log, + lit_counts.as_ptr(), + total, + MAX_LL as u32, + 1, + ) + }; + if ERR_isError(normalized) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return normalized; + } + lit_log = normalized as u32; + + let mut dst = dst_buffer; + let mut remaining = max_dst_size; + let mut entropy_size = unsafe { + HUF_writeCTable_wksp( + dst.cast(), + remaining, + huf_table.as_ptr(), + 255, + huff_log, + huf_workspace.as_mut_ptr().cast(), + size_of_val(&huf_workspace), + ) + }; + if ERR_isError(entropy_size) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return entropy_size; + } + dst = unsafe { dst.add(entropy_size) }; + remaining -= entropy_size; + let header_size = unsafe { + FSE_writeNCount( + dst.cast(), + remaining, + offcode_ncount.as_ptr(), + OFFCODE_MAX as u32, + off_log, + ) + }; + if ERR_isError(header_size) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return header_size; + } + entropy_size += header_size; + dst = unsafe { dst.add(header_size) }; + remaining -= header_size; + let header_size = unsafe { + FSE_writeNCount( + dst.cast(), + remaining, + match_ncount.as_ptr(), + MAX_ML as u32, + match_log, + ) + }; + if ERR_isError(header_size) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return header_size; + } + entropy_size += header_size; + dst = unsafe { dst.add(header_size) }; + remaining -= header_size; + let header_size = unsafe { + FSE_writeNCount( + dst.cast(), + remaining, + lit_ncount.as_ptr(), + MAX_LL as u32, + lit_log, + ) + }; + if ERR_isError(header_size) { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return header_size; + } + entropy_size += header_size; + dst = unsafe { dst.add(header_size) }; + remaining -= header_size; + if remaining < 12 { + unsafe { + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + return dictionary_error(ZstdErrorCode::DstSizeTooSmall); + } + unsafe { + MEM_writeLE32(dst.cast(), 1); + MEM_writeLE32(dst.add(4).cast(), 4); + MEM_writeLE32(dst.add(8).cast(), 8); + ZSTD_freeCDict(cdict); + ZSTD_freeCCtx(cctx); + } + entropy_size + 12 +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_isError(error_code: usize) -> c_uint { + c_uint::from(ERR_isError(error_code)) +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_getErrorName(error_code: usize) -> *const c_char { + ERR_getErrorName(error_code) +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_getDictID(dict_buffer: *const c_void, dict_size: usize) -> c_uint { + if dict_size < 8 { + return 0; + } + if unsafe { MEM_readLE32(dict_buffer) } != ZSTD_MAGIC_DICTIONARY { + return 0; + } + unsafe { MEM_readLE32(dict_buffer.cast::().add(4).cast()) } +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_getDictHeaderSize( + dict_buffer: *const c_void, + dict_size: usize, +) -> usize { + if dict_size <= 8 || unsafe { MEM_readLE32(dict_buffer) } != ZSTD_MAGIC_DICTIONARY { + return dictionary_error(ZstdErrorCode::DictionaryCorrupted); + } + let mut state = unsafe { MaybeUninit::::zeroed().assume_init() }; + let mut workspace = vec![0u32; HUF_WORKSPACE_SIZE / size_of::()]; + unsafe { + ZSTD_reset_compressedBlockState(&mut state); + ZSTD_loadCEntropy( + &mut state, + workspace.as_mut_ptr().cast(), + dict_buffer, + dict_size, + ) + } +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_finalizeDictionary( + dict_buffer: *mut c_void, + dict_buffer_capacity: usize, + custom_dict_content: *const c_void, + mut dict_content_size: usize, + samples_buffer: *const c_void, + samples_sizes: *const usize, + nb_samples: c_uint, + params: ZDICT_params_t, +) -> usize { + if dict_buffer_capacity < dict_content_size || dict_buffer_capacity < ZDICT_DICTSIZE_MIN { + return dictionary_error(ZstdErrorCode::DstSizeTooSmall); + } + let mut header = [0u8; 256]; + unsafe { + MEM_writeLE32(header.as_mut_ptr().cast(), ZSTD_MAGIC_DICTIONARY); + let hash = XXH64(custom_dict_content, dict_content_size, 0); + let compliant_id = (hash % ((1u64 << 31) - 32_768)) as u32 + 32_768; + let dict_id = if params.dictID != 0 { + params.dictID + } else { + compliant_id + }; + MEM_writeLE32(header.as_mut_ptr().add(4).cast(), dict_id); + } + let entropy_size = analyze_entropy( + header.as_mut_ptr().add(8), + header.len() - 8, + if params.compressionLevel == 0 { + ZSTD_CLEVEL_DEFAULT + } else { + params.compressionLevel + }, + samples_buffer.cast(), + samples_sizes, + nb_samples, + custom_dict_content.cast(), + dict_content_size, + params.notificationLevel, + ); + if ERR_isError(entropy_size) { + return entropy_size; + } + let header_size = 8 + entropy_size; + if header_size + dict_content_size > dict_buffer_capacity { + dict_content_size = dict_buffer_capacity - header_size; + } + let min_content_size = 8usize; + let padding_size = if dict_content_size < min_content_size { + if header_size + min_content_size > dict_buffer_capacity { + return dictionary_error(ZstdErrorCode::DstSizeTooSmall); + } + min_content_size - dict_content_size + } else { + 0 + }; + let dictionary_size = header_size + padding_size + dict_content_size; + unsafe { + let output = dict_buffer.cast::(); + let content = output.add(header_size + padding_size); + ptr::copy(custom_dict_content.cast::(), content, dict_content_size); + ptr::copy_nonoverlapping(header.as_ptr(), output, header_size); + ptr::write_bytes(output.add(header_size), 0, padding_size); + } + dictionary_size +} + +unsafe fn add_entropy_tables_advanced( + dict_buffer: *mut c_void, + dict_content_size: usize, + dict_buffer_capacity: usize, + samples_buffer: *const c_void, + samples_sizes: *const usize, + nb_samples: c_uint, + params: ZDICT_params_t, +) -> usize { + if dict_buffer_capacity < 8 || dict_content_size > dict_buffer_capacity { + return dictionary_error(ZstdErrorCode::DstSizeTooSmall); + } + let content = dict_buffer + .cast::() + .add(dict_buffer_capacity - dict_content_size); + let entropy_size = analyze_entropy( + dict_buffer.cast::().add(8), + dict_buffer_capacity - 8, + if params.compressionLevel == 0 { + ZSTD_CLEVEL_DEFAULT + } else { + params.compressionLevel + }, + samples_buffer.cast(), + samples_sizes, + nb_samples, + content, + dict_content_size, + params.notificationLevel, + ); + if ERR_isError(entropy_size) { + return entropy_size; + } + let header_size = 8 + entropy_size; + unsafe { + MEM_writeLE32(dict_buffer, ZSTD_MAGIC_DICTIONARY); + let hash = XXH64(content.cast(), dict_content_size, 0); + let compliant_id = (hash % ((1u64 << 31) - 32_768)) as u32 + 32_768; + MEM_writeLE32( + dict_buffer.cast::().add(4).cast(), + if params.dictID != 0 { + params.dictID + } else { + compliant_id + }, + ); + if header_size + dict_content_size < dict_buffer_capacity { + ptr::copy( + content, + dict_buffer.cast::().add(header_size), + dict_content_size, + ); + } + } + dict_buffer_capacity.min(header_size + dict_content_size) +} + +unsafe fn train_from_buffer_unsafe_legacy( + dict_buffer: *mut c_void, + max_dict_size: usize, + samples_buffer: &[u8], + samples_sizes: &[usize], + params: ZDICT_legacy_params_t, +) -> usize { + let dict_list_size = DICTLISTSIZE_DEFAULT + .max(samples_sizes.len()) + .max(max_dict_size / 16); + let mut dict_list = Vec::::new(); + if dict_list.try_reserve_exact(dict_list_size).is_err() { + return dictionary_error(ZstdErrorCode::MemoryAllocation); + } + dict_list.resize(dict_list_size, DictItem::default()); + init_dict_item(&mut dict_list[0]); + + let selectivity = if params.selectivityLevel == 0 { + 9usize + } else { + params.selectivityLevel as usize + }; + let min_rep = if selectivity > 30 { + MINRATIO + } else { + samples_sizes.len() >> selectivity + }; + let sample_size = total_sample_size(samples_sizes); + if max_dict_size < ZDICT_DICTSIZE_MIN { + return dictionary_error(ZstdErrorCode::DstSizeTooSmall); + } + if sample_size < ZDICT_MIN_SAMPLES_SIZE { + return dictionary_error(ZstdErrorCode::DictionaryCreationFailed); + } + + let _ = train_buffer_legacy( + &mut dict_list, + samples_buffer, + samples_sizes, + min_rep, + params.zParams.notificationLevel, + ); + let mut content_size = dict_size(&dict_list); + if content_size < ZDICT_CONTENTSIZE_MIN { + return dictionary_error(ZstdErrorCode::DictionaryCreationFailed); + } + let output = unsafe { std::slice::from_raw_parts_mut(dict_buffer.cast::(), max_dict_size) }; + let mut write_at = max_dict_size; + for item in dict_list.iter().take(dict_list[0].pos as usize).skip(1) { + let length = item.length as usize; + if length > write_at { + return dictionary_error(ZstdErrorCode::Generic); + } + write_at -= length; + if write_at + length > output.len() + || item.pos as usize + length > samples_buffer.len().saturating_sub(NOISELENGTH) + { + return dictionary_error(ZstdErrorCode::Generic); + } + output[write_at..write_at + length] + .copy_from_slice(&samples_buffer[item.pos as usize..item.pos as usize + length]); + } + + let max = dict_list[0].pos as usize; + let mut current_size = 0usize; + let mut count = 1usize; + while count < max { + current_size += dict_list[count].length as usize; + if current_size > max_dict_size { + current_size -= dict_list[count].length as usize; + break; + } + count += 1; + } + dict_list[0].pos = count as u32; + content_size = current_size; + add_entropy_tables_advanced( + dict_buffer, + content_size, + max_dict_size, + samples_buffer.as_ptr().cast(), + samples_sizes.as_ptr(), + samples_sizes.len() as c_uint, + params.zParams, + ) +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_trainFromBuffer_legacy( + dict_buffer: *mut c_void, + dict_buffer_capacity: usize, + samples_buffer: *const c_void, + samples_sizes: *const usize, + nb_samples: c_uint, + params: ZDICT_legacy_params_t, +) -> usize { + let sizes = if nb_samples == 0 { + &[][..] + } else { + unsafe { std::slice::from_raw_parts(samples_sizes, nb_samples as usize) } + }; + let sample_size = total_sample_size(sizes); + if sample_size < ZDICT_MIN_SAMPLES_SIZE { + return 0; + } + let mut guarded = Vec::::new(); + if guarded + .try_reserve_exact(sample_size.saturating_add(NOISELENGTH)) + .is_err() + { + return dictionary_error(ZstdErrorCode::MemoryAllocation); + } + guarded.resize(sample_size + NOISELENGTH, 0); + if sample_size != 0 { + unsafe { + ptr::copy_nonoverlapping( + samples_buffer.cast::(), + guarded.as_mut_ptr(), + sample_size, + ); + } + } + fill_noise(&mut guarded[sample_size..]); + unsafe { + train_from_buffer_unsafe_legacy(dict_buffer, dict_buffer_capacity, &guarded, sizes, params) + } +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_trainFromBuffer( + dict_buffer: *mut c_void, + dict_buffer_capacity: usize, + samples_buffer: *const c_void, + samples_sizes: *const usize, + nb_samples: c_uint, +) -> usize { + let mut params = ZDICT_fastCover_params_t { + d: 8, + steps: 4, + zParams: ZDICT_params_t { + compressionLevel: ZSTD_CLEVEL_DEFAULT, + ..ZDICT_params_t::default() + }, + ..ZDICT_fastCover_params_t::default() + }; + unsafe { + ZDICT_optimizeTrainFromBuffer_fastCover( + dict_buffer, + dict_buffer_capacity, + samples_buffer, + samples_sizes, + nb_samples, + &mut params, + ) + } +} + +#[no_mangle] +pub unsafe extern "C" fn ZDICT_addEntropyTablesFromBuffer( + dict_buffer: *mut c_void, + dict_content_size: usize, + dict_buffer_capacity: usize, + samples_buffer: *const c_void, + samples_sizes: *const usize, + nb_samples: c_uint, +) -> usize { + unsafe { + add_entropy_tables_advanced( + dict_buffer, + dict_content_size, + dict_buffer_capacity, + samples_buffer, + samples_sizes, + nb_samples, + ZDICT_params_t::default(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CStr; + + #[test] + fn helper_abi_and_error_names_match() { + assert_eq!(unsafe { ZDICT_getDictID(ptr::null(), 0) }, 0); + let error = dictionary_error(ZstdErrorCode::DstSizeTooSmall); + assert_eq!(unsafe { ZDICT_isError(error) }, 1); + let name = unsafe { CStr::from_ptr(ZDICT_getErrorName(error)) }; + assert!(name + .to_bytes() + .windows(11) + .any(|part| part == b"Destination")); + } + + #[test] + fn dictionary_id_reads_only_valid_headers() { + let mut dict = [0u8; 8]; + unsafe { + MEM_writeLE32(dict.as_mut_ptr().cast(), ZSTD_MAGIC_DICTIONARY); + MEM_writeLE32(dict.as_mut_ptr().add(4).cast(), 1234); + } + assert_eq!( + unsafe { ZDICT_getDictID(dict.as_ptr().cast(), dict.len()) }, + 1234 + ); + dict[0] ^= 1; + assert_eq!( + unsafe { ZDICT_getDictID(dict.as_ptr().cast(), dict.len()) }, + 0 + ); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 92fed8e0b..376310e1f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -6,6 +6,10 @@ pub mod common; pub mod cpu; pub mod debug; #[cfg(feature = "dict-builder")] +pub mod dict_builder_cover; +#[cfg(all(feature = "compression", feature = "dict-builder"))] +pub mod dict_builder_zdict; +#[cfg(feature = "dict-builder")] pub mod divsufsort; pub mod entropy_common; pub mod errors; @@ -18,8 +22,6 @@ pub mod hist; pub mod huf_compress; #[cfg(feature = "decompression")] pub mod huf_decompress; -#[cfg(feature = "dict-builder")] -pub mod dict_builder_cover; pub mod legacy; pub mod mem; pub mod pool;