Compare commits

...
Author SHA1 Message Date
ddidderr 24488e4eaa feat(rust): port legacy v0.1 decoder
Port lib/legacy/zstd_v01.c (the frozen zstd v0.1 decoder) to
rust/src/legacy/zstd_v01.rs as the first legacy-format port on the new
scaffolding, and reduce the C file to a declaration-only shim that
keeps its header includes for configuration and platform preprocessor
behavior.

Frozen-decoder policy: zstd_v01.c embeds its own v0.1-era FSE and
Huff0 snapshot, distinct from every other release. The Rust port is a
line-by-line translation with the same table layouts (FSE_DTable as a
u32 header word plus packed newState/symbol/nbBits entries, the Huff0
u16 DTable with byte/nbBits pairs), the same arithmetic including
wrap-around and pointer-comparison quirks (e.g. the offset-vs-base
address check in ZSTD_execSequence), the same internal FSE error space
(size_t)-1..-7, and the same public ZSTD error codes. It reuses no
modern Rust entropy module; its only crate dependency is `errors`,
matching the C file's error_private.h include. The 32-bit-only reload
points are kept as compile-time conditions on usize::BITS.

Symbol takeover boundary: all nine ZSTDv01_* entry points from
zstd_v01.h now come from Rust as context-free #[no_mangle] extern "C"
functions (isError, decompress, decompressDCtx,
findFrameSizeInfoLegacy, createDCtx, freeDCtx, resetDCtx,
nextSrcSizeToDecompress, decompressContinue). zstd_legacy.h only uses
the first four for v0.1; streaming for v0.1-v0.3 intentionally returns
version_unsupported there, unchanged. The ZSTDv01_Dctx struct
definition moves entirely into Rust: C code only ever holds an opaque
pointer (zstd_v01.h forward-declares the type), and the context is
malloc/free-allocated exactly like the C version so create/free may
pair across the language boundary.

Byte-identity verification against the pristine pre-migration C build
(f8745da6, pure C, ZSTD_LEGACY_SUPPORT=1):

- Real v0.1 frames were generated by building the v0.1.0 git tag and
  compressing text, random, and 426 KB multi-block inputs. A one-shot
  ZSTD_decompress harness linked once against the pristine C libzstd.a
  and once against the Rust-backed libzstd.a produced bit-identical
  outputs for all frames.
- A direct ZSTDv01_* probe (one-shot decode, dst-too-small, truncated
  input, bad magic, findFrameSizeInfoLegacy, and the streaming
  continue loop) printed identical results, including exact error
  codes (-70 dstSize_tooSmall, -72 srcSize_wrong, -10 prefix_unknown)
  and identical dBound values.
- zstd -l -v on v0.1 files matches the pristine binary; CLI streaming
  decode of v0.1 fails with the same "Version not supported" in both,
  by design of zstd_legacy.h.

Unit tests embed three v0.1.0-generated fixtures (entropy-coded,
raw-block, and four-block frames) plus the truncation, bad-magic,
small-destination, and streaming-API cases, all asserting the exact C
error codes above. Note that `make -C tests test-legacy` only covers
v0.4+ frames, so the embedded fixtures and the harness comparison are
the actual v0.1 coverage.

Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
  --features legacy-v01 -- -D warnings && cargo test --all-targets
  --features legacy-v01 (127 tests, 9 for v0.1)
- cargo clippy/test --no-default-features --features
  decompression,legacy-v01 (module builds standalone)
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests, also with
  ZSTD_LEGACY_SUPPORT=1 (mixed Rust v0.1 + C v0.2-0.7 link)
- make -C tests test-rust-lib-smoke && make -C tests test-legacy
- make -C programs zstd (default and ZSTD_LEGACY_SUPPORT=1); nm shows
  the nine ZSTDv01_* symbols provided by Rust at level 1
- make -C lib libzstd.a ZSTD_LEGACY_SUPPORT=0 (no legacy symbols) and
  meson -Dlegacy_level=1 shared library exporting all nine
2026-07-11 14:25:23 +02:00
ddidderr c0a1b0bae1 build(rust): add legacy feature scaffolding
The legacy decoders (lib/legacy/zstd_v01.c .. zstd_v07.c) are next in
the Rust migration. Each of those files is a frozen snapshot of the
FSE/Huff0 entropy coders and frame logic of one historical release, so
their ports must not reuse the modern Rust entropy modules and must not
share code with each other: outputs and error codes have to stay
byte-identical to the frozen C forever. This commit installs the
build-system scaffolding so seven per-version ports can land
independently, each adding only its own module file plus a one-line
registration in rust/src/legacy/mod.rs.

Cargo grows features legacy-v01 .. legacy-v07. They are never default
features: the C build defaults differ per build system, so each build
system passes the list explicitly, derived from its own legacy
configuration:

- lib/Makefile and programs/Makefile map ZSTD_LEGACY_SUPPORT=N to the
  features for versions N..7 (0 disables legacy), mirroring the
  ZSTD_LEGACY_FILES selection in lib/libzstd.mk.
- tests/Makefile always enables all seven features because its
  ZSTDLEGACY_FILES wildcard compiles every lib/legacy/*.c regardless of
  the dispatch level.
- build/meson maps legacy_level exactly like the makefiles; build/cmake
  enables all seven whenever ZSTD_LEGACY_SUPPORT is ON because it
  always compiles all seven C files (ZSTD_LEGACY_LEVEL only selects the
  C dispatch).

Every build system also encodes the legacy selection in the Rust target
directory name (e.g. c1-d1-default-legacy5), for the same reason the
HUF mode is encoded there: a cached archive built for one configuration
must never be linked into a build expecting another. In tests/Makefile
the legacy level additionally flows into the existing HUF C-mode stamp,
so the flat C test objects (which bake -DZSTD_LEGACY_SUPPORT into the
dispatch) are rebuilt whenever the level changes. In programs/Makefile
the compress-only, decompress-only, and CLI archives keep
level-independent directories (RUST_HUF_MODE) because they are only
linked into ZSTD_LEGACY_SUPPORT=0 program variants and carry no legacy
features.

A feature whose version has not been ported yet gates nothing: the
module registration in rust/src/legacy/mod.rs is added by each port,
so enabling e.g. legacy-v05 today simply leaves that decoder in C.
This is what makes mixed C/Rust legacy levels link cleanly while the
seven ports land in any order.

Test plan:
- cd rust && cargo fmt --check && cargo clippy --all-targets
  -- -D warnings && cargo test --all-targets
- cargo clippy with --no-default-features --features
  decompression,legacy-v01 and with all seven legacy features
- make -C tests fuzzer && ./tests/fuzzer -i1 --no-big-tests
- make -C tests test-rust-lib-smoke; make -C tests test-legacy
- make -C lib libzstd.a with ZSTD_LEGACY_SUPPORT=0, 1 and default (5)
- cmake configure and meson setup (including -Dlegacy_level=1) emit the
  expected --features lists and legacy-suffixed target directories
2026-07-11 14:24:47 +02:00
11 changed files with 2531 additions and 2124 deletions
+13 -1
View File
@@ -164,6 +164,18 @@ elseif(_zstd_huf_force_x2)
endif()
endif()
# This CMake build compiles every lib/legacy/zstd_v0N.c whenever legacy
# support is enabled (ZSTD_LEGACY_LEVEL only selects the C dispatch), so the
# Rust archive enables every per-version legacy feature to match. The build
# configuration encodes the switch so archives never mix.
set(_zstd_rust_legacy 0)
if(ZSTD_LEGACY_SUPPORT)
set(_zstd_rust_legacy 1)
list(APPEND _zstd_rust_features
legacy-v01 legacy-v02 legacy-v03 legacy-v04
legacy-v05 legacy-v06 legacy-v07)
endif()
set(_zstd_rust_target "${ZSTD_RUST_TARGET}")
if(NOT _zstd_rust_target AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_SIZEOF_VOID_P EQUAL 4)
@@ -190,7 +202,7 @@ if(_zstd_rust_features)
endif()
set(_zstd_rust_build_config
"c${_zstd_rust_compression}-d${_zstd_rust_decompression}-${_zstd_rust_huf_mode}")
"c${_zstd_rust_compression}-d${_zstd_rust_decompression}-${_zstd_rust_huf_mode}-legacy${_zstd_rust_legacy}")
set(ZSTD_RUST_MANIFEST "${ZSTD_SOURCE_DIR}/rust/Cargo.toml")
set(ZSTD_RUST_TARGET_DIR
"${CMAKE_CURRENT_BINARY_DIR}/rust-target/${_zstd_rust_build_config}")
+11 -1
View File
@@ -86,6 +86,16 @@ elif rust_huf_force_x2
rust_huf_c_args += '-DHUF_FORCE_DECOMPRESS_X2'
endif
# Mirror the legacy source selection below: legacy_level N compiles
# lib/legacy/zstd_v0N.c .. zstd_v07.c, so the Rust archive enables the
# matching per-version features. The build configuration encodes the level
# so archives built for different legacy levels never mix.
foreach i : [1, 2, 3, 4, 5, 6, 7]
if legacy_level != 0 and legacy_level <= i
rust_features += 'legacy-v0@0@'.format(i)
endif
endforeach
rust_target = get_option('zstd_rust_target')
if rust_target == ''
if host_machine_os == os_linux and \
@@ -96,7 +106,7 @@ if rust_target == ''
endif
endif
rust_build_config = 'c1-d1-' + rust_huf_mode
rust_build_config = 'c1-d1-' + rust_huf_mode + '-legacy@0@'.format(legacy_level)
rust_target_dir = join_paths(meson.current_build_dir(), 'rust-target', rust_build_config)
is_msvc = cc_id == compiler_msvc or cc_id == 'clang-cl'
rust_staticlib_name = is_msvc ? 'zstd_rs.lib' : 'libzstd_rs.a'
+12
View File
@@ -100,6 +100,18 @@ ifneq ($(ZSTD_LIB_DECOMPRESSION),0)
RUST_CARGO_FEATURES += $(RUST_HUF_FEATURE)
endif
endif
# Legacy decoders follow the ZSTD_LEGACY_FILES selection exactly: level N
# enables versions v0.N .. v0.7 (0 disables legacy). The build directory
# also encodes the level, so archives for different legacy levels never mix.
RUST_LEGACY_FEATURES :=
ifneq ($(ZSTD_LEGACY_SUPPORT), 0)
ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0)
RUST_LEGACY_FEATURES := $(addprefix legacy-v0,$(wordlist $(ZSTD_LEGACY_SUPPORT),7,1 2 3 4 5 6 7))
endif
endif
RUST_CARGO_FEATURES += $(RUST_LEGACY_FEATURES)
RUST_BUILD_CONFIG := $(RUST_BUILD_CONFIG)-legacy$(ZSTD_LEGACY_SUPPORT)
RUST_CARGO_FEATURES := $(subst $(space),$(comma),$(strip $(RUST_CARGO_FEATURES)))
RUST_TARGET ?=
+4 -2108
View File
@@ -17,2111 +17,7 @@
#include "../common/compiler.h"
#include "../common/error_private.h"
/******************************************
* Static allocation
******************************************/
/* You can statically allocate FSE CTable/DTable as a table of unsigned using below macro */
#define FSE_DTABLE_SIZE_U32(maxTableLog) (1 + (1<<maxTableLog))
/* You can statically allocate Huff0 DTable as a table of unsigned short using below macro */
#define HUF_DTABLE_SIZE_U16(maxTableLog) (1 + (1<<maxTableLog))
#define HUF_CREATE_STATIC_DTABLE(DTable, maxTableLog) \
unsigned short DTable[HUF_DTABLE_SIZE_U16(maxTableLog)] = { maxTableLog }
/******************************************
* Error Management
******************************************/
#define FSE_LIST_ERRORS(ITEM) \
ITEM(FSE_OK_NoError) ITEM(FSE_ERROR_GENERIC) \
ITEM(FSE_ERROR_tableLog_tooLarge) ITEM(FSE_ERROR_maxSymbolValue_tooLarge) ITEM(FSE_ERROR_maxSymbolValue_tooSmall) \
ITEM(FSE_ERROR_dstSize_tooSmall) ITEM(FSE_ERROR_srcSize_wrong)\
ITEM(FSE_ERROR_corruptionDetected) \
ITEM(FSE_ERROR_maxCode)
#define FSE_GENERATE_ENUM(ENUM) ENUM,
typedef enum { FSE_LIST_ERRORS(FSE_GENERATE_ENUM) } FSE_errorCodes; /* enum is exposed, to detect & handle specific errors; compare function result to -enum value */
/******************************************
* FSE symbol compression API
******************************************/
/*
This API consists of small unitary functions, which highly benefit from being inlined.
You will want to enable link-time-optimization to ensure these functions are properly inlined in your binary.
Visual seems to do it automatically.
For gcc or clang, you'll need to add -flto flag at compilation and linking stages.
If none of these solutions is applicable, include "fse.c" directly.
*/
typedef unsigned FSE_CTable; /* don't allocate that. It's just a way to be more restrictive than void* */
typedef unsigned FSE_DTable; /* don't allocate that. It's just a way to be more restrictive than void* */
typedef struct
{
size_t bitContainer;
int bitPos;
char* startPtr;
char* ptr;
char* endPtr;
} FSE_CStream_t;
typedef struct
{
ptrdiff_t value;
const void* stateTable;
const void* symbolTT;
unsigned stateLog;
} FSE_CState_t;
typedef struct
{
size_t bitContainer;
unsigned bitsConsumed;
const char* ptr;
const char* start;
} FSE_DStream_t;
typedef struct
{
size_t state;
const void* table; /* precise table may vary, depending on U16 */
} FSE_DState_t;
typedef enum { FSE_DStream_unfinished = 0,
FSE_DStream_endOfBuffer = 1,
FSE_DStream_completed = 2,
FSE_DStream_tooFar = 3 } FSE_DStream_status; /* result of FSE_reloadDStream() */
/* 1,2,4,8 would be better for bitmap combinations, but slows down performance a bit ... ?! */
/****************************************************************
* Tuning parameters
****************************************************************/
/* MEMORY_USAGE :
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
* Increasing memory usage improves compression ratio
* Reduced memory usage can improve speed, due to cache effect
* Recommended max value is 14, for 16KB, which nicely fits into Intel x86 L1 cache */
#define FSE_MAX_MEMORY_USAGE 14
#define FSE_DEFAULT_MEMORY_USAGE 13
/* FSE_MAX_SYMBOL_VALUE :
* Maximum symbol value authorized.
* Required for proper stack allocation */
#define FSE_MAX_SYMBOL_VALUE 255
/****************************************************************
* template functions type & suffix
****************************************************************/
#define FSE_FUNCTION_TYPE BYTE
#define FSE_FUNCTION_EXTENSION
/****************************************************************
* Byte symbol type
****************************************************************/
typedef struct
{
unsigned short newState;
unsigned char symbol;
unsigned char nbBits;
} FSE_decode_t; /* size == U32 */
/****************************************************************
* Compiler specifics
****************************************************************/
#ifdef _MSC_VER /* Visual Studio */
# define FORCE_INLINE static __forceinline
# include <intrin.h> /* For Visual 2005 */
# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
# pragma warning(disable : 4214) /* disable: C4214: non-int bitfields */
#else
# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
# ifdef __GNUC__
# define FORCE_INLINE static inline __attribute__((always_inline))
# else
# define FORCE_INLINE static inline
# endif
# else
# define FORCE_INLINE static
# endif /* __STDC_VERSION__ */
#endif
/****************************************************************
* Includes
****************************************************************/
#include <stdlib.h> /* malloc, free, qsort */
#include <string.h> /* memcpy, memset */
#include <stdio.h> /* printf (debug) */
#ifndef MEM_ACCESS_MODULE
#define MEM_ACCESS_MODULE
/****************************************************************
* Basic Types
*****************************************************************/
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
# include <stdint.h>
typedef uint8_t BYTE;
typedef uint16_t U16;
typedef int16_t S16;
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
typedef int64_t S64;
#else
typedef unsigned char BYTE;
typedef unsigned short U16;
typedef signed short S16;
typedef unsigned int U32;
typedef signed int S32;
typedef unsigned long long U64;
typedef signed long long S64;
#endif
#endif /* MEM_ACCESS_MODULE */
/****************************************************************
* Memory I/O
*****************************************************************/
static unsigned FSE_32bits(void)
{
return sizeof(void*)==4;
}
static unsigned FSE_isLittleEndian(void)
{
const union { U32 i; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
return one.c[0];
}
static U16 FSE_read16(const void* memPtr)
{
U16 val; memcpy(&val, memPtr, sizeof(val)); return val;
}
static U32 FSE_read32(const void* memPtr)
{
U32 val; memcpy(&val, memPtr, sizeof(val)); return val;
}
static U64 FSE_read64(const void* memPtr)
{
U64 val; memcpy(&val, memPtr, sizeof(val)); return val;
}
static U16 FSE_readLE16(const void* memPtr)
{
if (FSE_isLittleEndian())
return FSE_read16(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U16)(p[0] + (p[1]<<8));
}
}
static U32 FSE_readLE32(const void* memPtr)
{
if (FSE_isLittleEndian())
return FSE_read32(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U32)((U32)p[0] + ((U32)p[1]<<8) + ((U32)p[2]<<16) + ((U32)p[3]<<24));
}
}
static U64 FSE_readLE64(const void* memPtr)
{
if (FSE_isLittleEndian())
return FSE_read64(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U64)((U64)p[0] + ((U64)p[1]<<8) + ((U64)p[2]<<16) + ((U64)p[3]<<24)
+ ((U64)p[4]<<32) + ((U64)p[5]<<40) + ((U64)p[6]<<48) + ((U64)p[7]<<56));
}
}
static size_t FSE_readLEST(const void* memPtr)
{
if (FSE_32bits())
return (size_t)FSE_readLE32(memPtr);
else
return (size_t)FSE_readLE64(memPtr);
}
/****************************************************************
* Constants
*****************************************************************/
#define FSE_MAX_TABLELOG (FSE_MAX_MEMORY_USAGE-2)
#define FSE_MAX_TABLESIZE (1U<<FSE_MAX_TABLELOG)
#define FSE_MAXTABLESIZE_MASK (FSE_MAX_TABLESIZE-1)
#define FSE_DEFAULT_TABLELOG (FSE_DEFAULT_MEMORY_USAGE-2)
#define FSE_MIN_TABLELOG 5
#define FSE_TABLELOG_ABSOLUTE_MAX 15
#if FSE_MAX_TABLELOG > FSE_TABLELOG_ABSOLUTE_MAX
#error "FSE_MAX_TABLELOG > FSE_TABLELOG_ABSOLUTE_MAX is not supported"
#endif
/****************************************************************
* Error Management
****************************************************************/
#define FSE_STATIC_ASSERT(c) { enum { FSE_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */
/****************************************************************
* Complex types
****************************************************************/
typedef struct
{
int deltaFindState;
U32 deltaNbBits;
} FSE_symbolCompressionTransform; /* total 8 bytes */
typedef U32 DTable_max_t[FSE_DTABLE_SIZE_U32(FSE_MAX_TABLELOG)];
/****************************************************************
* Internal functions
****************************************************************/
FORCE_INLINE unsigned FSE_highbit32 (U32 val)
{
# if defined(_MSC_VER) /* Visual */
unsigned long r;
return _BitScanReverse(&r, val) ? (unsigned)r : 0;
# elif defined(__GNUC__) && (GCC_VERSION >= 304) /* GCC Intrinsic */
return __builtin_clz (val) ^ 31;
# else /* Software version */
static const unsigned DeBruijnClz[32] = { 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 };
U32 v = val;
unsigned r;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
r = DeBruijnClz[ (U32) (v * 0x07C4ACDDU) >> 27];
return r;
# endif
}
/****************************************************************
* Templates
****************************************************************/
/*
designed to be included
for type-specific functions (template emulation in C)
Objective is to write these functions only once, for improved maintenance
*/
/* safety checks */
#ifndef FSE_FUNCTION_EXTENSION
# error "FSE_FUNCTION_EXTENSION must be defined"
#endif
#ifndef FSE_FUNCTION_TYPE
# error "FSE_FUNCTION_TYPE must be defined"
#endif
/* Function names */
#define FSE_CAT(X,Y) X##Y
#define FSE_FUNCTION_NAME(X,Y) FSE_CAT(X,Y)
#define FSE_TYPE_NAME(X,Y) FSE_CAT(X,Y)
static U32 FSE_tableStep(U32 tableSize) { return (tableSize>>1) + (tableSize>>3) + 3; }
#define FSE_DECODE_TYPE FSE_decode_t
typedef struct {
U16 tableLog;
U16 fastMode;
} FSE_DTableHeader; /* sizeof U32 */
static size_t FSE_buildDTable
(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog)
{
void* ptr = dt;
FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr;
FSE_DECODE_TYPE* const tableDecode = (FSE_DECODE_TYPE*)(ptr) + 1; /* because dt is unsigned, 32-bits aligned on 32-bits */
const U32 tableSize = 1 << tableLog;
const U32 tableMask = tableSize-1;
const U32 step = FSE_tableStep(tableSize);
U16 symbolNext[FSE_MAX_SYMBOL_VALUE+1];
U32 position = 0;
U32 highThreshold = tableSize-1;
const S16 largeLimit= (S16)(1 << (tableLog-1));
U32 noLarge = 1;
U32 s;
/* Sanity Checks */
if (maxSymbolValue > FSE_MAX_SYMBOL_VALUE) return (size_t)-FSE_ERROR_maxSymbolValue_tooLarge;
if (tableLog > FSE_MAX_TABLELOG) return (size_t)-FSE_ERROR_tableLog_tooLarge;
/* Init, lay down lowprob symbols */
DTableH[0].tableLog = (U16)tableLog;
for (s=0; s<=maxSymbolValue; s++)
{
if (normalizedCounter[s]==-1)
{
tableDecode[highThreshold--].symbol = (FSE_FUNCTION_TYPE)s;
symbolNext[s] = 1;
}
else
{
if (normalizedCounter[s] >= largeLimit) noLarge=0;
symbolNext[s] = normalizedCounter[s];
}
}
/* Spread symbols */
for (s=0; s<=maxSymbolValue; s++)
{
int i;
for (i=0; i<normalizedCounter[s]; i++)
{
tableDecode[position].symbol = (FSE_FUNCTION_TYPE)s;
position = (position + step) & tableMask;
while (position > highThreshold) position = (position + step) & tableMask; /* lowprob area */
}
}
if (position!=0) return (size_t)-FSE_ERROR_GENERIC; /* position must reach all cells once, otherwise normalizedCounter is incorrect */
/* Build Decoding table */
{
U32 i;
for (i=0; i<tableSize; i++)
{
FSE_FUNCTION_TYPE symbol = (FSE_FUNCTION_TYPE)(tableDecode[i].symbol);
U16 nextState = symbolNext[symbol]++;
tableDecode[i].nbBits = (BYTE) (tableLog - FSE_highbit32 ((U32)nextState) );
tableDecode[i].newState = (U16) ( (nextState << tableDecode[i].nbBits) - tableSize);
}
}
DTableH->fastMode = (U16)noLarge;
return 0;
}
/******************************************
* FSE byte symbol
******************************************/
#ifndef FSE_COMMONDEFS_ONLY
static unsigned FSE_isError(size_t code) { return (code > (size_t)(-FSE_ERROR_maxCode)); }
static short FSE_abs(short a)
{
return a<0? -a : a;
}
/****************************************************************
* Header bitstream management
****************************************************************/
static size_t FSE_readNCount (short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
const void* headerBuffer, size_t hbSize)
{
const BYTE* const istart = (const BYTE*) headerBuffer;
const BYTE* const iend = istart + hbSize;
const BYTE* ip = istart;
int nbBits;
int remaining;
int threshold;
U32 bitStream;
int bitCount;
unsigned charnum = 0;
int previous0 = 0;
if (hbSize < 4) return (size_t)-FSE_ERROR_srcSize_wrong;
bitStream = FSE_readLE32(ip);
nbBits = (bitStream & 0xF) + FSE_MIN_TABLELOG; /* extract tableLog */
if (nbBits > FSE_TABLELOG_ABSOLUTE_MAX) return (size_t)-FSE_ERROR_tableLog_tooLarge;
bitStream >>= 4;
bitCount = 4;
*tableLogPtr = nbBits;
remaining = (1<<nbBits)+1;
threshold = 1<<nbBits;
nbBits++;
while ((remaining>1) && (charnum<=*maxSVPtr))
{
if (previous0)
{
unsigned n0 = charnum;
while ((bitStream & 0xFFFF) == 0xFFFF)
{
n0+=24;
if (ip < iend-5)
{
ip+=2;
bitStream = FSE_readLE32(ip) >> bitCount;
}
else
{
bitStream >>= 16;
bitCount+=16;
}
}
while ((bitStream & 3) == 3)
{
n0+=3;
bitStream>>=2;
bitCount+=2;
}
n0 += bitStream & 3;
bitCount += 2;
if (n0 > *maxSVPtr) return (size_t)-FSE_ERROR_maxSymbolValue_tooSmall;
while (charnum < n0) normalizedCounter[charnum++] = 0;
if ((ip <= iend-7) || (ip + (bitCount>>3) <= iend-4))
{
ip += bitCount>>3;
bitCount &= 7;
bitStream = FSE_readLE32(ip) >> bitCount;
}
else
bitStream >>= 2;
}
{
const short max = (short)((2*threshold-1)-remaining);
short count;
if ((bitStream & (threshold-1)) < (U32)max)
{
count = (short)(bitStream & (threshold-1));
bitCount += nbBits-1;
}
else
{
count = (short)(bitStream & (2*threshold-1));
if (count >= threshold) count -= max;
bitCount += nbBits;
}
count--; /* extra accuracy */
remaining -= FSE_abs(count);
normalizedCounter[charnum++] = count;
previous0 = !count;
while (remaining < threshold)
{
nbBits--;
threshold >>= 1;
}
{
if ((ip <= iend-7) || (ip + (bitCount>>3) <= iend-4))
{
ip += bitCount>>3;
bitCount &= 7;
}
else
{
bitCount -= (int)(8 * (iend - 4 - ip));
ip = iend - 4;
}
bitStream = FSE_readLE32(ip) >> (bitCount & 31);
}
}
}
if (remaining != 1) return (size_t)-FSE_ERROR_GENERIC;
*maxSVPtr = charnum-1;
ip += (bitCount+7)>>3;
if ((size_t)(ip-istart) > hbSize) return (size_t)-FSE_ERROR_srcSize_wrong;
return ip-istart;
}
/*********************************************************
* Decompression (Byte symbols)
*********************************************************/
static size_t FSE_buildDTable_rle (FSE_DTable* dt, BYTE symbolValue)
{
void* ptr = dt;
FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr;
FSE_decode_t* const cell = (FSE_decode_t*)(ptr) + 1; /* because dt is unsigned */
DTableH->tableLog = 0;
DTableH->fastMode = 0;
cell->newState = 0;
cell->symbol = symbolValue;
cell->nbBits = 0;
return 0;
}
static size_t FSE_buildDTable_raw (FSE_DTable* dt, unsigned nbBits)
{
void* ptr = dt;
FSE_DTableHeader* const DTableH = (FSE_DTableHeader*)ptr;
FSE_decode_t* const dinfo = (FSE_decode_t*)(ptr) + 1; /* because dt is unsigned */
const unsigned tableSize = 1 << nbBits;
const unsigned tableMask = tableSize - 1;
const unsigned maxSymbolValue = tableMask;
unsigned s;
/* Sanity checks */
if (nbBits < 1) return (size_t)-FSE_ERROR_GENERIC; /* min size */
/* Build Decoding Table */
DTableH->tableLog = (U16)nbBits;
DTableH->fastMode = 1;
for (s=0; s<=maxSymbolValue; s++)
{
dinfo[s].newState = 0;
dinfo[s].symbol = (BYTE)s;
dinfo[s].nbBits = (BYTE)nbBits;
}
return 0;
}
/* FSE_initDStream
* Initialize a FSE_DStream_t.
* srcBuffer must point at the beginning of an FSE block.
* The function result is the size of the FSE_block (== srcSize).
* If srcSize is too small, the function will return an errorCode;
*/
static size_t FSE_initDStream(FSE_DStream_t* bitD, const void* srcBuffer, size_t srcSize)
{
if (srcSize < 1) return (size_t)-FSE_ERROR_srcSize_wrong;
if (srcSize >= sizeof(size_t))
{
U32 contain32;
bitD->start = (const char*)srcBuffer;
bitD->ptr = (const char*)srcBuffer + srcSize - sizeof(size_t);
bitD->bitContainer = FSE_readLEST(bitD->ptr);
contain32 = ((const BYTE*)srcBuffer)[srcSize-1];
if (contain32 == 0) return (size_t)-FSE_ERROR_GENERIC; /* stop bit not present */
bitD->bitsConsumed = 8 - FSE_highbit32(contain32);
}
else
{
U32 contain32;
bitD->start = (const char*)srcBuffer;
bitD->ptr = bitD->start;
bitD->bitContainer = *(const BYTE*)(bitD->start);
switch(srcSize)
{
case 7: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[6]) << (sizeof(size_t)*8 - 16);
/* fallthrough */
case 6: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[5]) << (sizeof(size_t)*8 - 24);
/* fallthrough */
case 5: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[4]) << (sizeof(size_t)*8 - 32);
/* fallthrough */
case 4: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[3]) << 24;
/* fallthrough */
case 3: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[2]) << 16;
/* fallthrough */
case 2: bitD->bitContainer += (size_t)(((const BYTE*)(bitD->start))[1]) << 8;
/* fallthrough */
default:;
}
contain32 = ((const BYTE*)srcBuffer)[srcSize-1];
if (contain32 == 0) return (size_t)-FSE_ERROR_GENERIC; /* stop bit not present */
bitD->bitsConsumed = 8 - FSE_highbit32(contain32);
bitD->bitsConsumed += (U32)(sizeof(size_t) - srcSize)*8;
}
return srcSize;
}
/*!FSE_lookBits
* Provides next n bits from the bitContainer.
* bitContainer is not modified (bits are still present for next read/look)
* On 32-bits, maxNbBits==25
* On 64-bits, maxNbBits==57
* return : value extracted.
*/
static size_t FSE_lookBits(FSE_DStream_t* bitD, U32 nbBits)
{
const U32 bitMask = sizeof(bitD->bitContainer)*8 - 1;
return ((bitD->bitContainer << (bitD->bitsConsumed & bitMask)) >> 1) >> ((bitMask-nbBits) & bitMask);
}
static size_t FSE_lookBitsFast(FSE_DStream_t* bitD, U32 nbBits) /* only if nbBits >= 1 !! */
{
const U32 bitMask = sizeof(bitD->bitContainer)*8 - 1;
return (bitD->bitContainer << (bitD->bitsConsumed & bitMask)) >> (((bitMask+1)-nbBits) & bitMask);
}
static void FSE_skipBits(FSE_DStream_t* bitD, U32 nbBits)
{
bitD->bitsConsumed += nbBits;
}
/*!FSE_readBits
* Read next n bits from the bitContainer.
* On 32-bits, don't read more than maxNbBits==25
* On 64-bits, don't read more than maxNbBits==57
* Use the fast variant *only* if n >= 1.
* return : value extracted.
*/
static size_t FSE_readBits(FSE_DStream_t* bitD, U32 nbBits)
{
size_t value = FSE_lookBits(bitD, nbBits);
FSE_skipBits(bitD, nbBits);
return value;
}
static size_t FSE_readBitsFast(FSE_DStream_t* bitD, U32 nbBits) /* only if nbBits >= 1 !! */
{
size_t value = FSE_lookBitsFast(bitD, nbBits);
FSE_skipBits(bitD, nbBits);
return value;
}
static unsigned FSE_reloadDStream(FSE_DStream_t* bitD)
{
if (bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8)) /* should never happen */
return FSE_DStream_tooFar;
if (bitD->ptr >= bitD->start + sizeof(bitD->bitContainer))
{
bitD->ptr -= bitD->bitsConsumed >> 3;
bitD->bitsConsumed &= 7;
bitD->bitContainer = FSE_readLEST(bitD->ptr);
return FSE_DStream_unfinished;
}
if (bitD->ptr == bitD->start)
{
if (bitD->bitsConsumed < sizeof(bitD->bitContainer)*8) return FSE_DStream_endOfBuffer;
return FSE_DStream_completed;
}
{
U32 nbBytes = bitD->bitsConsumed >> 3;
U32 result = FSE_DStream_unfinished;
if (bitD->ptr - nbBytes < bitD->start)
{
nbBytes = (U32)(bitD->ptr - bitD->start); /* ptr > start */
result = FSE_DStream_endOfBuffer;
}
bitD->ptr -= nbBytes;
bitD->bitsConsumed -= nbBytes*8;
bitD->bitContainer = FSE_readLEST(bitD->ptr); /* reminder : srcSize > sizeof(bitD) */
return result;
}
}
static void FSE_initDState(FSE_DState_t* DStatePtr, FSE_DStream_t* bitD, const FSE_DTable* dt)
{
const void* ptr = dt;
const FSE_DTableHeader* const DTableH = (const FSE_DTableHeader*)ptr;
DStatePtr->state = FSE_readBits(bitD, DTableH->tableLog);
FSE_reloadDStream(bitD);
DStatePtr->table = dt + 1;
}
static BYTE FSE_decodeSymbol(FSE_DState_t* DStatePtr, FSE_DStream_t* bitD)
{
const FSE_decode_t DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
const U32 nbBits = DInfo.nbBits;
BYTE symbol = DInfo.symbol;
size_t lowBits = FSE_readBits(bitD, nbBits);
DStatePtr->state = DInfo.newState + lowBits;
return symbol;
}
static BYTE FSE_decodeSymbolFast(FSE_DState_t* DStatePtr, FSE_DStream_t* bitD)
{
const FSE_decode_t DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
const U32 nbBits = DInfo.nbBits;
BYTE symbol = DInfo.symbol;
size_t lowBits = FSE_readBitsFast(bitD, nbBits);
DStatePtr->state = DInfo.newState + lowBits;
return symbol;
}
/* FSE_endOfDStream
Tells if bitD has reached end of bitStream or not */
static unsigned FSE_endOfDStream(const FSE_DStream_t* bitD)
{
return ((bitD->ptr == bitD->start) && (bitD->bitsConsumed == sizeof(bitD->bitContainer)*8));
}
static unsigned FSE_endOfDState(const FSE_DState_t* DStatePtr)
{
return DStatePtr->state == 0;
}
FORCE_INLINE size_t FSE_decompress_usingDTable_generic(
void* dst, size_t maxDstSize,
const void* cSrc, size_t cSrcSize,
const FSE_DTable* dt, const unsigned fast)
{
BYTE* const ostart = (BYTE*) dst;
BYTE* op = ostart;
BYTE* const omax = op + maxDstSize;
BYTE* const olimit = omax-3;
FSE_DStream_t bitD;
FSE_DState_t state1;
FSE_DState_t state2;
size_t errorCode;
/* Init */
errorCode = FSE_initDStream(&bitD, cSrc, cSrcSize); /* replaced last arg by maxCompressed Size */
if (FSE_isError(errorCode)) return errorCode;
FSE_initDState(&state1, &bitD, dt);
FSE_initDState(&state2, &bitD, dt);
#define FSE_GETSYMBOL(statePtr) fast ? FSE_decodeSymbolFast(statePtr, &bitD) : FSE_decodeSymbol(statePtr, &bitD)
/* 4 symbols per loop */
for ( ; (FSE_reloadDStream(&bitD)==FSE_DStream_unfinished) && (op<olimit) ; op+=4)
{
op[0] = FSE_GETSYMBOL(&state1);
if (FSE_MAX_TABLELOG*2+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
FSE_reloadDStream(&bitD);
op[1] = FSE_GETSYMBOL(&state2);
if (FSE_MAX_TABLELOG*4+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
{ if (FSE_reloadDStream(&bitD) > FSE_DStream_unfinished) { op+=2; break; } }
op[2] = FSE_GETSYMBOL(&state1);
if (FSE_MAX_TABLELOG*2+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
FSE_reloadDStream(&bitD);
op[3] = FSE_GETSYMBOL(&state2);
}
/* tail */
/* note : FSE_reloadDStream(&bitD) >= FSE_DStream_partiallyFilled; Ends at exactly FSE_DStream_completed */
while (1)
{
if ( (FSE_reloadDStream(&bitD)>FSE_DStream_completed) || (op==omax) || (FSE_endOfDStream(&bitD) && (fast || FSE_endOfDState(&state1))) )
break;
*op++ = FSE_GETSYMBOL(&state1);
if ( (FSE_reloadDStream(&bitD)>FSE_DStream_completed) || (op==omax) || (FSE_endOfDStream(&bitD) && (fast || FSE_endOfDState(&state2))) )
break;
*op++ = FSE_GETSYMBOL(&state2);
}
/* end ? */
if (FSE_endOfDStream(&bitD) && FSE_endOfDState(&state1) && FSE_endOfDState(&state2))
return op-ostart;
if (op==omax) return (size_t)-FSE_ERROR_dstSize_tooSmall; /* dst buffer is full, but cSrc unfinished */
return (size_t)-FSE_ERROR_corruptionDetected;
}
static size_t FSE_decompress_usingDTable(void* dst, size_t originalSize,
const void* cSrc, size_t cSrcSize,
const FSE_DTable* dt)
{
FSE_DTableHeader DTableH;
memcpy(&DTableH, dt, sizeof(DTableH)); /* memcpy() into local variable, to avoid strict aliasing warning */
/* select fast mode (static) */
if (DTableH.fastMode) return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 1);
return FSE_decompress_usingDTable_generic(dst, originalSize, cSrc, cSrcSize, dt, 0);
}
static size_t FSE_decompress(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize)
{
const BYTE* const istart = (const BYTE*)cSrc;
const BYTE* ip = istart;
short counting[FSE_MAX_SYMBOL_VALUE+1];
DTable_max_t dt; /* Static analyzer seems unable to understand this table will be properly initialized later */
unsigned tableLog;
unsigned maxSymbolValue = FSE_MAX_SYMBOL_VALUE;
size_t errorCode;
if (cSrcSize<2) return (size_t)-FSE_ERROR_srcSize_wrong; /* too small input size */
/* normal FSE decoding mode */
errorCode = FSE_readNCount (counting, &maxSymbolValue, &tableLog, istart, cSrcSize);
if (FSE_isError(errorCode)) return errorCode;
if (errorCode >= cSrcSize) return (size_t)-FSE_ERROR_srcSize_wrong; /* too small input size */
ip += errorCode;
cSrcSize -= errorCode;
errorCode = FSE_buildDTable (dt, counting, maxSymbolValue, tableLog);
if (FSE_isError(errorCode)) return errorCode;
/* always return, even if it is an error code */
return FSE_decompress_usingDTable (dst, maxDstSize, ip, cSrcSize, dt);
}
/* *******************************************************
* Huff0 : Huffman block compression
*********************************************************/
#define HUF_MAX_SYMBOL_VALUE 255
#define HUF_DEFAULT_TABLELOG 12 /* used by default, when not specified */
#define HUF_MAX_TABLELOG 12 /* max possible tableLog; for allocation purpose; can be modified */
#define HUF_ABSOLUTEMAX_TABLELOG 16 /* absolute limit of HUF_MAX_TABLELOG. Beyond that value, code does not work */
#if (HUF_MAX_TABLELOG > HUF_ABSOLUTEMAX_TABLELOG)
# error "HUF_MAX_TABLELOG is too large !"
#endif
typedef struct HUF_CElt_s {
U16 val;
BYTE nbBits;
} HUF_CElt ;
typedef struct nodeElt_s {
U32 count;
U16 parent;
BYTE byte;
BYTE nbBits;
} nodeElt;
/* *******************************************************
* Huff0 : Huffman block decompression
*********************************************************/
typedef struct {
BYTE byte;
BYTE nbBits;
} HUF_DElt;
static size_t HUF_readDTable (U16* DTable, const void* src, size_t srcSize)
{
BYTE huffWeight[HUF_MAX_SYMBOL_VALUE + 1];
U32 rankVal[HUF_ABSOLUTEMAX_TABLELOG + 1]; /* large enough for values from 0 to 16 */
U32 weightTotal;
U32 maxBits;
const BYTE* ip = (const BYTE*) src;
size_t iSize;
size_t oSize;
U32 n;
U32 nextRankStart;
void* ptr = DTable+1;
HUF_DElt* const dt = (HUF_DElt*)ptr;
if (!srcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
iSize = ip[0];
FSE_STATIC_ASSERT(sizeof(HUF_DElt) == sizeof(U16)); /* if compilation fails here, assertion is false */
//memset(huffWeight, 0, sizeof(huffWeight)); /* should not be necessary, but some analyzer complain ... */
if (iSize >= 128) /* special header */
{
if (iSize >= (242)) /* RLE */
{
static int l[14] = { 1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128 };
oSize = l[iSize-242];
memset(huffWeight, 1, sizeof(huffWeight));
iSize = 0;
}
else /* Incompressible */
{
oSize = iSize - 127;
iSize = ((oSize+1)/2);
if (iSize+1 > srcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
ip += 1;
for (n=0; n<oSize; n+=2)
{
huffWeight[n] = ip[n/2] >> 4;
huffWeight[n+1] = ip[n/2] & 15;
}
}
}
else /* header compressed with FSE (normal case) */
{
if (iSize+1 > srcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
oSize = FSE_decompress(huffWeight, HUF_MAX_SYMBOL_VALUE, ip+1, iSize); /* max 255 values decoded, last one is implied */
if (FSE_isError(oSize)) return oSize;
}
/* collect weight stats */
memset(rankVal, 0, sizeof(rankVal));
weightTotal = 0;
for (n=0; n<oSize; n++)
{
if (huffWeight[n] >= HUF_ABSOLUTEMAX_TABLELOG) return (size_t)-FSE_ERROR_corruptionDetected;
rankVal[huffWeight[n]]++;
weightTotal += (1 << huffWeight[n]) >> 1;
}
if (weightTotal == 0) return (size_t)-FSE_ERROR_corruptionDetected;
/* get last non-null symbol weight (implied, total must be 2^n) */
maxBits = FSE_highbit32(weightTotal) + 1;
if (maxBits > DTable[0]) return (size_t)-FSE_ERROR_tableLog_tooLarge; /* DTable is too small */
DTable[0] = (U16)maxBits;
{
U32 total = 1 << maxBits;
U32 rest = total - weightTotal;
U32 verif = 1 << FSE_highbit32(rest);
U32 lastWeight = FSE_highbit32(rest) + 1;
if (verif != rest) return (size_t)-FSE_ERROR_corruptionDetected; /* last value must be a clean power of 2 */
huffWeight[oSize] = (BYTE)lastWeight;
rankVal[lastWeight]++;
}
/* check tree construction validity */
if ((rankVal[1] < 2) || (rankVal[1] & 1)) return (size_t)-FSE_ERROR_corruptionDetected; /* by construction : at least 2 elts of rank 1, must be even */
/* Prepare ranks */
nextRankStart = 0;
for (n=1; n<=maxBits; n++)
{
U32 current = nextRankStart;
nextRankStart += (rankVal[n] << (n-1));
rankVal[n] = current;
}
/* fill DTable */
for (n=0; n<=oSize; n++)
{
const U32 w = huffWeight[n];
const U32 length = (1 << w) >> 1;
U32 i;
HUF_DElt D;
D.byte = (BYTE)n; D.nbBits = (BYTE)(maxBits + 1 - w);
for (i = rankVal[w]; i < rankVal[w] + length; i++)
dt[i] = D;
rankVal[w] += length;
}
return iSize+1;
}
static BYTE HUF_decodeSymbol(FSE_DStream_t* Dstream, const HUF_DElt* dt, const U32 dtLog)
{
const size_t val = FSE_lookBitsFast(Dstream, dtLog); /* note : dtLog >= 1 */
const BYTE c = dt[val].byte;
FSE_skipBits(Dstream, dt[val].nbBits);
return c;
}
static size_t HUF_decompress_usingDTable( /* -3% slower when non static */
void* dst, size_t maxDstSize,
const void* cSrc, size_t cSrcSize,
const U16* DTable)
{
if (cSrcSize < 6) return (size_t)-FSE_ERROR_srcSize_wrong;
{
BYTE* const ostart = (BYTE*) dst;
BYTE* op = ostart;
BYTE* const omax = op + maxDstSize;
BYTE* const olimit = maxDstSize < 15 ? op : omax-15;
const void* ptr = DTable;
const HUF_DElt* const dt = (const HUF_DElt*)(ptr)+1;
const U32 dtLog = DTable[0];
size_t errorCode;
U32 reloadStatus;
/* Init */
const U16* jumpTable = (const U16*)cSrc;
const size_t length1 = FSE_readLE16(jumpTable);
const size_t length2 = FSE_readLE16(jumpTable+1);
const size_t length3 = FSE_readLE16(jumpTable+2);
const size_t length4 = cSrcSize - 6 - length1 - length2 - length3; /* check coherency !! */
const char* const start1 = (const char*)(cSrc) + 6;
const char* const start2 = start1 + length1;
const char* const start3 = start2 + length2;
const char* const start4 = start3 + length3;
FSE_DStream_t bitD1, bitD2, bitD3, bitD4;
if (length1+length2+length3+6 >= cSrcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
errorCode = FSE_initDStream(&bitD1, start1, length1);
if (FSE_isError(errorCode)) return errorCode;
errorCode = FSE_initDStream(&bitD2, start2, length2);
if (FSE_isError(errorCode)) return errorCode;
errorCode = FSE_initDStream(&bitD3, start3, length3);
if (FSE_isError(errorCode)) return errorCode;
errorCode = FSE_initDStream(&bitD4, start4, length4);
if (FSE_isError(errorCode)) return errorCode;
reloadStatus=FSE_reloadDStream(&bitD2);
/* 16 symbols per loop */
for ( ; (reloadStatus<FSE_DStream_completed) && (op<olimit); /* D2-3-4 are supposed to be synchronized and finish together */
op+=16, reloadStatus = FSE_reloadDStream(&bitD2) | FSE_reloadDStream(&bitD3) | FSE_reloadDStream(&bitD4), FSE_reloadDStream(&bitD1))
{
#define HUF_DECODE_SYMBOL_0(n, Dstream) \
op[n] = HUF_decodeSymbol(&Dstream, dt, dtLog);
#define HUF_DECODE_SYMBOL_1(n, Dstream) \
op[n] = HUF_decodeSymbol(&Dstream, dt, dtLog); \
if (FSE_32bits() && (HUF_MAX_TABLELOG>12)) FSE_reloadDStream(&Dstream)
#define HUF_DECODE_SYMBOL_2(n, Dstream) \
op[n] = HUF_decodeSymbol(&Dstream, dt, dtLog); \
if (FSE_32bits()) FSE_reloadDStream(&Dstream)
HUF_DECODE_SYMBOL_1( 0, bitD1);
HUF_DECODE_SYMBOL_1( 1, bitD2);
HUF_DECODE_SYMBOL_1( 2, bitD3);
HUF_DECODE_SYMBOL_1( 3, bitD4);
HUF_DECODE_SYMBOL_2( 4, bitD1);
HUF_DECODE_SYMBOL_2( 5, bitD2);
HUF_DECODE_SYMBOL_2( 6, bitD3);
HUF_DECODE_SYMBOL_2( 7, bitD4);
HUF_DECODE_SYMBOL_1( 8, bitD1);
HUF_DECODE_SYMBOL_1( 9, bitD2);
HUF_DECODE_SYMBOL_1(10, bitD3);
HUF_DECODE_SYMBOL_1(11, bitD4);
HUF_DECODE_SYMBOL_0(12, bitD1);
HUF_DECODE_SYMBOL_0(13, bitD2);
HUF_DECODE_SYMBOL_0(14, bitD3);
HUF_DECODE_SYMBOL_0(15, bitD4);
}
if (reloadStatus!=FSE_DStream_completed) /* not complete : some bitStream might be FSE_DStream_unfinished */
return (size_t)-FSE_ERROR_corruptionDetected;
/* tail */
{
/* bitTail = bitD1; */ /* *much* slower : -20% !??! */
FSE_DStream_t bitTail;
bitTail.ptr = bitD1.ptr;
bitTail.bitsConsumed = bitD1.bitsConsumed;
bitTail.bitContainer = bitD1.bitContainer; /* required in case of FSE_DStream_endOfBuffer */
bitTail.start = start1;
for ( ; (FSE_reloadDStream(&bitTail) < FSE_DStream_completed) && (op<omax) ; op++)
{
HUF_DECODE_SYMBOL_0(0, bitTail);
}
if (FSE_endOfDStream(&bitTail))
return op-ostart;
}
if (op==omax) return (size_t)-FSE_ERROR_dstSize_tooSmall; /* dst buffer is full, but cSrc unfinished */
return (size_t)-FSE_ERROR_corruptionDetected;
}
}
static size_t HUF_decompress (void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize)
{
HUF_CREATE_STATIC_DTABLE(DTable, HUF_MAX_TABLELOG);
const BYTE* ip = (const BYTE*) cSrc;
size_t errorCode;
errorCode = HUF_readDTable (DTable, cSrc, cSrcSize);
if (FSE_isError(errorCode)) return errorCode;
if (errorCode >= cSrcSize) return (size_t)-FSE_ERROR_srcSize_wrong;
ip += errorCode;
cSrcSize -= errorCode;
return HUF_decompress_usingDTable (dst, maxDstSize, ip, cSrcSize, DTable);
}
#endif /* FSE_COMMONDEFS_ONLY */
/*
zstd - standard compression library
Copyright (C) 2014-2015, Yann Collet.
BSD 2-Clause License (https://opensource.org/licenses/bsd-license.php)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You can contact the author at :
- zstd source repository : https://github.com/Cyan4973/zstd
- ztsd public forum : https://groups.google.com/forum/#!forum/lz4c
*/
/****************************************************************
* Tuning parameters
*****************************************************************/
/* MEMORY_USAGE :
* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
* Increasing memory usage improves compression ratio
* Reduced memory usage can improve speed, due to cache effect */
#define ZSTD_MEMORY_USAGE 17
/**************************************
CPU Feature Detection
**************************************/
/*
* Automated efficient unaligned memory access detection
* Based on known hardware architectures
* This list will be updated thanks to feedbacks
*/
#if defined(CPU_HAS_EFFICIENT_UNALIGNED_MEMORY_ACCESS) \
|| defined(__ARM_FEATURE_UNALIGNED) \
|| defined(__i386__) || defined(__x86_64__) \
|| defined(_M_IX86) || defined(_M_X64) \
|| defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_8__) \
|| (defined(_M_ARM) && (_M_ARM >= 7))
# define ZSTD_UNALIGNED_ACCESS 1
#else
# define ZSTD_UNALIGNED_ACCESS 0
#endif
/********************************************************
* Includes
*********************************************************/
#include <stdlib.h> /* calloc */
#include <string.h> /* memcpy, memmove */
#include <stdio.h> /* debug : printf */
/********************************************************
* Compiler specifics
*********************************************************/
#ifdef __AVX2__
# include <immintrin.h> /* AVX2 intrinsics */
#endif
#ifdef _MSC_VER /* Visual Studio */
# include <intrin.h> /* For Visual 2005 */
# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
# pragma warning(disable : 4324) /* disable: C4324: padded structure */
#endif
#ifndef MEM_ACCESS_MODULE
#define MEM_ACCESS_MODULE
/********************************************************
* Basic Types
*********************************************************/
#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
# if defined(_AIX)
# include <inttypes.h>
# else
# include <stdint.h> /* intptr_t */
# endif
typedef uint8_t BYTE;
typedef uint16_t U16;
typedef int16_t S16;
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
#else
typedef unsigned char BYTE;
typedef unsigned short U16;
typedef signed short S16;
typedef unsigned int U32;
typedef signed int S32;
typedef unsigned long long U64;
#endif
#endif /* MEM_ACCESS_MODULE */
/********************************************************
* Constants
*********************************************************/
static const U32 ZSTD_magicNumber = 0xFD2FB51E; /* 3rd version : seqNb header */
#define HASH_LOG (ZSTD_MEMORY_USAGE - 2)
#define HASH_TABLESIZE (1 << HASH_LOG)
#define HASH_MASK (HASH_TABLESIZE - 1)
#define KNUTH 2654435761
#define BIT7 128
#define BIT6 64
#define BIT5 32
#define BIT4 16
#define KB *(1 <<10)
#define MB *(1 <<20)
#define GB *(1U<<30)
#define BLOCKSIZE (128 KB) /* define, for static allocation */
#define WORKPLACESIZE (BLOCKSIZE*3)
#define MINMATCH 4
#define MLbits 7
#define LLbits 6
#define Offbits 5
#define MaxML ((1<<MLbits )-1)
#define MaxLL ((1<<LLbits )-1)
#define MaxOff ((1<<Offbits)-1)
#define LitFSELog 11
#define MLFSELog 10
#define LLFSELog 10
#define OffFSELog 9
#define MAX(a,b) ((a)<(b)?(b):(a))
#define MaxSeq MAX(MaxLL, MaxML)
#define LITERAL_NOENTROPY 63
#define COMMAND_NOENTROPY 7 /* to remove */
#define ZSTD_CONTENTSIZE_ERROR (0ULL - 2)
static const size_t ZSTD_blockHeaderSize = 3;
static const size_t ZSTD_frameHeaderSize = 4;
/********************************************************
* Memory operations
*********************************************************/
static unsigned ZSTD_32bits(void) { return sizeof(void*)==4; }
static unsigned ZSTD_isLittleEndian(void)
{
const union { U32 i; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
return one.c[0];
}
static U16 ZSTD_read16(const void* p) { U16 r; memcpy(&r, p, sizeof(r)); return r; }
static void ZSTD_copy4(void* dst, const void* src) { memcpy(dst, src, 4); }
static void ZSTD_copy8(void* dst, const void* src) { memcpy(dst, src, 8); }
#define COPY8(d,s) { ZSTD_copy8(d,s); d+=8; s+=8; }
static void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length)
{
const BYTE* ip = (const BYTE*)src;
BYTE* op = (BYTE*)dst;
BYTE* const oend = op + length;
while (op < oend) COPY8(op, ip);
}
static U16 ZSTD_readLE16(const void* memPtr)
{
if (ZSTD_isLittleEndian()) return ZSTD_read16(memPtr);
else
{
const BYTE* p = (const BYTE*)memPtr;
return (U16)((U16)p[0] + ((U16)p[1]<<8));
}
}
static U32 ZSTD_readLE24(const void* memPtr)
{
return ZSTD_readLE16(memPtr) + (((const BYTE*)memPtr)[2] << 16);
}
static U32 ZSTD_readBE32(const void* memPtr)
{
const BYTE* p = (const BYTE*)memPtr;
return (U32)(((U32)p[0]<<24) + ((U32)p[1]<<16) + ((U32)p[2]<<8) + ((U32)p[3]<<0));
}
/**************************************
* Local structures
***************************************/
typedef struct ZSTD_Cctx_s ZSTD_Cctx;
typedef enum { bt_compressed, bt_raw, bt_rle, bt_end } blockType_t;
typedef struct
{
blockType_t blockType;
U32 origSize;
} blockProperties_t;
typedef struct {
void* buffer;
U32* offsetStart;
U32* offset;
BYTE* offCodeStart;
BYTE* offCode;
BYTE* litStart;
BYTE* lit;
BYTE* litLengthStart;
BYTE* litLength;
BYTE* matchLengthStart;
BYTE* matchLength;
BYTE* dumpsStart;
BYTE* dumps;
} SeqStore_t;
typedef struct ZSTD_Cctx_s
{
const BYTE* base;
U32 current;
U32 nextUpdate;
SeqStore_t seqStore;
#ifdef __AVX2__
__m256i hashTable[HASH_TABLESIZE>>3];
#else
U32 hashTable[HASH_TABLESIZE];
#endif
BYTE buffer[WORKPLACESIZE];
} cctxi_t;
/**************************************
* Error Management
**************************************/
/* published entry point */
unsigned ZSTDv01_isError(size_t code) { return ERR_isError(code); }
/**************************************
* Tool functions
**************************************/
#define ZSTD_VERSION_MAJOR 0 /* for breaking interface changes */
#define ZSTD_VERSION_MINOR 1 /* for new (non-breaking) interface capabilities */
#define ZSTD_VERSION_RELEASE 3 /* for tweaks, bug-fixes, or development */
#define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE)
/**************************************************************
* Decompression code
**************************************************************/
static size_t ZSTDv01_getcBlockSize(const void* src, size_t srcSize, blockProperties_t* bpPtr)
{
const BYTE* const in = (const BYTE* const)src;
BYTE headerFlags;
U32 cSize;
if (srcSize < 3) return ERROR(srcSize_wrong);
headerFlags = *in;
cSize = in[2] + (in[1]<<8) + ((in[0] & 7)<<16);
bpPtr->blockType = (blockType_t)(headerFlags >> 6);
bpPtr->origSize = (bpPtr->blockType == bt_rle) ? cSize : 0;
if (bpPtr->blockType == bt_end) return 0;
if (bpPtr->blockType == bt_rle) return 1;
return cSize;
}
static size_t ZSTD_copyUncompressedBlock(void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
if (srcSize > maxDstSize) return ERROR(dstSize_tooSmall);
if (srcSize > 0) {
memcpy(dst, src, srcSize);
}
return srcSize;
}
static size_t ZSTD_decompressLiterals(void* ctx,
void* dst, size_t maxDstSize,
const void* src, size_t srcSize)
{
BYTE* op = (BYTE*)dst;
BYTE* const oend = op + maxDstSize;
const BYTE* ip = (const BYTE*)src;
size_t errorCode;
size_t litSize;
/* check : minimum 2, for litSize, +1, for content */
if (srcSize <= 3) return ERROR(corruption_detected);
litSize = ip[1] + (ip[0]<<8);
litSize += ((ip[-3] >> 3) & 7) << 16; /* mmmmh.... */
op = oend - litSize;
(void)ctx;
if (litSize > maxDstSize) return ERROR(dstSize_tooSmall);
errorCode = HUF_decompress(op, litSize, ip+2, srcSize-2);
if (FSE_isError(errorCode)) return ERROR(GENERIC);
return litSize;
}
static size_t ZSTDv01_decodeLiteralsBlock(void* ctx,
void* dst, size_t maxDstSize,
const BYTE** litStart, size_t* litSize,
const void* src, size_t srcSize)
{
const BYTE* const istart = (const BYTE* const)src;
const BYTE* ip = istart;
BYTE* const ostart = (BYTE* const)dst;
BYTE* const oend = ostart + maxDstSize;
blockProperties_t litbp;
size_t litcSize = ZSTDv01_getcBlockSize(src, srcSize, &litbp);
if (ZSTDv01_isError(litcSize)) return litcSize;
if (litcSize > srcSize - ZSTD_blockHeaderSize) return ERROR(srcSize_wrong);
ip += ZSTD_blockHeaderSize;
switch(litbp.blockType)
{
case bt_raw:
*litStart = ip;
ip += litcSize;
*litSize = litcSize;
break;
case bt_rle:
{
size_t rleSize = litbp.origSize;
if (rleSize>maxDstSize) return ERROR(dstSize_tooSmall);
if (!srcSize) return ERROR(srcSize_wrong);
if (rleSize > 0) {
memset(oend - rleSize, *ip, rleSize);
}
*litStart = oend - rleSize;
*litSize = rleSize;
ip++;
break;
}
case bt_compressed:
{
size_t decodedLitSize = ZSTD_decompressLiterals(ctx, dst, maxDstSize, ip, litcSize);
if (ZSTDv01_isError(decodedLitSize)) return decodedLitSize;
*litStart = oend - decodedLitSize;
*litSize = decodedLitSize;
ip += litcSize;
break;
}
case bt_end:
default:
return ERROR(GENERIC);
}
return ip-istart;
}
static size_t ZSTDv01_decodeSeqHeaders(int* nbSeq, const BYTE** dumpsPtr, size_t* dumpsLengthPtr,
FSE_DTable* DTableLL, FSE_DTable* DTableML, FSE_DTable* DTableOffb,
const void* src, size_t srcSize)
{
const BYTE* const istart = (const BYTE* const)src;
const BYTE* ip = istart;
const BYTE* const iend = istart + srcSize;
U32 LLtype, Offtype, MLtype;
U32 LLlog, Offlog, MLlog;
size_t dumpsLength;
/* check */
if (srcSize < 5) return ERROR(srcSize_wrong);
/* SeqHead */
*nbSeq = ZSTD_readLE16(ip); ip+=2;
LLtype = *ip >> 6;
Offtype = (*ip >> 4) & 3;
MLtype = (*ip >> 2) & 3;
if (*ip & 2)
{
dumpsLength = ip[2];
dumpsLength += ip[1] << 8;
ip += 3;
}
else
{
dumpsLength = ip[1];
dumpsLength += (ip[0] & 1) << 8;
ip += 2;
}
*dumpsPtr = ip;
ip += dumpsLength;
*dumpsLengthPtr = dumpsLength;
/* check */
if (ip > iend-3) return ERROR(srcSize_wrong); /* min : all 3 are "raw", hence no header, but at least xxLog bits per type */
/* sequences */
{
S16 norm[MaxML+1]; /* assumption : MaxML >= MaxLL and MaxOff */
size_t headerSize;
/* Build DTables */
switch(LLtype)
{
case bt_rle :
LLlog = 0;
FSE_buildDTable_rle(DTableLL, *ip++); break;
case bt_raw :
LLlog = LLbits;
FSE_buildDTable_raw(DTableLL, LLbits); break;
default :
{ U32 max = MaxLL;
headerSize = FSE_readNCount(norm, &max, &LLlog, ip, iend-ip);
if (FSE_isError(headerSize)) return ERROR(GENERIC);
if (LLlog > LLFSELog) return ERROR(corruption_detected);
ip += headerSize;
FSE_buildDTable(DTableLL, norm, max, LLlog);
} }
switch(Offtype)
{
case bt_rle :
Offlog = 0;
if (ip > iend-2) return ERROR(srcSize_wrong); /* min : "raw", hence no header, but at least xxLog bits */
FSE_buildDTable_rle(DTableOffb, *ip++); break;
case bt_raw :
Offlog = Offbits;
FSE_buildDTable_raw(DTableOffb, Offbits); break;
default :
{ U32 max = MaxOff;
headerSize = FSE_readNCount(norm, &max, &Offlog, ip, iend-ip);
if (FSE_isError(headerSize)) return ERROR(GENERIC);
if (Offlog > OffFSELog) return ERROR(corruption_detected);
ip += headerSize;
FSE_buildDTable(DTableOffb, norm, max, Offlog);
} }
switch(MLtype)
{
case bt_rle :
MLlog = 0;
if (ip > iend-2) return ERROR(srcSize_wrong); /* min : "raw", hence no header, but at least xxLog bits */
FSE_buildDTable_rle(DTableML, *ip++); break;
case bt_raw :
MLlog = MLbits;
FSE_buildDTable_raw(DTableML, MLbits); break;
default :
{ U32 max = MaxML;
headerSize = FSE_readNCount(norm, &max, &MLlog, ip, iend-ip);
if (FSE_isError(headerSize)) return ERROR(GENERIC);
if (MLlog > MLFSELog) return ERROR(corruption_detected);
ip += headerSize;
FSE_buildDTable(DTableML, norm, max, MLlog);
} } }
return ip-istart;
}
typedef struct {
size_t litLength;
size_t offset;
size_t matchLength;
} seq_t;
typedef struct {
FSE_DStream_t DStream;
FSE_DState_t stateLL;
FSE_DState_t stateOffb;
FSE_DState_t stateML;
size_t prevOffset;
const BYTE* dumps;
const BYTE* dumpsEnd;
} seqState_t;
static void ZSTD_decodeSequence(seq_t* seq, seqState_t* seqState)
{
size_t litLength;
size_t prevOffset;
size_t offset;
size_t matchLength;
const BYTE* dumps = seqState->dumps;
const BYTE* const de = seqState->dumpsEnd;
/* Literal length */
litLength = FSE_decodeSymbol(&(seqState->stateLL), &(seqState->DStream));
prevOffset = litLength ? seq->offset : seqState->prevOffset;
seqState->prevOffset = seq->offset;
if (litLength == MaxLL)
{
const U32 add = dumps<de ? *dumps++ : 0;
if (add < 255) litLength += add;
else
{
if (dumps<=(de-3))
{
litLength = ZSTD_readLE24(dumps);
dumps += 3;
}
}
}
/* Offset */
{
U32 offsetCode, nbBits;
offsetCode = FSE_decodeSymbol(&(seqState->stateOffb), &(seqState->DStream));
if (ZSTD_32bits()) FSE_reloadDStream(&(seqState->DStream));
nbBits = offsetCode - 1;
if (offsetCode==0) nbBits = 0; /* cmove */
offset = ((size_t)1 << (nbBits & ((sizeof(offset)*8)-1))) + FSE_readBits(&(seqState->DStream), nbBits);
if (ZSTD_32bits()) FSE_reloadDStream(&(seqState->DStream));
if (offsetCode==0) offset = prevOffset;
}
/* MatchLength */
matchLength = FSE_decodeSymbol(&(seqState->stateML), &(seqState->DStream));
if (matchLength == MaxML)
{
const U32 add = dumps<de ? *dumps++ : 0;
if (add < 255) matchLength += add;
else
{
if (dumps<=(de-3))
{
matchLength = ZSTD_readLE24(dumps);
dumps += 3;
}
}
}
matchLength += MINMATCH;
/* save result */
seq->litLength = litLength;
seq->offset = offset;
seq->matchLength = matchLength;
seqState->dumps = dumps;
}
static size_t ZSTD_execSequence(BYTE* op,
seq_t sequence,
const BYTE** litPtr, const BYTE* const litLimit,
BYTE* const base, BYTE* const oend)
{
static const int dec32table[] = {0, 1, 2, 1, 4, 4, 4, 4}; /* added */
static const int dec64table[] = {8, 8, 8, 7, 8, 9,10,11}; /* subtracted */
const BYTE* const ostart = op;
BYTE* const oLitEnd = op + sequence.litLength;
const size_t litLength = sequence.litLength;
BYTE* const endMatch = op + litLength + sequence.matchLength; /* risk : address space overflow (32-bits) */
const BYTE* const litEnd = *litPtr + litLength;
/* checks */
size_t const seqLength = sequence.litLength + sequence.matchLength;
if (seqLength > (size_t)(oend - op)) return ERROR(dstSize_tooSmall);
if (sequence.litLength > (size_t)(litLimit - *litPtr)) return ERROR(corruption_detected);
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */
if (sequence.offset > (U32)(oLitEnd - base)) return ERROR(corruption_detected);
if (endMatch > oend) return ERROR(dstSize_tooSmall); /* overwrite beyond dst buffer */
if (litEnd > litLimit) return ERROR(corruption_detected); /* overRead beyond lit buffer */
if (sequence.matchLength > (size_t)(*litPtr-op)) return ERROR(dstSize_tooSmall); /* overwrite literal segment */
/* copy Literals */
ZSTD_memmove(op, *litPtr, sequence.litLength); /* note : v0.1 seems to allow scenarios where output or input are close to end of buffer */
op += litLength;
*litPtr = litEnd; /* update for next sequence */
/* check : last match must be at a minimum distance of 8 from end of dest buffer */
if (oend-op < 8) return ERROR(dstSize_tooSmall);
/* copy Match */
{
const U32 overlapRisk = (((size_t)(litEnd - endMatch)) < 12);
const BYTE* match = op - sequence.offset; /* possible underflow at op - offset ? */
size_t qutt = 12;
U64 saved[2];
/* check */
if (match < base) return ERROR(corruption_detected);
if (sequence.offset > (size_t)base) return ERROR(corruption_detected);
/* save beginning of literal sequence, in case of write overlap */
if (overlapRisk)
{
if ((endMatch + qutt) > oend) qutt = oend-endMatch;
memcpy(saved, endMatch, qutt);
}
if (sequence.offset < 8)
{
const int dec64 = dec64table[sequence.offset];
op[0] = match[0];
op[1] = match[1];
op[2] = match[2];
op[3] = match[3];
match += dec32table[sequence.offset];
ZSTD_copy4(op+4, match);
match -= dec64;
} else { ZSTD_copy8(op, match); }
op += 8; match += 8;
if (endMatch > oend-(16-MINMATCH))
{
if (op < oend-8)
{
ZSTD_wildcopy(op, match, (oend-8) - op);
match += (oend-8) - op;
op = oend-8;
}
while (op<endMatch) *op++ = *match++;
}
else
ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength-8); /* works even if matchLength < 8 */
/* restore, in case of overlap */
if (overlapRisk) memcpy(endMatch, saved, qutt);
}
return endMatch-ostart;
}
typedef struct ZSTDv01_Dctx_s
{
U32 LLTable[FSE_DTABLE_SIZE_U32(LLFSELog)];
U32 OffTable[FSE_DTABLE_SIZE_U32(OffFSELog)];
U32 MLTable[FSE_DTABLE_SIZE_U32(MLFSELog)];
void* previousDstEnd;
void* base;
size_t expected;
blockType_t bType;
U32 phase;
} dctx_t;
static size_t ZSTD_decompressSequences(
void* ctx,
void* dst, size_t maxDstSize,
const void* seqStart, size_t seqSize,
const BYTE* litStart, size_t litSize)
{
dctx_t* dctx = (dctx_t*)ctx;
const BYTE* ip = (const BYTE*)seqStart;
const BYTE* const iend = ip + seqSize;
BYTE* const ostart = (BYTE* const)dst;
BYTE* op = ostart;
BYTE* const oend = ostart + maxDstSize;
size_t errorCode, dumpsLength;
const BYTE* litPtr = litStart;
const BYTE* const litEnd = litStart + litSize;
int nbSeq;
const BYTE* dumps;
U32* DTableLL = dctx->LLTable;
U32* DTableML = dctx->MLTable;
U32* DTableOffb = dctx->OffTable;
BYTE* const base = (BYTE*) (dctx->base);
/* Build Decoding Tables */
errorCode = ZSTDv01_decodeSeqHeaders(&nbSeq, &dumps, &dumpsLength,
DTableLL, DTableML, DTableOffb,
ip, iend-ip);
if (ZSTDv01_isError(errorCode)) return errorCode;
ip += errorCode;
/* Regen sequences */
{
seq_t sequence;
seqState_t seqState;
memset(&sequence, 0, sizeof(sequence));
seqState.dumps = dumps;
seqState.dumpsEnd = dumps + dumpsLength;
seqState.prevOffset = 1;
errorCode = FSE_initDStream(&(seqState.DStream), ip, iend-ip);
if (FSE_isError(errorCode)) return ERROR(corruption_detected);
FSE_initDState(&(seqState.stateLL), &(seqState.DStream), DTableLL);
FSE_initDState(&(seqState.stateOffb), &(seqState.DStream), DTableOffb);
FSE_initDState(&(seqState.stateML), &(seqState.DStream), DTableML);
for ( ; (FSE_reloadDStream(&(seqState.DStream)) <= FSE_DStream_completed) && (nbSeq>0) ; )
{
size_t oneSeqSize;
nbSeq--;
ZSTD_decodeSequence(&sequence, &seqState);
oneSeqSize = ZSTD_execSequence(op, sequence, &litPtr, litEnd, base, oend);
if (ZSTDv01_isError(oneSeqSize)) return oneSeqSize;
op += oneSeqSize;
}
/* check if reached exact end */
if ( !FSE_endOfDStream(&(seqState.DStream)) ) return ERROR(corruption_detected); /* requested too much : data is corrupted */
if (nbSeq<0) return ERROR(corruption_detected); /* requested too many sequences : data is corrupted */
/* last literal segment */
{
size_t lastLLSize = litEnd - litPtr;
if (op+lastLLSize > oend) return ERROR(dstSize_tooSmall);
if (lastLLSize > 0) {
if (op != litPtr) memmove(op, litPtr, lastLLSize);
op += lastLLSize;
}
}
}
return op-ostart;
}
static size_t ZSTD_decompressBlock(
void* ctx,
void* dst, size_t maxDstSize,
const void* src, size_t srcSize)
{
/* blockType == blockCompressed, srcSize is trusted */
const BYTE* ip = (const BYTE*)src;
const BYTE* litPtr = NULL;
size_t litSize = 0;
size_t errorCode;
/* Decode literals sub-block */
errorCode = ZSTDv01_decodeLiteralsBlock(ctx, dst, maxDstSize, &litPtr, &litSize, src, srcSize);
if (ZSTDv01_isError(errorCode)) return errorCode;
ip += errorCode;
srcSize -= errorCode;
return ZSTD_decompressSequences(ctx, dst, maxDstSize, ip, srcSize, litPtr, litSize);
}
size_t ZSTDv01_decompressDCtx(void* ctx, void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
const BYTE* ip = (const BYTE*)src;
const BYTE* iend = ip + srcSize;
BYTE* const ostart = (BYTE* const)dst;
BYTE* op = ostart;
BYTE* const oend = ostart + maxDstSize;
size_t remainingSize = srcSize;
U32 magicNumber;
size_t errorCode=0;
blockProperties_t blockProperties;
/* Frame Header */
if (srcSize < ZSTD_frameHeaderSize+ZSTD_blockHeaderSize) return ERROR(srcSize_wrong);
magicNumber = ZSTD_readBE32(src);
if (magicNumber != ZSTD_magicNumber) return ERROR(prefix_unknown);
ip += ZSTD_frameHeaderSize; remainingSize -= ZSTD_frameHeaderSize;
/* Loop on each block */
while (1)
{
size_t blockSize = ZSTDv01_getcBlockSize(ip, iend-ip, &blockProperties);
if (ZSTDv01_isError(blockSize)) return blockSize;
ip += ZSTD_blockHeaderSize;
remainingSize -= ZSTD_blockHeaderSize;
if (blockSize > remainingSize) return ERROR(srcSize_wrong);
switch(blockProperties.blockType)
{
case bt_compressed:
errorCode = ZSTD_decompressBlock(ctx, op, oend-op, ip, blockSize);
break;
case bt_raw :
errorCode = ZSTD_copyUncompressedBlock(op, oend-op, ip, blockSize);
break;
case bt_rle :
return ERROR(GENERIC); /* not yet supported */
break;
case bt_end :
/* end of frame */
if (remainingSize) return ERROR(srcSize_wrong);
break;
default:
return ERROR(GENERIC);
}
if (blockSize == 0) break; /* bt_end */
if (ZSTDv01_isError(errorCode)) return errorCode;
op += errorCode;
ip += blockSize;
remainingSize -= blockSize;
}
return op-ostart;
}
size_t ZSTDv01_decompress(void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
dctx_t ctx;
ctx.base = dst;
return ZSTDv01_decompressDCtx(&ctx, dst, maxDstSize, src, srcSize);
}
/* ZSTD_errorFrameSizeInfoLegacy() :
assumes `cSize` and `dBound` are _not_ NULL */
static void ZSTD_errorFrameSizeInfoLegacy(size_t* cSize, unsigned long long* dBound, size_t ret)
{
*cSize = ret;
*dBound = ZSTD_CONTENTSIZE_ERROR;
}
void ZSTDv01_findFrameSizeInfoLegacy(const void *src, size_t srcSize, size_t* cSize, unsigned long long* dBound)
{
const BYTE* ip = (const BYTE*)src;
size_t remainingSize = srcSize;
size_t nbBlocks = 0;
U32 magicNumber;
blockProperties_t blockProperties;
/* Frame Header */
if (srcSize < ZSTD_frameHeaderSize+ZSTD_blockHeaderSize) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, ERROR(srcSize_wrong));
return;
}
magicNumber = ZSTD_readBE32(src);
if (magicNumber != ZSTD_magicNumber) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, ERROR(prefix_unknown));
return;
}
ip += ZSTD_frameHeaderSize; remainingSize -= ZSTD_frameHeaderSize;
/* Loop on each block */
while (1)
{
size_t blockSize = ZSTDv01_getcBlockSize(ip, remainingSize, &blockProperties);
if (ZSTDv01_isError(blockSize)) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, blockSize);
return;
}
ip += ZSTD_blockHeaderSize;
remainingSize -= ZSTD_blockHeaderSize;
if (blockSize > remainingSize) {
ZSTD_errorFrameSizeInfoLegacy(cSize, dBound, ERROR(srcSize_wrong));
return;
}
if (blockSize == 0) break; /* bt_end */
ip += blockSize;
remainingSize -= blockSize;
nbBlocks++;
}
*cSize = ip - (const BYTE*)src;
*dBound = nbBlocks * BLOCKSIZE;
}
/*******************************
* Streaming Decompression API
*******************************/
size_t ZSTDv01_resetDCtx(ZSTDv01_Dctx* dctx)
{
dctx->expected = ZSTD_frameHeaderSize;
dctx->phase = 0;
dctx->previousDstEnd = NULL;
dctx->base = NULL;
return 0;
}
ZSTDv01_Dctx* ZSTDv01_createDCtx(void)
{
ZSTDv01_Dctx* dctx = (ZSTDv01_Dctx*)malloc(sizeof(ZSTDv01_Dctx));
if (dctx==NULL) return NULL;
ZSTDv01_resetDCtx(dctx);
return dctx;
}
size_t ZSTDv01_freeDCtx(ZSTDv01_Dctx* dctx)
{
free(dctx);
return 0;
}
size_t ZSTDv01_nextSrcSizeToDecompress(ZSTDv01_Dctx* dctx)
{
return ((dctx_t*)dctx)->expected;
}
size_t ZSTDv01_decompressContinue(ZSTDv01_Dctx* dctx, void* dst, size_t maxDstSize, const void* src, size_t srcSize)
{
dctx_t* ctx = (dctx_t*)dctx;
/* Sanity check */
if (srcSize != ctx->expected) return ERROR(srcSize_wrong);
if (dst != ctx->previousDstEnd) /* not contiguous */
ctx->base = dst;
/* Decompress : frame header */
if (ctx->phase == 0)
{
/* Check frame magic header */
U32 magicNumber = ZSTD_readBE32(src);
if (magicNumber != ZSTD_magicNumber) return ERROR(prefix_unknown);
ctx->phase = 1;
ctx->expected = ZSTD_blockHeaderSize;
return 0;
}
/* Decompress : block header */
if (ctx->phase == 1)
{
blockProperties_t bp;
size_t blockSize = ZSTDv01_getcBlockSize(src, ZSTD_blockHeaderSize, &bp);
if (ZSTDv01_isError(blockSize)) return blockSize;
if (bp.blockType == bt_end)
{
ctx->expected = 0;
ctx->phase = 0;
}
else
{
ctx->expected = blockSize;
ctx->bType = bp.blockType;
ctx->phase = 2;
}
return 0;
}
/* Decompress : block content */
{
size_t rSize;
switch(ctx->bType)
{
case bt_compressed:
rSize = ZSTD_decompressBlock(ctx, dst, maxDstSize, src, srcSize);
break;
case bt_raw :
rSize = ZSTD_copyUncompressedBlock(dst, maxDstSize, src, srcSize);
break;
case bt_rle :
return ERROR(GENERIC); /* not yet handled */
break;
case bt_end : /* should never happen (filtered at phase 1) */
rSize = 0;
break;
default:
return ERROR(GENERIC);
}
ctx->phase = 1;
ctx->expected = ZSTD_blockHeaderSize;
if (ZSTDv01_isError(rSize)) return rSize;
ctx->previousDstEnd = (void*)( ((char*)dst) + rSize);
return rSize;
}
}
/* Implementation moved to Rust (rust/src/legacy/zstd_v01.rs).
* The frozen v0.1 decoder, including its embedded FSE/Huff0 snapshot and the
* ZSTDv01_Dctx streaming state, now lives entirely in Rust; C code only ever
* holds an opaque ZSTDv01_Dctx pointer. */
+28 -8
View File
@@ -55,16 +55,33 @@ endif
endif
RUST_HUF_FEATURE :=
RUST_BUILD_CONFIG := default
RUST_HUF_MODE := default
ifneq ($(RUST_HUF_FORCE_X1),0)
RUST_HUF_FEATURE := huf-force-decompress-x1
RUST_BUILD_CONFIG := huf-force-decompress-x1
RUST_HUF_MODE := huf-force-decompress-x1
endif
ifneq ($(RUST_HUF_FORCE_X2),0)
RUST_HUF_FEATURE := huf-force-decompress-x2
RUST_BUILD_CONFIG := huf-force-decompress-x2
RUST_HUF_MODE := huf-force-decompress-x2
endif
# The full-featured zstd program mirrors libzstd.mk's legacy file selection:
# ZSTD_LEGACY_SUPPORT=N compiles v0.N .. v0.7, so the Rust archive enables the
# matching per-version features. The archive directory encodes the level so
# builds for different legacy levels never share cached Rust outputs. The
# compress-only, decompress-only, and CLI archives further below are used
# solely by ZSTD_LEGACY_SUPPORT=0 program variants and stay legacy-free.
empty :=
space := $(empty) $(empty)
comma := ,
RUST_LEGACY_FEATURES :=
ifneq ($(ZSTD_LEGACY_SUPPORT), 0)
ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0)
RUST_LEGACY_FEATURES := $(addprefix legacy-v0,$(wordlist $(ZSTD_LEGACY_SUPPORT),7,1 2 3 4 5 6 7))
endif
endif
RUST_BUILD_CONFIG := $(RUST_HUF_MODE)-legacy$(ZSTD_LEGACY_SUPPORT)
RUST_TARGET_DIR := $(RUST_DIR)/target/$(RUST_BUILD_CONFIG)
RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a
RUST_TARGET_32 ?= i686-unknown-linux-gnu
@@ -75,6 +92,9 @@ RUST_CARGO_FLAGS += --features compression,decompression
ifneq ($(RUST_HUF_FEATURE),)
RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE)
endif
ifneq ($(RUST_LEGACY_FEATURES),)
RUST_CARGO_FLAGS += --features $(subst $(space),$(comma),$(strip $(RUST_LEGACY_FEATURES)))
endif
$(RUST_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS)
@@ -82,7 +102,7 @@ $(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_BUILD_CONFIG := cli-c1-d1-$(RUST_HUF_MODE)
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
@@ -96,7 +116,7 @@ $(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
$(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_BUILD_CONFIG := lib-c0-d1-$(RUST_HUF_MODE)
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 \
@@ -109,7 +129,7 @@ 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_BUILD_CONFIG := cli-c0-d1-$(RUST_HUF_MODE)
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 \
@@ -119,7 +139,7 @@ RUST_DECOMPRESS_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --releas
$(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_BUILD_CONFIG := lib-c1-d0-$(RUST_HUF_MODE)
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 \
@@ -129,7 +149,7 @@ RUST_COMPRESS_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
$(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_BUILD_CONFIG := cli-c1-d0-$(RUST_HUF_MODE)
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 \
+12
View File
@@ -12,6 +12,18 @@ compression = []
decompression = []
huf-force-decompress-x1 = []
huf-force-decompress-x2 = []
# Legacy-format decoders (zstd v0.1 .. v0.7). Never default features: the
# build systems map ZSTD_LEGACY_SUPPORT=N to the features for versions >= N,
# exactly mirroring which lib/legacy/zstd_v0N.c files the C build compiles.
# A feature whose version is not yet ported to Rust gates nothing; the
# original C file still provides that decoder.
legacy-v01 = []
legacy-v02 = []
legacy-v03 = []
legacy-v04 = []
legacy-v05 = []
legacy-v06 = []
legacy-v07 = []
[dependencies]
libc = "0.2"
+44 -3
View File
@@ -47,6 +47,10 @@ zstd ABI:
- `pool` implements the bounded worker pool used by multithreaded compression.
- Dictionary support
- `zstd_ddict` owns, loads, copies, and references decode dictionaries.
- Legacy decoding
- `legacy` hosts one frozen module per historical format; `legacy::zstd_v01`
ports the self-contained v0.1 decoder. Versions v0.2 through v0.7 are
still C.
- Block decompression
- `zstd_decompress_block` decodes literal and sequence sections, maintains
FSE/Huffman repeat state, and executes compressed-block sequences.
@@ -61,9 +65,46 @@ zstd ABI:
metadata, and streaming I/O.
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.
the legacy v0.2-v0.7 decoders, 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.
## Legacy decoding
Each `lib/legacy/zstd_v0N.c` file is a frozen snapshot of the entropy coders
and frame logic of one historical release. The Rust ports in `src/legacy/`
keep that property: every version owns its own frozen FSE/Huff0 and frame
logic, ported line by line, and must never reuse the modern entropy modules
or share code with other legacy versions. Outputs and error codes must be
byte-identical to the original C files. Their only shared dependency is the
`errors` module, matching the C files' `error_private.h` include.
Cargo features `legacy-v01` .. `legacy-v07` gate the per-version modules and
are never default features. The build systems derive the feature list from
the C configuration:
- `lib/Makefile` and `programs/Makefile` map `ZSTD_LEGACY_SUPPORT=N` to the
features for versions >= N (0 disables legacy), matching the
`ZSTD_LEGACY_FILES` selection in `lib/libzstd.mk`.
- `tests/Makefile` always enables all seven features because the test
objects compile every `lib/legacy/*.c` file regardless of dispatch level.
- `build/meson` maps `legacy_level` like the makefiles; `build/cmake`
enables all seven whenever `ZSTD_LEGACY_SUPPORT` is on because it always
compiles all seven C files.
Every build system also encodes the legacy selection in the Rust target
directory name (for example `c1-d1-default-legacy5`), for the same reason the
HUF mode is encoded there: a cached archive built for one configuration must
never be linked into a build that expects another.
A feature whose version has not been ported yet gates nothing; the original
C file still provides that decoder, so mixed C/Rust legacy levels link
cleanly. Porting a version means adding `src/legacy/zstd_v0N.rs`, registering
it in `src/legacy/mod.rs` behind its feature, and reducing
`lib/legacy/zstd_v0N.c` to a declaration-only shim. For v0.1 the streaming
`ZSTDv01_Dctx` state lives entirely in Rust: C code only ever holds an opaque
pointer, so the C-side struct definition is gone.
## Compatibility boundary
+40
View File
@@ -0,0 +1,40 @@
//! Frozen decoders for the legacy zstd formats (v0.1 through v0.7).
//!
//! Frozen-decoder policy
//! =====================
//!
//! Each `lib/legacy/zstd_v0N.c` translation unit is a self-contained snapshot
//! of the entropy coders and frame logic of that historical release. The
//! Rust ports mirror that property:
//!
//! - Every version keeps its own frozen FSE/Huff0 and frame logic. The
//! modern `fse_decompress`, `huf_decompress`, `bitstream`, or `mem`
//! modules must NOT be reused here, and legacy versions must not share
//! code with each other, even where functions look identical. The legacy
//! formats are frozen; the modern modules keep evolving.
//! - Ports are line-by-line translations of the corresponding C file: same
//! table layouts, same arithmetic, same error codes. Outputs must be
//! byte-identical to the C implementation, including error behavior.
//! - The only shared dependency is `crate::errors`, because the C files
//! include `error_private.h` for the public `ZSTD_ErrorCode` values.
//!
//! Registration
//! ============
//!
//! Cargo features `legacy-v01` .. `legacy-v07` are all declared in
//! `Cargo.toml`. The build systems always pass the feature list derived
//! from the C configuration (`ZSTD_LEGACY_SUPPORT=N` enables versions N
//! and newer), so a feature may be enabled before its port exists. A version
//! without a Rust module simply stays implemented by its C file.
//!
//! To port version `v0N`: add `zstd_v0N.rs` next to this file, reduce
//! `lib/legacy/zstd_v0N.c` to a declaration-only shim, and register the
//! module here with exactly one line:
//!
//! ```text
//! #[cfg(feature = "legacy-v0N")]
//! pub mod zstd_v0N;
//! ```
#[cfg(feature = "legacy-v01")]
pub mod zstd_v01;
+2354
View File
@@ -0,0 +1,2354 @@
#![allow(non_snake_case)]
//! Frozen decoder for the zstd v0.1 format.
//!
//! This is a line-by-line port of `lib/legacy/zstd_v01.c`: the same table
//! layouts, the same arithmetic, and the same error codes. The C file is a
//! self-contained snapshot of the v0.1-era FSE and Huff0 coders, so this
//! module deliberately reimplements them instead of reusing the modern
//! `fse_decompress`/`huf_decompress` modules (see `legacy/mod.rs` for the
//! frozen-decoder policy). The only shared dependency is `crate::errors`,
//! mirroring the C file's `error_private.h` include.
//!
//! The streaming `ZSTDv01_Dctx` state lives entirely in Rust; C callers only
//! ever hold an opaque pointer to it. It is allocated with `libc::malloc`
//! and released with `libc::free`, exactly like the original C context.
use crate::errors::{ERR_isError, ZstdErrorCode, ERROR};
use std::os::raw::{c_uint, c_void};
use std::ptr;
/* ******************************************
* Error management (frozen v0.1 FSE codes)
********************************************/
/* FSE_LIST_ERRORS in zstd_v01.c; values are returned as `(size_t)-code`. */
const FSE_ERROR_GENERIC: usize = 1;
const FSE_ERROR_TABLELOG_TOO_LARGE: usize = 2;
const FSE_ERROR_MAX_SYMBOL_VALUE_TOO_LARGE: usize = 3;
const FSE_ERROR_MAX_SYMBOL_VALUE_TOO_SMALL: usize = 4;
const FSE_ERROR_DST_SIZE_TOO_SMALL: usize = 5;
const FSE_ERROR_SRC_SIZE_WRONG: usize = 6;
const FSE_ERROR_CORRUPTION_DETECTED: usize = 7;
const FSE_ERROR_MAX_CODE: usize = 8;
#[inline]
fn fse_error(code: usize) -> usize {
code.wrapping_neg()
}
#[inline]
fn fse_is_error(code: usize) -> bool {
code > fse_error(FSE_ERROR_MAX_CODE)
}
/* ******************************************
* Tuning parameters (frozen)
********************************************/
const FSE_MAX_MEMORY_USAGE: u32 = 14;
const FSE_MAX_SYMBOL_VALUE: u32 = 255;
const FSE_MAX_TABLELOG: u32 = FSE_MAX_MEMORY_USAGE - 2;
const FSE_MIN_TABLELOG: u32 = 5;
const FSE_TABLELOG_ABSOLUTE_MAX: u32 = 15;
const HUF_MAX_SYMBOL_VALUE: u32 = 255;
const HUF_MAX_TABLELOG: u32 = 12;
const HUF_ABSOLUTEMAX_TABLELOG: u32 = 16;
const IS_32BITS: bool = std::mem::size_of::<usize>() == 4;
const USIZE_BITS: u32 = usize::BITS;
/* ******************************************
* Memory I/O (FSE_read* / ZSTD_read* helpers)
********************************************/
#[inline]
unsafe fn fse_read_le16(mem_ptr: *const u8) -> u16 {
u16::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 2]))
}
#[inline]
unsafe fn fse_read_le32(mem_ptr: *const u8) -> u32 {
u32::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 4]))
}
#[inline]
unsafe fn fse_read_le64(mem_ptr: *const u8) -> u64 {
u64::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 8]))
}
#[inline]
unsafe fn fse_read_lest(mem_ptr: *const u8) -> usize {
if IS_32BITS {
fse_read_le32(mem_ptr) as usize
} else {
fse_read_le64(mem_ptr) as usize
}
}
/// `FSE_highbit32`; the caller guarantees `val != 0`, as in C.
#[inline]
fn fse_highbit32(val: u32) -> u32 {
val.leading_zeros() ^ 31
}
/* ******************************************
* FSE structures
********************************************/
#[repr(C)]
#[derive(Clone, Copy)]
struct FseDecode {
new_state: u16,
symbol: u8,
nb_bits: u8,
}
#[repr(C)]
struct FseDTableHeader {
table_log: u16,
fast_mode: u16,
}
struct FseDStream {
bit_container: usize,
bits_consumed: u32,
ptr: *const u8,
start: *const u8,
}
struct FseDState {
state: usize,
table: *const FseDecode,
}
const FSE_DSTREAM_UNFINISHED: u32 = 0;
const FSE_DSTREAM_END_OF_BUFFER: u32 = 1;
const FSE_DSTREAM_COMPLETED: u32 = 2;
const FSE_DSTREAM_TOO_FAR: u32 = 3;
#[inline]
fn fse_table_step(table_size: u32) -> u32 {
(table_size >> 1) + (table_size >> 3) + 3
}
/* An FSE_DTable is an opaque u32 array: one header word followed by
* `1 << tableLog` FseDecode entries, exactly as in C. */
unsafe fn fse_build_dtable(
dt: *mut u32,
normalized_counter: *const i16,
max_symbol_value: u32,
table_log: u32,
) -> usize {
let dtable_h = dt as *mut FseDTableHeader;
let table_decode = dt.add(1) as *mut FseDecode;
/* Sanity checks */
if max_symbol_value > FSE_MAX_SYMBOL_VALUE {
return fse_error(FSE_ERROR_MAX_SYMBOL_VALUE_TOO_LARGE);
}
if table_log > FSE_MAX_TABLELOG {
return fse_error(FSE_ERROR_TABLELOG_TOO_LARGE);
}
let table_size: u32 = 1 << table_log;
let table_mask = table_size - 1;
let step = fse_table_step(table_size);
let mut symbol_next = [0u16; (FSE_MAX_SYMBOL_VALUE + 1) as usize];
let mut position: u32 = 0;
let mut high_threshold = table_size - 1;
let large_limit = (1i32 << (table_log - 1)) as i16;
let mut no_large: u32 = 1;
/* Init, lay down lowprob symbols */
(*dtable_h).table_log = table_log as u16;
for s in 0..=max_symbol_value {
let count = *normalized_counter.add(s as usize);
if count == -1 {
(*table_decode.add(high_threshold as usize)).symbol = s as u8;
high_threshold = high_threshold.wrapping_sub(1);
symbol_next[s as usize] = 1;
} else {
if count >= large_limit {
no_large = 0;
}
symbol_next[s as usize] = count as u16;
}
}
/* Spread symbols */
for s in 0..=max_symbol_value {
let count = *normalized_counter.add(s as usize);
let mut i = 0i32;
while i < count as i32 {
(*table_decode.add(position as usize)).symbol = s as u8;
position = (position + step) & table_mask;
while position > high_threshold {
position = (position + step) & table_mask; /* lowprob area */
}
i += 1;
}
}
if position != 0 {
/* position must reach all cells once, otherwise normalizedCounter is incorrect */
return fse_error(FSE_ERROR_GENERIC);
}
/* Build Decoding table */
for i in 0..table_size as usize {
let symbol = (*table_decode.add(i)).symbol;
let next_state = symbol_next[symbol as usize];
symbol_next[symbol as usize] = next_state.wrapping_add(1);
let nb_bits = (table_log - fse_highbit32(next_state as u32)) as u8;
(*table_decode.add(i)).nb_bits = nb_bits;
(*table_decode.add(i)).new_state =
(((next_state as u32) << nb_bits).wrapping_sub(table_size)) as u16;
}
(*dtable_h).fast_mode = no_large as u16;
0
}
/* ******************************************
* FSE header bitstream (FSE_readNCount)
********************************************/
unsafe fn fse_read_ncount(
normalized_counter: *mut i16,
max_sv_ptr: &mut u32,
table_log_ptr: &mut u32,
header_buffer: *const u8,
hb_size: usize,
) -> usize {
let istart = header_buffer;
let iend_addr = (istart as usize).wrapping_add(hb_size);
let mut ip = istart;
let mut charnum: u32 = 0;
let mut previous0 = false;
if hb_size < 4 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let mut bit_stream = fse_read_le32(ip);
let mut nb_bits: i32 = ((bit_stream & 0xF) + FSE_MIN_TABLELOG) as i32; /* extract tableLog */
if nb_bits > FSE_TABLELOG_ABSOLUTE_MAX as i32 {
return fse_error(FSE_ERROR_TABLELOG_TOO_LARGE);
}
bit_stream >>= 4;
let mut bit_count: i32 = 4;
*table_log_ptr = nb_bits as u32;
let mut remaining: i32 = (1 << nb_bits) + 1;
let mut threshold: i32 = 1 << nb_bits;
nb_bits += 1;
while remaining > 1 && charnum <= *max_sv_ptr {
if previous0 {
let mut n0 = charnum;
while (bit_stream & 0xFFFF) == 0xFFFF {
n0 += 24;
if (ip as usize) < iend_addr.wrapping_sub(5) {
ip = ip.add(2);
bit_stream = fse_read_le32(ip).wrapping_shr(bit_count as u32);
} else {
bit_stream >>= 16;
bit_count += 16;
}
}
while (bit_stream & 3) == 3 {
n0 += 3;
bit_stream >>= 2;
bit_count += 2;
}
n0 += bit_stream & 3;
bit_count += 2;
if n0 > *max_sv_ptr {
return fse_error(FSE_ERROR_MAX_SYMBOL_VALUE_TOO_SMALL);
}
while charnum < n0 {
*normalized_counter.add(charnum as usize) = 0;
charnum += 1;
}
if (ip as usize) <= iend_addr.wrapping_sub(7)
|| (ip as usize).wrapping_add((bit_count >> 3) as usize)
<= iend_addr.wrapping_sub(4)
{
ip = ip.add((bit_count >> 3) as usize);
bit_count &= 7;
bit_stream = fse_read_le32(ip).wrapping_shr(bit_count as u32);
} else {
bit_stream >>= 2;
}
}
{
let max: i16 = ((2 * threshold - 1) - remaining) as i16;
let mut count: i16;
if (bit_stream & (threshold - 1) as u32) < max as i32 as u32 {
count = (bit_stream & (threshold - 1) as u32) as u16 as i16;
bit_count += nb_bits - 1;
} else {
count = (bit_stream & (2 * threshold - 1) as u32) as u16 as i16;
if count as i32 >= threshold {
count = ((count as i32) - (max as i32)) as i16;
}
bit_count += nb_bits;
}
count = count.wrapping_sub(1); /* extra accuracy */
remaining -= (count as i32).abs();
*normalized_counter.add(charnum as usize) = count;
charnum += 1;
previous0 = count == 0;
while remaining < threshold {
nb_bits -= 1;
threshold >>= 1;
}
if (ip as usize) <= iend_addr.wrapping_sub(7)
|| (ip as usize).wrapping_add((bit_count >> 3) as usize)
<= iend_addr.wrapping_sub(4)
{
ip = ip.add((bit_count >> 3) as usize);
bit_count &= 7;
} else {
bit_count -=
(8 * (iend_addr.wrapping_sub(4) as isize - ip as usize as isize)) as i32;
ip = (iend_addr - 4) as *const u8;
}
bit_stream = fse_read_le32(ip).wrapping_shr((bit_count & 31) as u32);
}
}
if remaining != 1 {
return fse_error(FSE_ERROR_GENERIC);
}
*max_sv_ptr = charnum - 1;
ip = ip.wrapping_offset(((bit_count + 7) >> 3) as isize);
if (ip as usize).wrapping_sub(istart as usize) > hb_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
(ip as usize).wrapping_sub(istart as usize)
}
/* ******************************************
* FSE decompression, byte symbols
********************************************/
unsafe fn fse_build_dtable_rle(dt: *mut u32, symbol_value: u8) -> usize {
let dtable_h = dt as *mut FseDTableHeader;
let cell = dt.add(1) as *mut FseDecode;
(*dtable_h).table_log = 0;
(*dtable_h).fast_mode = 0;
(*cell).new_state = 0;
(*cell).symbol = symbol_value;
(*cell).nb_bits = 0;
0
}
unsafe fn fse_build_dtable_raw(dt: *mut u32, nb_bits: u32) -> usize {
let dtable_h = dt as *mut FseDTableHeader;
let dinfo = dt.add(1) as *mut FseDecode;
/* Sanity checks */
if nb_bits < 1 {
return fse_error(FSE_ERROR_GENERIC); /* min size */
}
let table_size: u32 = 1 << nb_bits;
let table_mask = table_size - 1;
let max_symbol_value = table_mask;
(*dtable_h).table_log = nb_bits as u16;
(*dtable_h).fast_mode = 1;
for s in 0..=max_symbol_value {
let cell = dinfo.add(s as usize);
(*cell).new_state = 0;
(*cell).symbol = s as u8;
(*cell).nb_bits = nb_bits as u8;
}
0
}
unsafe fn fse_init_dstream(
bit_d: &mut FseDStream,
src_buffer: *const u8,
src_size: usize,
) -> usize {
let word = std::mem::size_of::<usize>();
if src_size < 1 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
if src_size >= word {
bit_d.start = src_buffer;
bit_d.ptr = src_buffer.add(src_size - word);
bit_d.bit_container = fse_read_lest(bit_d.ptr);
let contain32 = *src_buffer.add(src_size - 1) as u32;
if contain32 == 0 {
return fse_error(FSE_ERROR_GENERIC); /* stop bit not present */
}
bit_d.bits_consumed = 8 - fse_highbit32(contain32);
} else {
bit_d.start = src_buffer;
bit_d.ptr = bit_d.start;
bit_d.bit_container = *bit_d.start as usize;
/* C switch with fallthrough over srcSize 7..2 */
if src_size >= 7 {
bit_d.bit_container += (*src_buffer.add(6) as usize) << (USIZE_BITS as usize - 16);
}
if src_size >= 6 {
bit_d.bit_container += (*src_buffer.add(5) as usize) << (USIZE_BITS as usize - 24);
}
if src_size >= 5 {
bit_d.bit_container += (*src_buffer.add(4) as usize) << (USIZE_BITS as usize - 32);
}
if src_size >= 4 {
bit_d.bit_container += (*src_buffer.add(3) as usize) << 24;
}
if src_size >= 3 {
bit_d.bit_container += (*src_buffer.add(2) as usize) << 16;
}
if src_size >= 2 {
bit_d.bit_container += (*src_buffer.add(1) as usize) << 8;
}
let contain32 = *src_buffer.add(src_size - 1) as u32;
if contain32 == 0 {
return fse_error(FSE_ERROR_GENERIC); /* stop bit not present */
}
bit_d.bits_consumed = 8 - fse_highbit32(contain32);
bit_d.bits_consumed += ((word - src_size) * 8) as u32;
}
src_size
}
#[inline]
unsafe fn fse_look_bits(bit_d: &FseDStream, nb_bits: u32) -> usize {
let bit_mask = USIZE_BITS - 1;
((bit_d.bit_container << (bit_d.bits_consumed & bit_mask)) >> 1)
>> (bit_mask.wrapping_sub(nb_bits) & bit_mask)
}
#[inline]
unsafe fn fse_look_bits_fast(bit_d: &FseDStream, nb_bits: u32) -> usize {
/* only if nb_bits >= 1 */
let bit_mask = USIZE_BITS - 1;
(bit_d.bit_container << (bit_d.bits_consumed & bit_mask))
>> ((bit_mask + 1).wrapping_sub(nb_bits) & bit_mask)
}
#[inline]
fn fse_skip_bits(bit_d: &mut FseDStream, nb_bits: u32) {
bit_d.bits_consumed = bit_d.bits_consumed.wrapping_add(nb_bits);
}
#[inline]
unsafe fn fse_read_bits(bit_d: &mut FseDStream, nb_bits: u32) -> usize {
let value = fse_look_bits(bit_d, nb_bits);
fse_skip_bits(bit_d, nb_bits);
value
}
#[inline]
unsafe fn fse_read_bits_fast(bit_d: &mut FseDStream, nb_bits: u32) -> usize {
/* only if nb_bits >= 1 */
let value = fse_look_bits_fast(bit_d, nb_bits);
fse_skip_bits(bit_d, nb_bits);
value
}
unsafe fn fse_reload_dstream(bit_d: &mut FseDStream) -> u32 {
let word = std::mem::size_of::<usize>();
if bit_d.bits_consumed > (word * 8) as u32 {
/* should never happen */
return FSE_DSTREAM_TOO_FAR;
}
if (bit_d.ptr as usize) >= (bit_d.start as usize).wrapping_add(word) {
bit_d.ptr = bit_d.ptr.sub((bit_d.bits_consumed >> 3) as usize);
bit_d.bits_consumed &= 7;
bit_d.bit_container = fse_read_lest(bit_d.ptr);
return FSE_DSTREAM_UNFINISHED;
}
if bit_d.ptr == bit_d.start {
if bit_d.bits_consumed < (word * 8) as u32 {
return FSE_DSTREAM_END_OF_BUFFER;
}
return FSE_DSTREAM_COMPLETED;
}
{
let mut nb_bytes = bit_d.bits_consumed >> 3;
let mut result = FSE_DSTREAM_UNFINISHED;
if (bit_d.ptr as usize).wrapping_sub(nb_bytes as usize) < (bit_d.start as usize) {
nb_bytes = ((bit_d.ptr as usize) - (bit_d.start as usize)) as u32; /* ptr > start */
result = FSE_DSTREAM_END_OF_BUFFER;
}
bit_d.ptr = bit_d.ptr.sub(nb_bytes as usize);
bit_d.bits_consumed -= nb_bytes * 8;
bit_d.bit_container = fse_read_lest(bit_d.ptr); /* reminder : srcSize > sizeof(bitD) */
result
}
}
unsafe fn fse_init_dstate(d_state: &mut FseDState, bit_d: &mut FseDStream, dt: *const u32) {
let dtable_h = dt as *const FseDTableHeader;
d_state.state = fse_read_bits(bit_d, (*dtable_h).table_log as u32);
fse_reload_dstream(bit_d);
d_state.table = dt.add(1) as *const FseDecode;
}
unsafe fn fse_decode_symbol(d_state: &mut FseDState, bit_d: &mut FseDStream) -> u8 {
let d_info = *d_state.table.add(d_state.state);
let low_bits = fse_read_bits(bit_d, d_info.nb_bits as u32);
d_state.state = (d_info.new_state as usize).wrapping_add(low_bits);
d_info.symbol
}
unsafe fn fse_decode_symbol_fast(d_state: &mut FseDState, bit_d: &mut FseDStream) -> u8 {
let d_info = *d_state.table.add(d_state.state);
let low_bits = fse_read_bits_fast(bit_d, d_info.nb_bits as u32);
d_state.state = (d_info.new_state as usize).wrapping_add(low_bits);
d_info.symbol
}
#[inline]
fn fse_end_of_dstream(bit_d: &FseDStream) -> bool {
bit_d.ptr == bit_d.start && bit_d.bits_consumed == USIZE_BITS
}
#[inline]
fn fse_end_of_dstate(d_state: &FseDState) -> bool {
d_state.state == 0
}
unsafe fn fse_decompress_using_dtable_generic(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
dt: *const u32,
fast: bool,
) -> usize {
let ostart = dst;
let mut op = ostart;
let omax_addr = (op as usize).wrapping_add(max_dst_size);
let olimit_addr = omax_addr.wrapping_sub(3);
let mut bit_d = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut state1 = FseDState {
state: 0,
table: ptr::null(),
};
let mut state2 = FseDState {
state: 0,
table: ptr::null(),
};
/* Init */
let error_code = fse_init_dstream(&mut bit_d, c_src, c_src_size);
if fse_is_error(error_code) {
return error_code;
}
fse_init_dstate(&mut state1, &mut bit_d, dt);
fse_init_dstate(&mut state2, &mut bit_d, dt);
macro_rules! fse_getsymbol {
($state:expr) => {
if fast {
fse_decode_symbol_fast($state, &mut bit_d)
} else {
fse_decode_symbol($state, &mut bit_d)
}
};
}
/* Constant conditions from the C source; on 64-bit both are false, on
* 32-bit only the *4 variant reloads. */
const RELOAD_2: bool = FSE_MAX_TABLELOG * 2 + 7 > USIZE_BITS;
const RELOAD_4: bool = FSE_MAX_TABLELOG * 4 + 7 > USIZE_BITS;
/* 4 symbols per loop */
while fse_reload_dstream(&mut bit_d) == FSE_DSTREAM_UNFINISHED && (op as usize) < olimit_addr {
*op = fse_getsymbol!(&mut state1);
if RELOAD_2 {
/* This test must be static */
fse_reload_dstream(&mut bit_d);
}
*op.add(1) = fse_getsymbol!(&mut state2);
if RELOAD_4 {
/* This test must be static */
if fse_reload_dstream(&mut bit_d) > FSE_DSTREAM_UNFINISHED {
op = op.add(2);
break;
}
}
*op.add(2) = fse_getsymbol!(&mut state1);
if RELOAD_2 {
/* This test must be static */
fse_reload_dstream(&mut bit_d);
}
*op.add(3) = fse_getsymbol!(&mut state2);
op = op.add(4);
}
/* tail */
loop {
if fse_reload_dstream(&mut bit_d) > FSE_DSTREAM_COMPLETED
|| (op as usize) == omax_addr
|| (fse_end_of_dstream(&bit_d) && (fast || fse_end_of_dstate(&state1)))
{
break;
}
*op = fse_getsymbol!(&mut state1);
op = op.add(1);
if fse_reload_dstream(&mut bit_d) > FSE_DSTREAM_COMPLETED
|| (op as usize) == omax_addr
|| (fse_end_of_dstream(&bit_d) && (fast || fse_end_of_dstate(&state2)))
{
break;
}
*op = fse_getsymbol!(&mut state2);
op = op.add(1);
}
/* end ? */
if fse_end_of_dstream(&bit_d) && fse_end_of_dstate(&state1) && fse_end_of_dstate(&state2) {
return (op as usize) - (ostart as usize);
}
if (op as usize) == omax_addr {
/* dst buffer is full, but cSrc unfinished */
return fse_error(FSE_ERROR_DST_SIZE_TOO_SMALL);
}
fse_error(FSE_ERROR_CORRUPTION_DETECTED)
}
unsafe fn fse_decompress_using_dtable(
dst: *mut u8,
original_size: usize,
c_src: *const u8,
c_src_size: usize,
dt: *const u32,
) -> usize {
let fast_mode = (*(dt as *const FseDTableHeader)).fast_mode;
/* select fast mode (static) */
if fast_mode != 0 {
return fse_decompress_using_dtable_generic(
dst,
original_size,
c_src,
c_src_size,
dt,
true,
);
}
fse_decompress_using_dtable_generic(dst, original_size, c_src, c_src_size, dt, false)
}
unsafe fn fse_decompress(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
) -> usize {
let istart = c_src;
let mut ip = istart;
let mut counting = [0i16; (FSE_MAX_SYMBOL_VALUE + 1) as usize];
let mut dt = [0u32; 1 + (1 << FSE_MAX_TABLELOG)]; /* DTable_max_t */
let mut table_log: u32 = 0;
let mut max_symbol_value: u32 = FSE_MAX_SYMBOL_VALUE;
let mut remaining_size = c_src_size;
if c_src_size < 2 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG); /* too small input size */
}
/* normal FSE decoding mode */
let error_code = fse_read_ncount(
counting.as_mut_ptr(),
&mut max_symbol_value,
&mut table_log,
istart,
c_src_size,
);
if fse_is_error(error_code) {
return error_code;
}
if error_code >= c_src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG); /* too small input size */
}
ip = ip.add(error_code);
remaining_size -= error_code;
let error_code = fse_build_dtable(
dt.as_mut_ptr(),
counting.as_ptr(),
max_symbol_value,
table_log,
);
if fse_is_error(error_code) {
return error_code;
}
/* always return, even if it is an error code */
fse_decompress_using_dtable(dst, max_dst_size, ip, remaining_size, dt.as_ptr())
}
/* ******************************************
* Huff0 : Huffman block decompression
********************************************/
#[repr(C)]
#[derive(Clone, Copy)]
struct HufDElt {
byte: u8,
nb_bits: u8,
}
/* Loop shapes and arithmetic below intentionally mirror the frozen C. */
#[allow(clippy::needless_range_loop, clippy::manual_div_ceil)]
unsafe fn huf_read_dtable(dtable: *mut u16, src: *const u8, src_size: usize) -> usize {
let mut huff_weight = [0u8; (HUF_MAX_SYMBOL_VALUE + 1) as usize];
let mut rank_val = [0u32; (HUF_ABSOLUTEMAX_TABLELOG + 1) as usize];
let ip = src;
let dt = dtable.add(1) as *mut HufDElt;
if src_size == 0 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let mut i_size = *ip as usize;
let o_size: usize;
if i_size >= 128 {
/* special header */
if i_size >= 242 {
/* RLE */
const L: [usize; 14] = [1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128];
o_size = L[i_size - 242];
huff_weight = [1u8; (HUF_MAX_SYMBOL_VALUE + 1) as usize];
i_size = 0;
} else {
/* Incompressible */
o_size = i_size - 127;
i_size = (o_size + 1) / 2;
if i_size + 1 > src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let ip = ip.add(1);
let mut n = 0usize;
while n < o_size {
huff_weight[n] = *ip.add(n / 2) >> 4;
huff_weight[n + 1] = *ip.add(n / 2) & 15;
n += 2;
}
}
} else {
/* header compressed with FSE (normal case) */
if i_size + 1 > src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
/* max 255 values decoded, last one is implied */
let decoded = fse_decompress(
huff_weight.as_mut_ptr(),
HUF_MAX_SYMBOL_VALUE as usize,
ip.add(1),
i_size,
);
if fse_is_error(decoded) {
return decoded;
}
o_size = decoded;
}
/* collect weight stats */
let mut weight_total: u32 = 0;
for n in 0..o_size {
if huff_weight[n] >= HUF_ABSOLUTEMAX_TABLELOG as u8 {
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
rank_val[huff_weight[n] as usize] += 1;
weight_total += (1u32 << huff_weight[n]) >> 1;
}
if weight_total == 0 {
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
/* get last non-null symbol weight (implied, total must be 2^n) */
let max_bits = fse_highbit32(weight_total) + 1;
if max_bits > *dtable as u32 {
return fse_error(FSE_ERROR_TABLELOG_TOO_LARGE); /* DTable is too small */
}
*dtable = max_bits as u16;
{
let total = 1u32 << max_bits;
let rest = total - weight_total;
let verif = 1u32 << fse_highbit32(rest);
let last_weight = fse_highbit32(rest) + 1;
if verif != rest {
return fse_error(FSE_ERROR_CORRUPTION_DETECTED); /* last value must be a clean power of 2 */
}
huff_weight[o_size] = last_weight as u8;
rank_val[last_weight as usize] += 1;
}
/* check tree construction validity */
if rank_val[1] < 2 || (rank_val[1] & 1) != 0 {
/* by construction : at least 2 elts of rank 1, must be even */
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
/* Prepare ranks */
let mut next_rank_start: u32 = 0;
for n in 1..=(max_bits as usize) {
let current = next_rank_start;
next_rank_start += rank_val[n] << (n - 1);
rank_val[n] = current;
}
/* fill DTable */
for n in 0..=o_size {
let w = huff_weight[n] as usize;
let length = (1u32 << w) >> 1;
let d = HufDElt {
byte: n as u8,
nb_bits: (max_bits + 1 - w as u32) as u8,
};
for i in rank_val[w]..(rank_val[w] + length) {
*dt.add(i as usize) = d;
}
rank_val[w] += length;
}
i_size + 1
}
unsafe fn huf_decode_symbol(d_stream: &mut FseDStream, dt: *const HufDElt, dt_log: u32) -> u8 {
let val = fse_look_bits_fast(d_stream, dt_log); /* note : dtLog >= 1 */
let entry = *dt.add(val);
fse_skip_bits(d_stream, entry.nb_bits as u32);
entry.byte
}
/* The C decode macros: SYMBOL_1 reloads on 32-bit only when
* HUF_MAX_TABLELOG > 12 (never here); SYMBOL_2 reloads on 32-bit. */
#[inline]
unsafe fn huf_decode_symbol_1(
op: *mut u8,
d_stream: &mut FseDStream,
dt: *const HufDElt,
dt_log: u32,
) {
*op = huf_decode_symbol(d_stream, dt, dt_log);
if IS_32BITS && HUF_MAX_TABLELOG > 12 {
fse_reload_dstream(d_stream);
}
}
#[inline]
unsafe fn huf_decode_symbol_2(
op: *mut u8,
d_stream: &mut FseDStream,
dt: *const HufDElt,
dt_log: u32,
) {
*op = huf_decode_symbol(d_stream, dt, dt_log);
if IS_32BITS {
fse_reload_dstream(d_stream);
}
}
unsafe fn huf_decompress_using_dtable(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
dtable: *const u16,
) -> usize {
if c_src_size < 6 {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let ostart = dst;
let mut op = ostart;
let omax_addr = (op as usize).wrapping_add(max_dst_size);
let olimit_addr = if max_dst_size < 15 {
op as usize
} else {
omax_addr - 15
};
let dt = dtable.add(1) as *const HufDElt;
let dt_log = *dtable as u32;
/* Init */
let length1 = fse_read_le16(c_src) as usize;
let length2 = fse_read_le16(c_src.add(2)) as usize;
let length3 = fse_read_le16(c_src.add(4)) as usize;
let length4 = c_src_size
.wrapping_sub(6)
.wrapping_sub(length1)
.wrapping_sub(length2)
.wrapping_sub(length3); /* check coherency !! */
let start1 = c_src.add(6);
let start2 = start1.wrapping_add(length1);
let start3 = start2.wrapping_add(length2);
let start4 = start3.wrapping_add(length3);
if length1 + length2 + length3 + 6 >= c_src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
let mut bit_d1 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut bit_d2 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut bit_d3 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let mut bit_d4 = FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
};
let error_code = fse_init_dstream(&mut bit_d1, start1, length1);
if fse_is_error(error_code) {
return error_code;
}
let error_code = fse_init_dstream(&mut bit_d2, start2, length2);
if fse_is_error(error_code) {
return error_code;
}
let error_code = fse_init_dstream(&mut bit_d3, start3, length3);
if fse_is_error(error_code) {
return error_code;
}
let error_code = fse_init_dstream(&mut bit_d4, start4, length4);
if fse_is_error(error_code) {
return error_code;
}
let mut reload_status = fse_reload_dstream(&mut bit_d2);
/* 16 symbols per loop; D2-3-4 are supposed to be synchronized and finish together */
while reload_status < FSE_DSTREAM_COMPLETED && (op as usize) < olimit_addr {
huf_decode_symbol_1(op, &mut bit_d1, dt, dt_log);
huf_decode_symbol_1(op.add(1), &mut bit_d2, dt, dt_log);
huf_decode_symbol_1(op.add(2), &mut bit_d3, dt, dt_log);
huf_decode_symbol_1(op.add(3), &mut bit_d4, dt, dt_log);
huf_decode_symbol_2(op.add(4), &mut bit_d1, dt, dt_log);
huf_decode_symbol_2(op.add(5), &mut bit_d2, dt, dt_log);
huf_decode_symbol_2(op.add(6), &mut bit_d3, dt, dt_log);
huf_decode_symbol_2(op.add(7), &mut bit_d4, dt, dt_log);
huf_decode_symbol_1(op.add(8), &mut bit_d1, dt, dt_log);
huf_decode_symbol_1(op.add(9), &mut bit_d2, dt, dt_log);
huf_decode_symbol_1(op.add(10), &mut bit_d3, dt, dt_log);
huf_decode_symbol_1(op.add(11), &mut bit_d4, dt, dt_log);
*op.add(12) = huf_decode_symbol(&mut bit_d1, dt, dt_log);
*op.add(13) = huf_decode_symbol(&mut bit_d2, dt, dt_log);
*op.add(14) = huf_decode_symbol(&mut bit_d3, dt, dt_log);
*op.add(15) = huf_decode_symbol(&mut bit_d4, dt, dt_log);
op = op.add(16);
reload_status = fse_reload_dstream(&mut bit_d2)
| fse_reload_dstream(&mut bit_d3)
| fse_reload_dstream(&mut bit_d4);
fse_reload_dstream(&mut bit_d1);
}
if reload_status != FSE_DSTREAM_COMPLETED {
/* not complete : some bitStream might be FSE_DStream_unfinished */
return fse_error(FSE_ERROR_CORRUPTION_DETECTED);
}
/* tail */
{
let mut bit_tail = FseDStream {
bit_container: bit_d1.bit_container, /* required in case of FSE_DStream_endOfBuffer */
bits_consumed: bit_d1.bits_consumed,
ptr: bit_d1.ptr,
start: start1,
};
while fse_reload_dstream(&mut bit_tail) < FSE_DSTREAM_COMPLETED && (op as usize) < omax_addr
{
*op = huf_decode_symbol(&mut bit_tail, dt, dt_log);
op = op.add(1);
}
if fse_end_of_dstream(&bit_tail) {
return (op as usize) - (ostart as usize);
}
}
if (op as usize) == omax_addr {
return fse_error(FSE_ERROR_DST_SIZE_TOO_SMALL); /* dst buffer is full, but cSrc unfinished */
}
fse_error(FSE_ERROR_CORRUPTION_DETECTED)
}
unsafe fn huf_decompress(
dst: *mut u8,
max_dst_size: usize,
c_src: *const u8,
c_src_size: usize,
) -> usize {
/* HUF_CREATE_STATIC_DTABLE(DTable, HUF_MAX_TABLELOG) */
let mut dtable = [0u16; 1 + (1 << HUF_MAX_TABLELOG)];
dtable[0] = HUF_MAX_TABLELOG as u16;
let mut ip = c_src;
let error_code = huf_read_dtable(dtable.as_mut_ptr(), c_src, c_src_size);
if fse_is_error(error_code) {
return error_code;
}
if error_code >= c_src_size {
return fse_error(FSE_ERROR_SRC_SIZE_WRONG);
}
ip = ip.add(error_code);
huf_decompress_using_dtable(
dst,
max_dst_size,
ip,
c_src_size - error_code,
dtable.as_ptr(),
)
}
/* ******************************************
* zstd v0.1 frame decoding
********************************************/
const ZSTD_MAGIC_NUMBER: u32 = 0xFD2FB51E; /* 3rd version : seqNb header */
const KB: usize = 1 << 10;
const BLOCKSIZE: usize = 128 * KB; /* define, for static allocation */
const MINMATCH: usize = 4;
const MLBITS: u32 = 7;
const LLBITS: u32 = 6;
const OFFBITS: u32 = 5;
const MAX_ML: u32 = (1 << MLBITS) - 1;
const MAX_LL: u32 = (1 << LLBITS) - 1;
const MAX_OFF: u32 = (1 << OFFBITS) - 1;
#[allow(dead_code)] /* part of the frozen v0.1 constant set; used only by the compressor */
const LIT_FSE_LOG: u32 = 11;
const ML_FSE_LOG: u32 = 10;
const LL_FSE_LOG: u32 = 10;
const OFF_FSE_LOG: u32 = 9;
const ZSTD_CONTENTSIZE_ERROR: u64 = 0u64.wrapping_sub(2);
const ZSTD_BLOCK_HEADER_SIZE: usize = 3;
const ZSTD_FRAME_HEADER_SIZE: usize = 4;
/* FSE_DTABLE_SIZE_U32(maxTableLog) == 1 + (1 << maxTableLog) */
const LL_DTABLE_SIZE_U32: usize = 1 + (1 << LL_FSE_LOG as usize);
const OFF_DTABLE_SIZE_U32: usize = 1 + (1 << OFF_FSE_LOG as usize);
const ML_DTABLE_SIZE_U32: usize = 1 + (1 << ML_FSE_LOG as usize);
#[inline]
unsafe fn zstd_copy4(dst: *mut u8, src: *const u8) {
ptr::copy_nonoverlapping(src, dst, 4);
}
#[inline]
unsafe fn zstd_copy8(dst: *mut u8, src: *const u8) {
ptr::copy_nonoverlapping(src, dst, 8);
}
unsafe fn zstd_wildcopy(dst: *mut u8, src: *const u8, length: isize) {
let mut ip = src;
let mut op = dst;
let oend_addr = (op as usize).wrapping_add(length as usize);
while (op as usize) < oend_addr {
zstd_copy8(op, ip);
op = op.add(8);
ip = ip.add(8);
}
}
#[inline]
unsafe fn zstd_read_le16(mem_ptr: *const u8) -> u16 {
u16::from_le_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 2]))
}
#[inline]
unsafe fn zstd_read_le24(mem_ptr: *const u8) -> u32 {
zstd_read_le16(mem_ptr) as u32 + ((*mem_ptr.add(2) as u32) << 16)
}
#[inline]
unsafe fn zstd_read_be32(mem_ptr: *const u8) -> u32 {
u32::from_be_bytes(ptr::read_unaligned(mem_ptr as *const [u8; 4]))
}
/* blockType_t */
const BT_COMPRESSED: u32 = 0;
const BT_RAW: u32 = 1;
const BT_RLE: u32 = 2;
const BT_END: u32 = 3;
struct BlockProperties {
block_type: u32,
orig_size: u32,
}
/// The v0.1 streaming decompression context (`dctx_t` in C). The struct is
/// opaque to C: `zstd_v01.h` only forward-declares it, so the definition now
/// lives here.
#[repr(C)]
pub struct ZSTDv01_Dctx {
ll_table: [u32; LL_DTABLE_SIZE_U32],
off_table: [u32; OFF_DTABLE_SIZE_U32],
ml_table: [u32; ML_DTABLE_SIZE_U32],
previous_dst_end: *const u8,
base: *const u8,
expected: usize,
b_type: u32,
phase: u32,
}
unsafe fn zstdv01_getc_block_size(
src: *const u8,
src_size: usize,
bp_ptr: &mut BlockProperties,
) -> usize {
if src_size < 3 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let header_flags = *src;
let c_size =
(*src.add(2) as u32) + ((*src.add(1) as u32) << 8) + (((header_flags & 7) as u32) << 16);
bp_ptr.block_type = (header_flags >> 6) as u32;
bp_ptr.orig_size = if bp_ptr.block_type == BT_RLE {
c_size
} else {
0
};
if bp_ptr.block_type == BT_END {
return 0;
}
if bp_ptr.block_type == BT_RLE {
return 1;
}
c_size as usize
}
unsafe fn zstd_copy_uncompressed_block(
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
if src_size > max_dst_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if src_size > 0 {
ptr::copy_nonoverlapping(src, dst, src_size);
}
src_size
}
unsafe fn zstd_decompress_literals(
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
let oend = dst.add(max_dst_size);
let ip = src;
/* check : minimum 2, for litSize, +1, for content */
if src_size <= 3 {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
let mut lit_size = (*ip.add(1) as usize) + ((*ip as usize) << 8);
lit_size += (((*ip.offset(-3) as usize) >> 3) & 7) << 16; /* mmmmh.... */
if lit_size > max_dst_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
let op = oend.sub(lit_size);
let error_code = huf_decompress(op, lit_size, ip.add(2), src_size - 2);
if fse_is_error(error_code) {
return ERROR(ZstdErrorCode::Generic);
}
lit_size
}
unsafe fn zstdv01_decode_literals_block(
dst: *mut u8,
max_dst_size: usize,
lit_start: &mut *const u8,
lit_size: &mut usize,
src: *const u8,
src_size: usize,
) -> usize {
let istart = src;
let mut ip = istart;
let ostart = dst;
let oend = ostart.add(max_dst_size);
let mut litbp = BlockProperties {
block_type: 0,
orig_size: 0,
};
let litc_size = zstdv01_getc_block_size(src, src_size, &mut litbp);
if ERR_isError(litc_size) {
return litc_size;
}
if litc_size > src_size - ZSTD_BLOCK_HEADER_SIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
ip = ip.add(ZSTD_BLOCK_HEADER_SIZE);
match litbp.block_type {
BT_RAW => {
*lit_start = ip;
ip = ip.add(litc_size);
*lit_size = litc_size;
}
BT_RLE => {
let rle_size = litbp.orig_size as usize;
if rle_size > max_dst_size {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if src_size == 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if rle_size > 0 {
ptr::write_bytes(oend.sub(rle_size), *ip, rle_size);
}
*lit_start = oend.sub(rle_size);
*lit_size = rle_size;
ip = ip.add(1);
}
BT_COMPRESSED => {
let decoded_lit_size = zstd_decompress_literals(dst, max_dst_size, ip, litc_size);
if ERR_isError(decoded_lit_size) {
return decoded_lit_size;
}
*lit_start = oend.sub(decoded_lit_size);
*lit_size = decoded_lit_size;
ip = ip.add(litc_size);
}
_ => {
/* bt_end and impossible values */
return ERROR(ZstdErrorCode::Generic);
}
}
(ip as usize) - (istart as usize)
}
#[allow(clippy::too_many_arguments)]
unsafe fn zstdv01_decode_seq_headers(
nb_seq: &mut i32,
dumps_ptr: &mut *const u8,
dumps_length_ptr: &mut usize,
dtable_ll: *mut u32,
dtable_ml: *mut u32,
dtable_offb: *mut u32,
src: *const u8,
src_size: usize,
) -> usize {
let istart = src;
let mut ip = istart;
let iend_addr = (istart as usize).wrapping_add(src_size);
/* check */
if src_size < 5 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
/* SeqHead */
*nb_seq = zstd_read_le16(ip) as i32;
ip = ip.add(2);
let ll_type = (*ip >> 6) as u32;
let off_type = ((*ip >> 4) & 3) as u32;
let ml_type = ((*ip >> 2) & 3) as u32;
let dumps_length: usize;
if (*ip & 2) != 0 {
dumps_length = (*ip.add(2) as usize) + ((*ip.add(1) as usize) << 8);
ip = ip.add(3);
} else {
dumps_length = (*ip.add(1) as usize) + (((*ip as usize) & 1) << 8);
ip = ip.add(2);
}
*dumps_ptr = ip;
ip = ip.wrapping_add(dumps_length);
*dumps_length_ptr = dumps_length;
/* check */
if (ip as usize) > iend_addr.wrapping_sub(3) {
/* min : all 3 are "raw", hence no header, but at least xxLog bits per type */
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
/* sequences */
{
let mut norm = [0i16; (MAX_ML + 1) as usize]; /* assumption : MaxML >= MaxLL and MaxOff */
/* Build DTables */
match ll_type {
BT_RLE => {
fse_build_dtable_rle(dtable_ll, *ip);
ip = ip.add(1);
}
BT_RAW => {
fse_build_dtable_raw(dtable_ll, LLBITS);
}
_ => {
let mut max = MAX_LL;
let mut ll_log = 0u32;
let header_size = fse_read_ncount(
norm.as_mut_ptr(),
&mut max,
&mut ll_log,
ip,
iend_addr - ip as usize,
);
if fse_is_error(header_size) {
return ERROR(ZstdErrorCode::Generic);
}
if ll_log > LL_FSE_LOG {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
ip = ip.add(header_size);
fse_build_dtable(dtable_ll, norm.as_ptr(), max, ll_log);
}
}
match off_type {
BT_RLE => {
if (ip as usize) > iend_addr.wrapping_sub(2) {
/* min : "raw", hence no header, but at least xxLog bits */
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
fse_build_dtable_rle(dtable_offb, *ip);
ip = ip.add(1);
}
BT_RAW => {
fse_build_dtable_raw(dtable_offb, OFFBITS);
}
_ => {
let mut max = MAX_OFF;
let mut off_log = 0u32;
let header_size = fse_read_ncount(
norm.as_mut_ptr(),
&mut max,
&mut off_log,
ip,
iend_addr - ip as usize,
);
if fse_is_error(header_size) {
return ERROR(ZstdErrorCode::Generic);
}
if off_log > OFF_FSE_LOG {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
ip = ip.add(header_size);
fse_build_dtable(dtable_offb, norm.as_ptr(), max, off_log);
}
}
match ml_type {
BT_RLE => {
if (ip as usize) > iend_addr.wrapping_sub(2) {
/* min : "raw", hence no header, but at least xxLog bits */
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
fse_build_dtable_rle(dtable_ml, *ip);
ip = ip.add(1);
}
BT_RAW => {
fse_build_dtable_raw(dtable_ml, MLBITS);
}
_ => {
let mut max = MAX_ML;
let mut ml_log = 0u32;
let header_size = fse_read_ncount(
norm.as_mut_ptr(),
&mut max,
&mut ml_log,
ip,
iend_addr - ip as usize,
);
if fse_is_error(header_size) {
return ERROR(ZstdErrorCode::Generic);
}
if ml_log > ML_FSE_LOG {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
ip = ip.add(header_size);
fse_build_dtable(dtable_ml, norm.as_ptr(), max, ml_log);
}
}
}
(ip as usize) - (istart as usize)
}
#[derive(Clone, Copy)]
struct Seq {
lit_length: usize,
offset: usize,
match_length: usize,
}
struct SeqState {
d_stream: FseDStream,
state_ll: FseDState,
state_offb: FseDState,
state_ml: FseDState,
prev_offset: usize,
dumps: *const u8,
dumps_end: *const u8,
}
unsafe fn zstd_decode_sequence(seq: &mut Seq, seq_state: &mut SeqState) {
let mut dumps = seq_state.dumps;
let de = seq_state.dumps_end;
/* Literal length */
let mut lit_length =
fse_decode_symbol(&mut seq_state.state_ll, &mut seq_state.d_stream) as usize;
let prev_offset = if lit_length != 0 {
seq.offset
} else {
seq_state.prev_offset
};
seq_state.prev_offset = seq.offset;
if lit_length == MAX_LL as usize {
let add = if (dumps as usize) < (de as usize) {
let v = *dumps as u32;
dumps = dumps.add(1);
v
} else {
0
};
if add < 255 {
lit_length += add as usize;
} else if (dumps as usize) <= (de as usize).wrapping_sub(3) {
lit_length = zstd_read_le24(dumps) as usize;
dumps = dumps.add(3);
}
}
/* Offset */
let mut offset: usize;
{
let offset_code =
fse_decode_symbol(&mut seq_state.state_offb, &mut seq_state.d_stream) as u32;
if IS_32BITS {
fse_reload_dstream(&mut seq_state.d_stream);
}
let mut nb_bits = offset_code.wrapping_sub(1);
if offset_code == 0 {
nb_bits = 0; /* cmove */
}
offset = (1usize << (nb_bits & (USIZE_BITS - 1)))
.wrapping_add(fse_read_bits(&mut seq_state.d_stream, nb_bits));
if IS_32BITS {
fse_reload_dstream(&mut seq_state.d_stream);
}
if offset_code == 0 {
offset = prev_offset;
}
}
/* MatchLength */
let mut match_length =
fse_decode_symbol(&mut seq_state.state_ml, &mut seq_state.d_stream) as usize;
if match_length == MAX_ML as usize {
let add = if (dumps as usize) < (de as usize) {
let v = *dumps as u32;
dumps = dumps.add(1);
v
} else {
0
};
if add < 255 {
match_length += add as usize;
} else if (dumps as usize) <= (de as usize).wrapping_sub(3) {
match_length = zstd_read_le24(dumps) as usize;
dumps = dumps.add(3);
}
}
match_length += MINMATCH;
/* save result */
seq.lit_length = lit_length;
seq.offset = offset;
seq.match_length = match_length;
seq_state.dumps = dumps;
}
unsafe fn zstd_exec_sequence(
op: *mut u8,
sequence: Seq,
lit_ptr: &mut *const u8,
lit_limit: *const u8,
base: *const u8,
oend: *mut u8,
) -> usize {
static DEC32TABLE: [usize; 8] = [0, 1, 2, 1, 4, 4, 4, 4]; /* added */
static DEC64TABLE: [usize; 8] = [8, 8, 8, 7, 8, 9, 10, 11]; /* subtracted */
let ostart = op;
let mut op = op;
let o_lit_end = op.wrapping_add(sequence.lit_length);
let lit_length = sequence.lit_length;
/* risk : address space overflow (32-bits) */
let end_match = op
.wrapping_add(lit_length)
.wrapping_add(sequence.match_length);
let lit_end = (*lit_ptr).wrapping_add(lit_length);
/* checks */
let seq_length = sequence.lit_length.wrapping_add(sequence.match_length);
if seq_length > (oend as usize).wrapping_sub(op as usize) {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if sequence.lit_length > (lit_limit as usize).wrapping_sub(*lit_ptr as usize) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
/* Now we know there are no overflow in literal nor match lengths, can use pointer checks */
if sequence.offset > ((o_lit_end as usize).wrapping_sub(base as usize)) as u32 as usize {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if (end_match as usize) > (oend as usize) {
/* overwrite beyond dst buffer */
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if (lit_end as usize) > (lit_limit as usize) {
/* overRead beyond lit buffer */
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if sequence.match_length > (*lit_ptr as usize).wrapping_sub(op as usize) {
/* overwrite literal segment */
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
/* copy Literals */
/* note : v0.1 seems to allow scenarios where output or input are close to end of buffer */
ptr::copy(*lit_ptr, op, sequence.lit_length);
op = op.add(lit_length);
*lit_ptr = lit_end; /* update for next sequence */
/* check : last match must be at a minimum distance of 8 from end of dest buffer */
if (oend as usize) - (op as usize) < 8 {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
/* copy Match */
{
let overlap_risk = (lit_end as usize).wrapping_sub(end_match as usize) < 12;
/* possible underflow at op - offset ? */
let mut match_ptr = (op as usize).wrapping_sub(sequence.offset) as *const u8;
let mut qutt: usize = 12;
let mut saved = [0u8; 16]; /* U64 saved[2] */
/* check */
if (match_ptr as usize) < (base as usize) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if sequence.offset > base as usize {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
/* save beginning of literal sequence, in case of write overlap */
if overlap_risk {
if (end_match as usize).wrapping_add(qutt) > (oend as usize) {
qutt = (oend as usize) - (end_match as usize);
}
ptr::copy_nonoverlapping(end_match as *const u8, saved.as_mut_ptr(), qutt);
}
if sequence.offset < 8 {
let dec64 = DEC64TABLE[sequence.offset];
*op = *match_ptr;
*op.add(1) = *match_ptr.add(1);
*op.add(2) = *match_ptr.add(2);
*op.add(3) = *match_ptr.add(3);
match_ptr = match_ptr.add(DEC32TABLE[sequence.offset]);
zstd_copy4(op.add(4), match_ptr);
match_ptr = match_ptr.sub(dec64);
} else {
zstd_copy8(op, match_ptr);
}
op = op.add(8);
match_ptr = match_ptr.add(8);
if (end_match as usize) > (oend as usize).wrapping_sub(16 - MINMATCH) {
if (op as usize) < (oend as usize).wrapping_sub(8) {
let dist = ((oend as usize) - 8) - (op as usize);
zstd_wildcopy(op, match_ptr, dist as isize);
match_ptr = match_ptr.add(dist);
op = oend.sub(8);
}
while (op as usize) < (end_match as usize) {
*op = *match_ptr;
op = op.add(1);
match_ptr = match_ptr.add(1);
}
} else {
/* works even if matchLength < 8 */
zstd_wildcopy(op, match_ptr, sequence.match_length as isize - 8);
}
/* restore, in case of overlap */
if overlap_risk {
ptr::copy_nonoverlapping(saved.as_ptr(), end_match, qutt);
}
}
(end_match as usize) - (ostart as usize)
}
unsafe fn zstd_decompress_sequences(
ctx: *mut ZSTDv01_Dctx,
dst: *mut u8,
max_dst_size: usize,
seq_start: *const u8,
seq_size: usize,
lit_start: *const u8,
lit_size: usize,
) -> usize {
let dctx = ctx;
let mut ip = seq_start;
let iend_addr = (ip as usize).wrapping_add(seq_size);
let ostart = dst;
let mut op = ostart;
let oend = ostart.add(max_dst_size);
let mut lit_ptr = lit_start;
let lit_end = lit_start.add(lit_size);
let mut nb_seq: i32 = 0;
let mut dumps: *const u8 = ptr::null();
let mut dumps_length: usize = 0;
let dtable_ll = (*dctx).ll_table.as_mut_ptr();
let dtable_ml = (*dctx).ml_table.as_mut_ptr();
let dtable_offb = (*dctx).off_table.as_mut_ptr();
let base = (*dctx).base;
/* Build Decoding Tables */
let error_code = zstdv01_decode_seq_headers(
&mut nb_seq,
&mut dumps,
&mut dumps_length,
dtable_ll,
dtable_ml,
dtable_offb,
ip,
iend_addr - ip as usize,
);
if ERR_isError(error_code) {
return error_code;
}
ip = ip.add(error_code);
/* Regen sequences */
{
let mut sequence = Seq {
lit_length: 0,
offset: 0,
match_length: 0,
};
let mut seq_state = SeqState {
d_stream: FseDStream {
bit_container: 0,
bits_consumed: 0,
ptr: ptr::null(),
start: ptr::null(),
},
state_ll: FseDState {
state: 0,
table: ptr::null(),
},
state_offb: FseDState {
state: 0,
table: ptr::null(),
},
state_ml: FseDState {
state: 0,
table: ptr::null(),
},
prev_offset: 1,
dumps,
dumps_end: dumps.wrapping_add(dumps_length),
};
let error_code = fse_init_dstream(&mut seq_state.d_stream, ip, iend_addr - ip as usize);
if fse_is_error(error_code) {
return ERROR(ZstdErrorCode::CorruptionDetected);
}
fse_init_dstate(&mut seq_state.state_ll, &mut seq_state.d_stream, dtable_ll);
fse_init_dstate(
&mut seq_state.state_offb,
&mut seq_state.d_stream,
dtable_offb,
);
fse_init_dstate(&mut seq_state.state_ml, &mut seq_state.d_stream, dtable_ml);
while fse_reload_dstream(&mut seq_state.d_stream) <= FSE_DSTREAM_COMPLETED && nb_seq > 0 {
nb_seq -= 1;
zstd_decode_sequence(&mut sequence, &mut seq_state);
let one_seq_size = zstd_exec_sequence(op, sequence, &mut lit_ptr, lit_end, base, oend);
if ERR_isError(one_seq_size) {
return one_seq_size;
}
op = op.add(one_seq_size);
}
/* check if reached exact end */
if !fse_end_of_dstream(&seq_state.d_stream) {
/* requested too much : data is corrupted */
return ERROR(ZstdErrorCode::CorruptionDetected);
}
if nb_seq < 0 {
/* requested too many sequences : data is corrupted */
return ERROR(ZstdErrorCode::CorruptionDetected);
}
/* last literal segment */
{
let last_ll_size = (lit_end as usize) - (lit_ptr as usize);
if (op as usize).wrapping_add(last_ll_size) > (oend as usize) {
return ERROR(ZstdErrorCode::DstSizeTooSmall);
}
if last_ll_size > 0 {
if !std::ptr::eq(op as *const u8, lit_ptr) {
ptr::copy(lit_ptr, op, last_ll_size);
}
op = op.add(last_ll_size);
}
}
}
(op as usize) - (ostart as usize)
}
unsafe fn zstd_decompress_block(
ctx: *mut ZSTDv01_Dctx,
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
/* blockType == blockCompressed, srcSize is trusted */
let mut ip = src;
let mut lit_ptr: *const u8 = ptr::null();
let mut lit_size: usize = 0;
/* Decode literals sub-block */
let error_code = zstdv01_decode_literals_block(
dst,
max_dst_size,
&mut lit_ptr,
&mut lit_size,
src,
src_size,
);
if ERR_isError(error_code) {
return error_code;
}
ip = ip.add(error_code);
let src_size = src_size - error_code;
zstd_decompress_sequences(ctx, dst, max_dst_size, ip, src_size, lit_ptr, lit_size)
}
unsafe fn zstdv01_decompress_dctx(
ctx: *mut ZSTDv01_Dctx,
dst: *mut u8,
max_dst_size: usize,
src: *const u8,
src_size: usize,
) -> usize {
let mut ip = src;
let iend_addr = (ip as usize).wrapping_add(src_size);
let ostart = dst;
let mut op = ostart;
let oend_addr = (ostart as usize).wrapping_add(max_dst_size);
let mut remaining_size = src_size;
let mut error_code: usize = 0;
/* Frame Header */
if src_size < ZSTD_FRAME_HEADER_SIZE + ZSTD_BLOCK_HEADER_SIZE {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
let magic_number = zstd_read_be32(src);
if magic_number != ZSTD_MAGIC_NUMBER {
return ERROR(ZstdErrorCode::PrefixUnknown);
}
ip = ip.add(ZSTD_FRAME_HEADER_SIZE);
remaining_size -= ZSTD_FRAME_HEADER_SIZE;
/* Loop on each block */
loop {
let mut block_properties = BlockProperties {
block_type: 0,
orig_size: 0,
};
let block_size =
zstdv01_getc_block_size(ip, iend_addr - ip as usize, &mut block_properties);
if ERR_isError(block_size) {
return block_size;
}
ip = ip.add(ZSTD_BLOCK_HEADER_SIZE);
remaining_size -= ZSTD_BLOCK_HEADER_SIZE;
if block_size > remaining_size {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
match block_properties.block_type {
BT_COMPRESSED => {
error_code =
zstd_decompress_block(ctx, op, oend_addr - op as usize, ip, block_size);
}
BT_RAW => {
error_code =
zstd_copy_uncompressed_block(op, oend_addr - op as usize, ip, block_size);
}
BT_RLE => {
return ERROR(ZstdErrorCode::Generic); /* not yet supported */
}
BT_END => {
/* end of frame */
if remaining_size != 0 {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
}
_ => {
return ERROR(ZstdErrorCode::Generic);
}
}
if block_size == 0 {
break; /* bt_end */
}
if ERR_isError(error_code) {
return error_code;
}
op = op.add(error_code);
ip = ip.add(block_size);
remaining_size -= block_size;
}
(op as usize) - (ostart as usize)
}
/* ZSTD_errorFrameSizeInfoLegacy() :
assumes `cSize` and `dBound` are _not_ NULL */
unsafe fn zstd_error_frame_size_info_legacy(c_size: *mut usize, d_bound: *mut u64, ret: usize) {
*c_size = ret;
*d_bound = ZSTD_CONTENTSIZE_ERROR;
}
/* ******************************************
* Exported C ABI (zstd_v01.h)
********************************************/
/// C ABI: `ZSTDv01_isError`.
#[no_mangle]
pub extern "C" fn ZSTDv01_isError(code: usize) -> c_uint {
ERR_isError(code) as c_uint
}
/// C ABI: `ZSTDv01_decompressDCtx`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_decompressDCtx(
ctx: *mut c_void,
dst: *mut c_void,
max_original_size: usize,
src: *const c_void,
compressed_size: usize,
) -> usize {
zstdv01_decompress_dctx(
ctx as *mut ZSTDv01_Dctx,
dst as *mut u8,
max_original_size,
src as *const u8,
compressed_size,
)
}
/// C ABI: `ZSTDv01_decompress`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_decompress(
dst: *mut c_void,
max_original_size: usize,
src: *const c_void,
compressed_size: usize,
) -> usize {
/* The C version uses an uninitialized on-stack dctx_t; only `base` is
* read before being written, so a zeroed context is equivalent. */
let mut ctx = std::mem::MaybeUninit::<ZSTDv01_Dctx>::zeroed();
let ctx_ptr = ctx.as_mut_ptr();
(*ctx_ptr).base = dst as *const u8;
zstdv01_decompress_dctx(
ctx_ptr,
dst as *mut u8,
max_original_size,
src as *const u8,
compressed_size,
)
}
/// C ABI: `ZSTDv01_findFrameSizeInfoLegacy`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_findFrameSizeInfoLegacy(
src: *const c_void,
src_size: usize,
c_size: *mut usize,
d_bound: *mut u64,
) {
let mut ip = src as *const u8;
let mut remaining_size = src_size;
let mut nb_blocks: usize = 0;
/* Frame Header */
if src_size < ZSTD_FRAME_HEADER_SIZE + ZSTD_BLOCK_HEADER_SIZE {
zstd_error_frame_size_info_legacy(c_size, d_bound, ERROR(ZstdErrorCode::SrcSizeWrong));
return;
}
let magic_number = zstd_read_be32(src as *const u8);
if magic_number != ZSTD_MAGIC_NUMBER {
zstd_error_frame_size_info_legacy(c_size, d_bound, ERROR(ZstdErrorCode::PrefixUnknown));
return;
}
ip = ip.add(ZSTD_FRAME_HEADER_SIZE);
remaining_size -= ZSTD_FRAME_HEADER_SIZE;
/* Loop on each block */
loop {
let mut block_properties = BlockProperties {
block_type: 0,
orig_size: 0,
};
let block_size = zstdv01_getc_block_size(ip, remaining_size, &mut block_properties);
if ERR_isError(block_size) {
zstd_error_frame_size_info_legacy(c_size, d_bound, block_size);
return;
}
ip = ip.add(ZSTD_BLOCK_HEADER_SIZE);
remaining_size -= ZSTD_BLOCK_HEADER_SIZE;
if block_size > remaining_size {
zstd_error_frame_size_info_legacy(c_size, d_bound, ERROR(ZstdErrorCode::SrcSizeWrong));
return;
}
if block_size == 0 {
break; /* bt_end */
}
ip = ip.add(block_size);
remaining_size -= block_size;
nb_blocks += 1;
}
*c_size = (ip as usize) - (src as usize);
*d_bound = (nb_blocks * BLOCKSIZE) as u64;
}
/* ******************************************
* Streaming Decompression API
********************************************/
/// C ABI: `ZSTDv01_resetDCtx`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_resetDCtx(dctx: *mut ZSTDv01_Dctx) -> usize {
(*dctx).expected = ZSTD_FRAME_HEADER_SIZE;
(*dctx).phase = 0;
(*dctx).previous_dst_end = ptr::null();
(*dctx).base = ptr::null();
0
}
/// C ABI: `ZSTDv01_createDCtx`. Allocated with `malloc` exactly like the C
/// implementation, so create/free may be paired across the C/Rust boundary.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_createDCtx() -> *mut ZSTDv01_Dctx {
let dctx = libc::malloc(std::mem::size_of::<ZSTDv01_Dctx>()) as *mut ZSTDv01_Dctx;
if dctx.is_null() {
return ptr::null_mut();
}
ZSTDv01_resetDCtx(dctx);
dctx
}
/// C ABI: `ZSTDv01_freeDCtx`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_freeDCtx(dctx: *mut ZSTDv01_Dctx) -> usize {
libc::free(dctx as *mut c_void);
0
}
/// C ABI: `ZSTDv01_nextSrcSizeToDecompress`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_nextSrcSizeToDecompress(dctx: *mut ZSTDv01_Dctx) -> usize {
(*dctx).expected
}
/// C ABI: `ZSTDv01_decompressContinue`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDv01_decompressContinue(
dctx: *mut ZSTDv01_Dctx,
dst: *mut c_void,
max_dst_size: usize,
src: *const c_void,
src_size: usize,
) -> usize {
let ctx = dctx;
let src = src as *const u8;
let dst = dst as *mut u8;
/* Sanity check */
if src_size != (*ctx).expected {
return ERROR(ZstdErrorCode::SrcSizeWrong);
}
if !std::ptr::eq(dst as *const u8, (*ctx).previous_dst_end) {
/* not contiguous */
(*ctx).base = dst as *const u8;
}
/* Decompress : frame header */
if (*ctx).phase == 0 {
/* Check frame magic header */
let magic_number = zstd_read_be32(src);
if magic_number != ZSTD_MAGIC_NUMBER {
return ERROR(ZstdErrorCode::PrefixUnknown);
}
(*ctx).phase = 1;
(*ctx).expected = ZSTD_BLOCK_HEADER_SIZE;
return 0;
}
/* Decompress : block header */
if (*ctx).phase == 1 {
let mut bp = BlockProperties {
block_type: 0,
orig_size: 0,
};
let block_size = zstdv01_getc_block_size(src, ZSTD_BLOCK_HEADER_SIZE, &mut bp);
if ERR_isError(block_size) {
return block_size;
}
if bp.block_type == BT_END {
(*ctx).expected = 0;
(*ctx).phase = 0;
} else {
(*ctx).expected = block_size;
(*ctx).b_type = bp.block_type;
(*ctx).phase = 2;
}
return 0;
}
/* Decompress : block content */
{
let r_size = match (*ctx).b_type {
BT_COMPRESSED => zstd_decompress_block(ctx, dst, max_dst_size, src, src_size),
BT_RAW => zstd_copy_uncompressed_block(dst, max_dst_size, src, src_size),
BT_RLE => {
return ERROR(ZstdErrorCode::Generic); /* not yet handled */
}
BT_END => {
/* should never happen (filtered at phase 1) */
0
}
_ => {
return ERROR(ZstdErrorCode::Generic);
}
};
(*ctx).phase = 1;
(*ctx).expected = ZSTD_BLOCK_HEADER_SIZE;
if ERR_isError(r_size) {
return r_size;
}
(*ctx).previous_dst_end = (dst as usize + r_size) as *const u8;
r_size
}
}
#[cfg(test)]
mod tests {
use super::*;
/// 237-byte base text; the fixtures compress repetitions of it.
const SAMPLE: &str = "snowden is snowed in / he's now then in his snow den / when does the snow end?\ngoodbye little dog / you dug some holes in your day / they'll be hard to fill.\nwhen life shuts a door, / just open it. it's a door. / that is how doors work.\n";
/// `SAMPLE` repeated 3 times (711 bytes), compressed by a zstd binary
/// built from the v0.1.0 tag. Single compressed block (FSE + Huff0),
/// verified byte-identical against the pristine C decoder.
const FRAME_ENTROPY: &[u8] = &[
0xFD, 0x2F, 0xB5, 0x1E, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x93, 0x00, 0xD6, 0x19, 0x80, 0x87,
0xB1, 0x00, 0xC0, 0x73, 0xC5, 0x8A, 0xA5, 0x6C, 0x6F, 0x4B, 0xF2, 0x9F, 0x94, 0xC4, 0x2B,
0xE4, 0x6B, 0x2C, 0x96, 0xAE, 0x5F, 0xC8, 0x9C, 0x1F, 0x00, 0x1C, 0x00, 0x1B, 0x00, 0x43,
0x31, 0x70, 0xE4, 0xC9, 0xA3, 0x46, 0xD9, 0xAD, 0xFD, 0x78, 0x37, 0x66, 0xA6, 0x4D, 0x15,
0xCA, 0x33, 0xF5, 0xA7, 0x31, 0x83, 0x02, 0x96, 0x7E, 0xD0, 0x5E, 0xAC, 0xE9, 0x48, 0x03,
0xC7, 0xEF, 0x44, 0x81, 0x0B, 0x00, 0x90, 0xEA, 0x55, 0x41, 0xD8, 0x4E, 0x36, 0x10, 0xAC,
0x95, 0x92, 0x42, 0x5D, 0x92, 0xF4, 0x64, 0x7F, 0x96, 0x44, 0x59, 0x0B, 0x03, 0x4A, 0xF6,
0x16, 0x9F, 0xC8, 0xB9, 0x11, 0x70, 0xB8, 0x35, 0x06, 0xC4, 0x26, 0xC7, 0x6A, 0x89, 0xE4,
0x6F, 0x65, 0xC0, 0x9B, 0x55, 0x08, 0xEB, 0xE8, 0x37, 0x66, 0x67, 0x9E, 0x7D, 0x57, 0xA0,
0x25, 0x0D, 0xA4, 0x3E, 0x26, 0x8F, 0x32, 0xF6, 0xC9, 0x49, 0x50, 0x40, 0x0A, 0xED, 0x34,
0x94, 0x15, 0x5E, 0xE3, 0x1B, 0xD6, 0x27, 0x05, 0x00, 0x54, 0x05, 0x41, 0xFF, 0xD6, 0x01,
0x00, 0x80, 0x36, 0x00, 0xFE, 0x8D, 0x38, 0x42, 0x51, 0x7E, 0x40, 0x38, 0xD5, 0x60, 0x46,
0x03, 0x90, 0x25, 0xC0, 0x00, 0x00,
];
/// 48 random bytes compressed by v0.1.0: a raw (uncompressed) block.
const FRAME_RAW: &[u8] = &[
0xFD, 0x2F, 0xB5, 0x1E, 0x40, 0x00, 0x30, 0x77, 0xE9, 0x9A, 0x70, 0x2F, 0x77, 0x16, 0x9A,
0x92, 0xD4, 0xEE, 0x72, 0xFD, 0xDD, 0x0F, 0x39, 0x33, 0x8B, 0x8B, 0x3C, 0x6A, 0x91, 0xB2,
0x2B, 0x3A, 0xF0, 0x0E, 0xA6, 0x4C, 0x51, 0xA0, 0xC9, 0x5F, 0x69, 0x1A, 0xCE, 0x93, 0x5C,
0x7F, 0x43, 0x0C, 0xD9, 0x25, 0xBC, 0x91, 0xDD, 0x6E, 0xD7, 0xC0, 0x00, 0x00,
];
const RAW_PAYLOAD: &[u8] = &[
0x77, 0xE9, 0x9A, 0x70, 0x2F, 0x77, 0x16, 0x9A, 0x92, 0xD4, 0xEE, 0x72, 0xFD, 0xDD, 0x0F,
0x39, 0x33, 0x8B, 0x8B, 0x3C, 0x6A, 0x91, 0xB2, 0x2B, 0x3A, 0xF0, 0x0E, 0xA6, 0x4C, 0x51,
0xA0, 0xC9, 0x5F, 0x69, 0x1A, 0xCE, 0x93, 0x5C, 0x7F, 0x43, 0x0C, 0xD9, 0x25, 0xBC, 0x91,
0xDD, 0x6E, 0xD7,
];
/// `SAMPLE` repeated 1800 times (426600 bytes) compressed by v0.1.0:
/// four data blocks, so it exercises multi-block frames and repeated
/// offsets.
const FRAME_MULTI_BLOCK: &[u8] = &[
0xFD, 0x2F, 0xB5, 0x1E, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x93, 0x00, 0xD6, 0x19, 0x80, 0x87,
0xB1, 0x00, 0xC0, 0x73, 0xC5, 0x8A, 0xA5, 0x6C, 0x6F, 0x4B, 0xF2, 0x9F, 0x94, 0xC4, 0x2B,
0xE4, 0x6B, 0x2C, 0x96, 0xAE, 0x5F, 0xC8, 0x9C, 0x1F, 0x00, 0x1C, 0x00, 0x1B, 0x00, 0x43,
0x31, 0x70, 0xE4, 0xC9, 0xA3, 0x46, 0xD9, 0xAD, 0xFD, 0x78, 0x37, 0x66, 0xA6, 0x4D, 0x15,
0xCA, 0x33, 0xF5, 0xA7, 0x31, 0x83, 0x02, 0x96, 0x7E, 0xD0, 0x5E, 0xAC, 0xE9, 0x48, 0x03,
0xC7, 0xEF, 0x44, 0x81, 0x0B, 0x00, 0x90, 0xEA, 0x55, 0x41, 0xD8, 0x4E, 0x36, 0x10, 0xAC,
0x95, 0x92, 0x42, 0x5D, 0x92, 0xF4, 0x64, 0x7F, 0x96, 0x44, 0x59, 0x0B, 0x03, 0x4A, 0xF6,
0x16, 0x9F, 0xC8, 0xB9, 0x11, 0x70, 0xB8, 0x35, 0x06, 0xC4, 0x26, 0xC7, 0x6A, 0x89, 0xE4,
0x6F, 0x65, 0xC0, 0x9B, 0x55, 0x08, 0xEB, 0xE8, 0x37, 0x66, 0x67, 0x9E, 0x7D, 0x57, 0xA0,
0x25, 0x0D, 0xA4, 0x3E, 0x26, 0x8F, 0x32, 0xF6, 0xC9, 0x49, 0x50, 0x40, 0x0A, 0xED, 0x34,
0x94, 0x15, 0x5E, 0xE3, 0x1B, 0xD6, 0x27, 0x05, 0x00, 0x54, 0x05, 0x41, 0xFF, 0x0F, 0xFF,
0x01, 0x80, 0x36, 0x00, 0xFE, 0x8D, 0x38, 0x42, 0x51, 0x7E, 0x40, 0x38, 0xD5, 0x60, 0x46,
0x03, 0x90, 0x25, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x01, 0x00, 0x54, 0x04, 0xFF, 0xFC,
0xFF, 0x01, 0x80, 0xFA, 0x7F, 0x00, 0xFC, 0x23, 0x10, 0x00, 0x00, 0x12, 0x40, 0x00, 0x00,
0x01, 0x00, 0x54, 0x04, 0xFF, 0xFC, 0xFF, 0x01, 0x00, 0xF5, 0xFF, 0x00, 0xF8, 0x4B, 0x20,
0x00, 0x00, 0x12, 0x40, 0x00, 0x00, 0x01, 0x00, 0x54, 0x04, 0xFF, 0x64, 0x82, 0x00, 0x80,
0xEF, 0xFF, 0x00, 0xF0, 0x9F, 0x40, 0xC0, 0x00, 0x00,
];
fn decompress(frame: &[u8], capacity: usize) -> Result<Vec<u8>, usize> {
let mut out = vec![0u8; capacity];
let code = unsafe {
ZSTDv01_decompress(
out.as_mut_ptr() as *mut c_void,
capacity,
frame.as_ptr() as *const c_void,
frame.len(),
)
};
if ZSTDv01_isError(code) != 0 {
return Err(code);
}
out.truncate(code);
Ok(out)
}
fn frame_size_info(frame: &[u8]) -> (usize, u64) {
let mut c_size = 0usize;
let mut d_bound = 0u64;
unsafe {
ZSTDv01_findFrameSizeInfoLegacy(
frame.as_ptr() as *const c_void,
frame.len(),
&mut c_size,
&mut d_bound,
);
}
(c_size, d_bound)
}
#[test]
fn decodes_entropy_frame_byte_identically() {
let expected = SAMPLE.repeat(3).into_bytes();
let out = decompress(FRAME_ENTROPY, expected.len()).unwrap();
assert_eq!(out, expected);
}
#[test]
fn decodes_raw_block_frame_byte_identically() {
let out = decompress(FRAME_RAW, RAW_PAYLOAD.len()).unwrap();
assert_eq!(out, RAW_PAYLOAD);
}
#[test]
fn decodes_multi_block_frame_byte_identically() {
let expected = SAMPLE.repeat(1800).into_bytes();
assert_eq!(expected.len(), 426600);
let out = decompress(FRAME_MULTI_BLOCK, expected.len()).unwrap();
assert_eq!(out, expected);
}
#[test]
fn reports_frame_size_info_like_c() {
/* Values verified against the pristine C implementation. */
assert_eq!(
frame_size_info(FRAME_ENTROPY),
(FRAME_ENTROPY.len(), 131072)
);
assert_eq!(frame_size_info(FRAME_RAW), (FRAME_RAW.len(), 131072));
assert_eq!(
frame_size_info(FRAME_MULTI_BLOCK),
(FRAME_MULTI_BLOCK.len(), 524288)
);
}
#[test]
fn rejects_bad_magic_with_prefix_unknown() {
let mut frame = FRAME_ENTROPY.to_vec();
frame[0] ^= 0x55;
assert_eq!(
decompress(&frame, 1024).unwrap_err(),
ERROR(ZstdErrorCode::PrefixUnknown)
);
let (c_size, d_bound) = frame_size_info(&frame);
assert_eq!(c_size, ERROR(ZstdErrorCode::PrefixUnknown));
assert_eq!(d_bound, ZSTD_CONTENTSIZE_ERROR);
}
#[test]
fn rejects_truncated_frames_with_src_size_wrong() {
for frame in [FRAME_ENTROPY, FRAME_RAW, FRAME_MULTI_BLOCK] {
let truncated = &frame[..frame.len() - 1];
assert_eq!(
decompress(truncated, 1 << 20).unwrap_err(),
ERROR(ZstdErrorCode::SrcSizeWrong)
);
assert_eq!(
decompress(&frame[..5], 1 << 20).unwrap_err(),
ERROR(ZstdErrorCode::SrcSizeWrong)
);
}
}
#[test]
fn rejects_small_destination_with_dst_size_too_small() {
/* Error codes verified against the pristine C implementation. */
let expected_len = SAMPLE.len() * 3;
assert_eq!(
decompress(FRAME_ENTROPY, expected_len - 1).unwrap_err(),
ERROR(ZstdErrorCode::DstSizeTooSmall)
);
assert_eq!(
decompress(FRAME_ENTROPY, 0).unwrap_err(),
ERROR(ZstdErrorCode::DstSizeTooSmall)
);
assert_eq!(
decompress(FRAME_RAW, RAW_PAYLOAD.len() - 1).unwrap_err(),
ERROR(ZstdErrorCode::DstSizeTooSmall)
);
}
#[test]
fn streaming_api_decodes_the_frame() {
let expected = SAMPLE.repeat(3).into_bytes();
let mut out = vec![0u8; expected.len()];
let mut in_pos = 0usize;
let mut out_pos = 0usize;
unsafe {
let dctx = ZSTDv01_createDCtx();
assert!(!dctx.is_null());
loop {
let needed = ZSTDv01_nextSrcSizeToDecompress(dctx);
if needed == 0 {
break;
}
assert!(in_pos + needed <= FRAME_ENTROPY.len());
let produced = ZSTDv01_decompressContinue(
dctx,
out.as_mut_ptr().add(out_pos) as *mut c_void,
out.len() - out_pos,
FRAME_ENTROPY.as_ptr().add(in_pos) as *const c_void,
needed,
);
assert_eq!(ZSTDv01_isError(produced), 0);
in_pos += needed;
out_pos += produced;
}
assert_eq!(ZSTDv01_freeDCtx(dctx), 0);
}
assert_eq!(in_pos, FRAME_ENTROPY.len());
assert_eq!(out_pos, expected.len());
assert_eq!(out, expected);
}
#[test]
fn streaming_context_reset_and_null_free() {
unsafe {
let dctx = ZSTDv01_createDCtx();
assert_eq!(ZSTDv01_nextSrcSizeToDecompress(dctx), 4);
/* bad magic through the streaming entry point */
let bad = [0u8; 4];
assert_eq!(
ZSTDv01_decompressContinue(
dctx,
ptr::null_mut(),
0,
bad.as_ptr() as *const c_void,
4
),
ERROR(ZstdErrorCode::PrefixUnknown)
);
/* wrong srcSize */
assert_eq!(
ZSTDv01_decompressContinue(
dctx,
ptr::null_mut(),
0,
bad.as_ptr() as *const c_void,
3
),
ERROR(ZstdErrorCode::SrcSizeWrong)
);
assert_eq!(ZSTDv01_resetDCtx(dctx), 0);
assert_eq!(ZSTDv01_nextSrcSizeToDecompress(dctx), 4);
assert_eq!(ZSTDv01_freeDCtx(dctx), 0);
assert_eq!(ZSTDv01_freeDCtx(ptr::null_mut()), 0);
}
}
}
+1
View File
@@ -16,6 +16,7 @@ pub mod hist;
pub mod huf_compress;
#[cfg(feature = "decompression")]
pub mod huf_decompress;
pub mod legacy;
pub mod mem;
pub mod pool;
pub mod threading;
+12 -3
View File
@@ -65,16 +65,24 @@ endif
endif
RUST_HUF_FEATURE :=
RUST_BUILD_CONFIG := default
RUST_HUF_MODE := default
ifneq ($(RUST_HUF_FORCE_X1),0)
RUST_HUF_FEATURE := huf-force-decompress-x1
RUST_BUILD_CONFIG := huf-force-decompress-x1
RUST_HUF_MODE := huf-force-decompress-x1
endif
ifneq ($(RUST_HUF_FORCE_X2),0)
RUST_HUF_FEATURE := huf-force-decompress-x2
RUST_BUILD_CONFIG := huf-force-decompress-x2
RUST_HUF_MODE := huf-force-decompress-x2
endif
# Test binaries compile every lib/legacy/*.c file regardless of the dispatch
# level (ZSTDLEGACY_FILES is a plain wildcard below), so the Rust archive must
# always carry all ported legacy decoders too. The build configuration still
# encodes the level because the flat C objects bake -DZSTD_LEGACY_SUPPORT into
# the dispatch code and must never outlive a level change.
RUST_LEGACY_FEATURES := legacy-v01,legacy-v02,legacy-v03,legacy-v04,legacy-v05,legacy-v06,legacy-v07
RUST_BUILD_CONFIG := $(RUST_HUF_MODE)-legacy$(ZSTD_LEGACY_SUPPORT)
RUST_TARGET_DIR := $(RUST_DIR)/target/$(RUST_BUILD_CONFIG)
RUST_STATICLIB := $(RUST_TARGET_DIR)/release/libzstd_rs.a
RUST_TARGET_32 ?= i686-unknown-linux-gnu
@@ -84,6 +92,7 @@ RUST_CARGO_FLAGS := --manifest-path $(RUST_MANIFEST) --release \
ifneq ($(RUST_HUF_FEATURE),)
RUST_CARGO_FLAGS += --features $(RUST_HUF_FEATURE)
endif
RUST_CARGO_FLAGS += --features $(RUST_LEGACY_FEATURES)
$(RUST_STATICLIB): $(RUST_SOURCES)
$(CARGO) build $(RUST_CARGO_FLAGS)