ZXC 0.15.0
High-performance asymmetric lossless compression library
Loading...
Searching...
No Matches
zxc_dispatch.c
Go to the documentation of this file.
1// SPDX-License-Identifier: BSD-3-Clause
2/*
3 * ZXC - High-performance lossless compression
4 *
5 * Copyright (c) Bertrand Lebonnois and contributors.
6 */
7
20#include "../../include/zxc_dict.h"
21#include "../../include/zxc_error.h"
22#include "zxc_internal.h"
23
24// ZXC_DISABLE_SIMD => force ZXC_ONLY_DEFAULT so the dispatcher never selects
25// an AVX2/AVX512/NEON variant.
26#if defined(ZXC_DISABLE_SIMD) && !defined(ZXC_ONLY_DEFAULT)
27#define ZXC_ONLY_DEFAULT
28#endif
29
30#if defined(_MSC_VER)
31#include <intrin.h>
32#if defined(_M_X64)
33#include <immintrin.h> // _xgetbv (x86-specific header; x64 AVX state check)
34#endif
35#endif
36
37#if (defined(__x86_64__) || defined(_M_X64)) && !defined(_MSC_VER) && !defined(ZXC_ONLY_DEFAULT)
38#include <cpuid.h> // __cpuid_count: CPUID probes in zxc_detect_cpu_features
39#endif
40
41#if defined(__linux__) && (defined(__arm__) || defined(_M_ARM)) && !defined(ZXC_ONLY_DEFAULT)
42#include <sys/auxv.h>
43// musl does not ship <asm/hwcap.h>; HWCAP_NEON is stable arm32 UAPI. Nested:
44// naming __has_include in a single #if is a syntax error where it is missing.
45#ifdef __has_include
46#if __has_include(<asm/hwcap.h>)
47#include <asm/hwcap.h>
48#endif
49#endif
50#ifndef HWCAP_NEON
51#define HWCAP_NEON (1 << 12)
52#endif
53#endif
54
55// ============================================================================
56// PROTOTYPES FOR MULTI-VERSIONED VARIANTS
57// ============================================================================
58// These are compiled in separate translation units with different flags.
59
60// Decompression Prototypes
61int zxc_decompress_chunk_wrapper_default(const zxc_cctx_t* RESTRICT ctx,
62 const uint8_t* RESTRICT src, const size_t src_sz,
63 uint8_t* RESTRICT dst, const size_t dst_cap,
64 const uint64_t block_index);
65int zxc_decompress_chunk_wrapper_dict_default(const zxc_cctx_t* RESTRICT ctx,
66 const uint8_t* RESTRICT src, const size_t src_sz,
67 uint8_t* RESTRICT dst, const size_t dst_cap,
68 const uint64_t block_index);
69int zxc_decompress_chunk_wrapper_safe_default(const zxc_cctx_t* RESTRICT ctx,
70 const uint8_t* RESTRICT src, const size_t src_sz,
71 uint8_t* RESTRICT dst, const size_t dst_cap,
72 const uint64_t block_index);
73
74#ifndef ZXC_ONLY_DEFAULT
75#if defined(__x86_64__) || defined(_M_X64)
76int zxc_decompress_chunk_wrapper_avx2(const zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
77 const size_t src_sz, uint8_t* RESTRICT dst,
78 const size_t dst_cap, const uint64_t block_index);
79int zxc_decompress_chunk_wrapper_dict_avx2(const zxc_cctx_t* RESTRICT ctx,
80 const uint8_t* RESTRICT src, const size_t src_sz,
81 uint8_t* RESTRICT dst, const size_t dst_cap,
82 const uint64_t block_index);
83int zxc_decompress_chunk_wrapper_avx512(const zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
84 const size_t src_sz, uint8_t* RESTRICT dst,
85 const size_t dst_cap, const uint64_t block_index);
86int zxc_decompress_chunk_wrapper_dict_avx512(const zxc_cctx_t* RESTRICT ctx,
87 const uint8_t* RESTRICT src, const size_t src_sz,
88 uint8_t* RESTRICT dst, const size_t dst_cap,
89 const uint64_t block_index);
90int zxc_decompress_chunk_wrapper_safe_avx2(const zxc_cctx_t* RESTRICT ctx,
91 const uint8_t* RESTRICT src, const size_t src_sz,
92 uint8_t* RESTRICT dst, const size_t dst_cap,
93 const uint64_t block_index);
94int zxc_decompress_chunk_wrapper_safe_avx512(const zxc_cctx_t* RESTRICT ctx,
95 const uint8_t* RESTRICT src, const size_t src_sz,
96 uint8_t* RESTRICT dst, const size_t dst_cap,
97 const uint64_t block_index);
98#elif defined(__arm__) || defined(_M_ARM)
99int zxc_decompress_chunk_wrapper_neon32(const zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
100 const size_t src_sz, uint8_t* RESTRICT dst,
101 const size_t dst_cap, const uint64_t block_index);
102int zxc_decompress_chunk_wrapper_dict_neon32(const zxc_cctx_t* RESTRICT ctx,
103 const uint8_t* RESTRICT src, const size_t src_sz,
104 uint8_t* RESTRICT dst, const size_t dst_cap,
105 const uint64_t block_index);
106int zxc_decompress_chunk_wrapper_safe_neon32(const zxc_cctx_t* RESTRICT ctx,
107 const uint8_t* RESTRICT src, const size_t src_sz,
108 uint8_t* RESTRICT dst, const size_t dst_cap,
109 const uint64_t block_index);
110#endif
111#endif
112
113// Compression Prototypes
114int zxc_compress_chunk_wrapper_default(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
115 const size_t src_sz, uint8_t* RESTRICT dst,
116 const size_t dst_cap, const uint64_t block_index);
117
118// Huffman prototypes (variant TUs of zxc_huffman.c). Compressor and decompressor
119// variants bind to the matching suffixed symbol at compile time, so the hot path
120// pays no dispatch; the wrappers below expose un-suffixed names to callers.
121int zxc_huf_build_code_lengths_default(const uint32_t* RESTRICT freq, uint8_t* RESTRICT code_len,
122 void* RESTRICT scratch, int max_code_len);
123size_t zxc_huf_calc_size_default(const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len,
124 int with_header);
125int zxc_huf_encode_section_default(const uint8_t* RESTRICT literals, size_t n_literals,
126 const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len,
127 uint8_t* RESTRICT dst, size_t dst_cap);
128int zxc_huf_decode_section_default(const uint8_t* RESTRICT payload, size_t payload_size,
129 uint8_t* RESTRICT dst, size_t n, uint8_t* RESTRICT scratch);
130int zxc_huf_encode_section_dict_default(const uint8_t* RESTRICT literals, size_t n_literals,
131 const uint32_t* RESTRICT freq,
132 const uint8_t* RESTRICT code_len,
133 const zxc_pivco_tree_t* RESTRICT tree,
134 const uint32_t* RESTRICT codes, uint8_t* RESTRICT dst,
135 size_t dst_cap);
136int zxc_huf_decode_section_dict_default(const uint8_t* RESTRICT payload, size_t payload_size,
137 uint8_t* RESTRICT dst, size_t n,
138 const zxc_pivco_tree_t* RESTRICT tree,
139 uint8_t* RESTRICT scratch);
140size_t zxc_huf_calc_size_dict_default(const uint32_t* RESTRICT freq,
141 const uint8_t* RESTRICT code_len,
142 const zxc_pivco_tree_t* RESTRICT tree);
143void zxc_huf_pack_lengths_default(const uint8_t* RESTRICT code_len, uint8_t* RESTRICT out);
144int zxc_huf_unpack_lengths_default(const uint8_t* RESTRICT in, uint8_t* RESTRICT code_len);
145
146#if defined(__x86_64__) || defined(_M_X64)
147int zxc_compress_chunk_wrapper_avx2(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
148 const size_t src_sz, uint8_t* RESTRICT dst,
149 const size_t dst_cap, const uint64_t block_index);
150int zxc_compress_chunk_wrapper_avx512(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
151 const size_t src_sz, uint8_t* RESTRICT dst,
152 const size_t dst_cap, const uint64_t block_index);
153#elif defined(__arm__) || defined(_M_ARM)
154int zxc_compress_chunk_wrapper_neon32(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
155 const size_t src_sz, uint8_t* RESTRICT dst,
156 const size_t dst_cap, const uint64_t block_index);
157#endif
158
159// ============================================================================
160// CPU DETECTION LOGIC
161// ============================================================================
162
163#if (defined(__x86_64__) || defined(_M_X64)) && !defined(ZXC_ONLY_DEFAULT)
164
166static inline void zxc_cpuid(const uint32_t leaf, const uint32_t sub, uint32_t regs[4]) {
167#if defined(_MSC_VER)
168 int r[4];
169 __cpuidex(r, (int)leaf, (int)sub);
170 regs[0] = (uint32_t)r[0];
171 regs[1] = (uint32_t)r[1];
172 regs[2] = (uint32_t)r[2];
173 regs[3] = (uint32_t)r[3];
174#else
175 __cpuid_count(leaf, sub, regs[0], regs[1], regs[2], regs[3]);
176#endif
177}
178
180static inline uint64_t zxc_xgetbv0(void) {
181#if defined(_MSC_VER)
182 return _xgetbv(0);
183#else
184 // Raw encoding: the xgetbv intrinsic needs -mxsave, which the baseline
185 // translation unit is not compiled with.
186 uint32_t lo, hi;
187 __asm__ volatile(".byte 0x0f, 0x01, 0xd0" : "=a"(lo), "=d"(hi) : "c"(0));
188 return ((uint64_t)hi << 32) | lo;
189#endif
190}
191#endif /* x86-64 && !ZXC_ONLY_DEFAULT */
192
194typedef int (*zxc_decompress_func_t)(const zxc_cctx_t* RESTRICT, const uint8_t* RESTRICT,
195 const size_t, uint8_t* RESTRICT, const size_t, const uint64_t);
197typedef int (*zxc_compress_func_t)(zxc_cctx_t* RESTRICT, const uint8_t* RESTRICT, const size_t,
198 uint8_t* RESTRICT, const size_t, const uint64_t);
199
200#ifndef ZXC_ONLY_DEFAULT
201
215
224// LCOV_EXCL_START
227
228#if defined(__x86_64__) || defined(_M_X64)
229 // AVX2/AVX512 need OS-enabled YMM/ZMM state: gate on OSXSAVE + XGETBV/XCR0,
230 // not CPUID alone (else a VEX/EVEX op faults #UD when the OS hasn't enabled it).
231 uint32_t regs[4];
232 int sse2 = 0;
233 int avx2 = 0;
234 int avx512 = 0;
235 int bmi_lzcnt = 0;
236
237 zxc_cpuid(0, 0, regs);
238 const uint32_t max_leaf = regs[0];
239
240 zxc_cpuid(1, 0, regs);
241 if (regs[3] & (1U << 26)) sse2 = 1; // CPUID.1:EDX[26]
242 if ((regs[2] & (1U << 27)) && max_leaf >= 7) { // OSXSAVE, and leaf 7 is real
243 const uint64_t xcr0 = zxc_xgetbv0();
244 if ((xcr0 & 0x6) == 0x6) { // SSE+YMM enabled
245 zxc_cpuid(7, 0, regs);
246 const int bmi1 = (regs[1] >> 3) & 1; // CPUID.7.0:EBX[3]
247 const int bmi2 = (regs[1] >> 8) & 1; // CPUID.7.0:EBX[8]
248 if (regs[1] & (1U << 5)) avx2 = 1;
249 // AVX512 also needs XCR0[5..7] (opmask/ZMM)
250 if ((regs[1] & (1U << 16)) && (regs[1] & (1U << 30)) && (regs[2] & (1U << 1)) &&
251 (regs[2] & (1U << 6)) && (xcr0 & 0xE0) == 0xE0)
252 avx512 = 1; /* AVX512 tier = F+BW+VBMI+VBMI2, as the variant is built */
253 // The AVX2/AVX512 variants are compiled with BMI1/BMI2/LZCNT enabled,
254 // so both gates must prove those bits too. LZCNT (ABM) lives in
255 // CPUID.80000001H:ECX[5]; that leaf is architectural on x86-64.
256 zxc_cpuid(0x80000001U, 0, regs);
257 bmi_lzcnt = bmi1 && bmi2 && ((regs[2] >> 5) & 1);
258 }
259 }
260
261 if (avx512 && bmi_lzcnt) {
262 features = ZXC_CPU_AVX512;
263 } else if (avx2 && bmi_lzcnt) {
264 features = ZXC_CPU_AVX2;
265 } else if (sse2) {
266 features = ZXC_CPU_SSE2;
267 }
268
269#elif defined(__aarch64__) || defined(_M_ARM64)
270 // ARM64 usually guarantees NEON
271 features = ZXC_CPU_NEON;
272
273#elif defined(__arm__) || defined(_M_ARM)
274 // ARM32 Runtime detection for Linux
275#if defined(__linux__)
276 const unsigned long hwcaps = getauxval(AT_HWCAP);
277 if (hwcaps & HWCAP_NEON) {
278 features = ZXC_CPU_NEON;
279 }
280#else
281// Fallback for non-Linux: rely on compiler flags.
282// If compiled with -mfpu=neon, we assume target supports it.
283// Otherwise, safe default is GENERIC.
284#if defined(__ARM_NEON)
285 features = ZXC_CPU_NEON;
286#endif
287#endif
288#endif
289
290 return features;
291}
292// LCOV_EXCL_STOP
293
294#endif // ZXC_ONLY_DEFAULT
295
296// ============================================================================
297// DISPATCHERS
298// ============================================================================
299// One constant variant set per ISA, one published pointer to the selected one;
300// ZXC_ONLY_DEFAULT binds _default at compile time.
301
310typedef struct {
311 zxc_decompress_func_t decompress;
312 zxc_decompress_func_t decompress_dict;
313 zxc_compress_func_t compress;
314} zxc_variant_set_t;
315
317#define ZXC_VARIANT_SET(sfx) \
318 static const zxc_variant_set_t zxc_variants##sfx = {zxc_decompress_chunk_wrapper##sfx, \
319 zxc_decompress_chunk_wrapper_dict##sfx, \
320 zxc_compress_chunk_wrapper##sfx}
321
322ZXC_VARIANT_SET(_default);
323#ifndef ZXC_ONLY_DEFAULT
324#if defined(__x86_64__) || defined(_M_X64)
325ZXC_VARIANT_SET(_avx2);
326ZXC_VARIANT_SET(_avx512);
327#elif defined(__arm__) || defined(_M_ARM)
328ZXC_VARIANT_SET(_neon32);
329#endif
330#endif
331#undef ZXC_VARIANT_SET
332
333#ifdef ZXC_ONLY_DEFAULT
335static ZXC_ALWAYS_INLINE const zxc_variant_set_t* zxc_variants(void) {
336 return &zxc_variants_default;
337}
338#else
344// LCOV_EXCL_START
345static const zxc_variant_set_t* zxc_select_variants(void) {
347#if defined(__x86_64__) || defined(_M_X64)
348 if (cpu == ZXC_CPU_AVX512) return &zxc_variants_avx512;
349 if (cpu == ZXC_CPU_AVX2) return &zxc_variants_avx2;
350#elif defined(__arm__) || defined(_M_ARM)
351 // 32-bit ARM: the only arch with a real runtime NEON probe (getauxval).
352 // cppcheck-suppress knownConditionTrueFalse
353 if (cpu == ZXC_CPU_NEON) return &zxc_variants_neon32;
354#else
355 (void)cpu;
356#endif
357 return &zxc_variants_default;
358}
359// LCOV_EXCL_STOP
360
362static const zxc_variant_set_t* ZXC_ATOMIC zxc_variants_ptr = NULL;
363
366// LCOV_EXCL_START
367static ZXC_COLD ZXC_NOINLINE const zxc_variant_set_t* zxc_variants_resolve(void) {
368 const zxc_variant_set_t* v = zxc_select_variants();
369#if ZXC_USE_C11_ATOMICS
370 atomic_store_explicit(&zxc_variants_ptr, v, memory_order_release);
371#else
373#endif
374 return v;
375}
376// LCOV_EXCL_STOP
377
379static ZXC_ALWAYS_INLINE const zxc_variant_set_t* zxc_variants(void) {
380#if ZXC_USE_C11_ATOMICS
381 const zxc_variant_set_t* v = atomic_load_explicit(&zxc_variants_ptr, memory_order_acquire);
382#else
383 const zxc_variant_set_t* v = zxc_variants_ptr;
384#endif
385 if (UNLIKELY(!v)) v = zxc_variants_resolve();
386 return v;
387}
388#endif // ZXC_ONLY_DEFAULT
389
390#ifdef ZXC_ONLY_DEFAULT
393 return zxc_decompress_chunk_wrapper_safe_default;
394}
395#else
398
400// LCOV_EXCL_START
402 const zxc_variant_set_t* v = zxc_variants();
403 zxc_decompress_func_t f = zxc_decompress_chunk_wrapper_safe_default;
404#if defined(__x86_64__) || defined(_M_X64)
405 if (v == &zxc_variants_avx512) f = zxc_decompress_chunk_wrapper_safe_avx512;
406 if (v == &zxc_variants_avx2) f = zxc_decompress_chunk_wrapper_safe_avx2;
407#elif defined(__arm__) || defined(_M_ARM)
408 if (v == &zxc_variants_neon32) f = zxc_decompress_chunk_wrapper_safe_neon32;
409#else
410 (void)v;
411#endif
412#if ZXC_USE_C11_ATOMICS
413 atomic_store_explicit(&zxc_decompress_safe_ptr, f, memory_order_release);
414#else
416#endif
417 return f;
418}
419// LCOV_EXCL_STOP
420
423#if ZXC_USE_C11_ATOMICS
424 zxc_decompress_func_t f = atomic_load_explicit(&zxc_decompress_safe_ptr, memory_order_acquire);
425#else
427#endif
428 if (UNLIKELY(!f)) f = zxc_decompress_safe_resolve();
429 return f;
430}
431#endif // ZXC_ONLY_DEFAULT
432
434int zxc_decompress_chunk_wrapper(const zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
435 const size_t src_sz, uint8_t* RESTRICT dst, const size_t dst_cap,
436 const uint64_t block_index) {
437 // dict_size is constant for a stream; this per-block branch (outside the decode
438 // loop) routes to the dict variant only when a dictionary is active, so the
439 // no-dict path runs the dict-free chunk wrapper (identical codegen to main).
440 const zxc_variant_set_t* v = zxc_variants();
441 return (ctx->dict_size ? v->decompress_dict : v->decompress)(ctx, src, src_sz, dst, dst_cap,
442 block_index);
443}
444
457 const uint8_t* RESTRICT src,
458 const size_t src_sz, uint8_t* RESTRICT dst,
459 const size_t dst_cap,
460 const uint64_t block_index) {
461 return zxc_decompress_safe_variant()(ctx, src, src_sz, dst, dst_cap, block_index);
462}
463
465int zxc_compress_chunk_wrapper(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
466 const size_t src_sz, uint8_t* RESTRICT dst, const size_t dst_cap,
467 const uint64_t block_index) {
468 return zxc_variants()->compress(ctx, src, src_sz, dst, dst_cap, block_index);
469}
470
471// ============================================================================
472// HUFFMAN TRAMPOLINES
473// ============================================================================
474// The Huffman codec is built per-variant (default / avx2 / avx512, plus neon32
475// on 32-bit ARM)
476// alongside zxc_compress.c and zxc_decompress.c, so the LZ77 stages and the
477// Huffman stage in a given variant share the same ISA flags (e.g. -mbmi2 on
478// the AVX2/AVX512 variants). The compress/decompress variant TUs resolve
479// their Huffman calls to the matching suffixed symbol at compile time, so
480// the production hot path has zero dispatch overhead.
481//
482// These thin wrappers exist only for tests and external callers that link
483// against the un-suffixed names. They forward to the default (scalar) variant.
490int zxc_huf_build_code_lengths(const uint32_t* RESTRICT freq, uint8_t* RESTRICT code_len,
491 void* RESTRICT scratch, const int max_code_len) {
492 return zxc_huf_build_code_lengths_default(freq, code_len, scratch, max_code_len);
493}
494
496size_t zxc_huf_calc_size(const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len,
497 const int with_header) {
498 return zxc_huf_calc_size_default(freq, code_len, with_header);
499}
500
501int zxc_huf_encode_section(const uint8_t* RESTRICT literals, const size_t n_literals,
502 const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len,
503 uint8_t* RESTRICT dst, const size_t dst_cap) {
504 return zxc_huf_encode_section_default(literals, n_literals, freq, code_len, dst, dst_cap);
505}
506
507int zxc_huf_decode_section(const uint8_t* RESTRICT payload, const size_t payload_size,
508 uint8_t* RESTRICT dst, const size_t n, uint8_t* RESTRICT scratch) {
509 return zxc_huf_decode_section_default(payload, payload_size, dst, n, scratch);
510}
511
512int zxc_huf_encode_section_dict(const uint8_t* RESTRICT literals, const size_t n_literals,
513 const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len,
514 const zxc_pivco_tree_t* RESTRICT tree,
515 const uint32_t* RESTRICT codes, uint8_t* RESTRICT dst,
516 const size_t dst_cap) {
517 return zxc_huf_encode_section_dict_default(literals, n_literals, freq, code_len, tree, codes,
518 dst, dst_cap);
519}
520
521int zxc_huf_decode_section_dict(const uint8_t* RESTRICT payload, const size_t payload_size,
522 uint8_t* RESTRICT dst, const size_t n,
523 const zxc_pivco_tree_t* RESTRICT tree, uint8_t* RESTRICT scratch) {
524 return zxc_huf_decode_section_dict_default(payload, payload_size, dst, n, tree, scratch);
525}
526
527size_t zxc_huf_calc_size_dict(const uint32_t* RESTRICT freq, const uint8_t* RESTRICT code_len,
528 const zxc_pivco_tree_t* RESTRICT tree) {
529 return zxc_huf_calc_size_dict_default(freq, code_len, tree);
530}
531
538void zxc_huf_pack_lengths(const uint8_t* RESTRICT code_len, uint8_t* RESTRICT out) {
539 zxc_huf_pack_lengths_default(code_len, out);
540}
541
548int zxc_huf_unpack_lengths(const uint8_t* RESTRICT in, uint8_t* RESTRICT code_len) {
549 return zxc_huf_unpack_lengths_default(in, code_len);
550}
551
552// ============================================================================
553// PUBLIC UTILITY API
554// ============================================================================
555// These wrapper functions provide a simplified interface by managing context
556// allocation and looping over blocks. They call the dispatched wrappers above.
557
567static int64_t zxc_write_empty_frame(uint8_t* RESTRICT dst, const size_t dst_capacity,
568 const size_t block_size, const int checksum_enabled,
569 const uint32_t did, const int seekable) {
570 const int h =
571 zxc_write_file_header(dst, dst_capacity, block_size, checksum_enabled, did, seekable);
572 if (UNLIKELY(h < 0)) return h;
573 size_t off = (size_t)h;
574
575 const zxc_block_header_t eof = {
576 .block_type = ZXC_BLOCK_EOF, .block_flags = 0, .reserved = 0, .comp_size = 0};
577 const int e = zxc_write_block_header(dst + off, dst_capacity - off, &eof);
578 if (UNLIKELY(e < 0)) return e;
579 off += (size_t)e;
580
581 // The flag promised a table: an empty one, a bare SEK header.
582 if (seekable) {
583 const int t = zxc_seek_table_header(dst + off, dst_capacity - off, 0);
584 if (UNLIKELY(t < 0)) return t;
585 off += (size_t)t;
586 }
587
588 const int f = zxc_write_file_footer(dst + off, dst_capacity - off, off, 0, 0, checksum_enabled);
589 if (UNLIKELY(f < 0)) return f;
590 return (int64_t)(off + (size_t)f);
591}
592
602static int64_t zxc_write_seek_table_from_blocks(uint8_t* RESTRICT dst, const size_t dst_capacity,
603 const uint8_t* RESTRICT blocks,
604 const uint8_t* const blocks_end,
605 const uint64_t num_blocks,
606 const int checksum_enabled) {
607 const size_t total = zxc_seek_table_size(num_blocks);
608 if (UNLIKELY(total == 0)) return ZXC_ERROR_OVERFLOW; // LCOV_EXCL_LINE
609 if (UNLIKELY(dst_capacity < total)) return ZXC_ERROR_DST_TOO_SMALL;
610
611 const int h = zxc_seek_table_header(dst, dst_capacity, num_blocks);
612 if (UNLIKELY(h < 0)) return h; // LCOV_EXCL_LINE
613 uint8_t* p = dst + h;
614
615 const size_t trailer = ZXC_BLOCK_HEADER_SIZE + (checksum_enabled ? ZXC_BLOCK_CHECKSUM_SIZE : 0);
616 uint64_t anchor = ZXC_FILE_HEADER_SIZE;
617 for (uint64_t i = 0; i < num_blocks; i++) {
618 if (i % ZXC_SEEK_GROUP == 0) {
619 zxc_store_le64(p, anchor);
621 }
622 const size_t left = (size_t)(blocks_end - blocks);
623 if (UNLIKELY(left < ZXC_BLOCK_HEADER_SIZE))
624 return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
625 const size_t size = (size_t)zxc_le32(blocks + 3) + trailer; // comp_size at offset 3
626 if (UNLIKELY(size > left)) return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
627 zxc_store_le32(p, (uint32_t)size);
629 anchor += size;
630 blocks += size;
631 }
632 if (UNLIKELY(blocks != blocks_end)) return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
633 return (int64_t)(p - dst);
634}
635
642// cppcheck-suppress unusedFunction
643int64_t zxc_compress(const void* RESTRICT src, const size_t src_size, void* RESTRICT dst,
644 const size_t dst_capacity, const zxc_compress_opts_t* opts) {
645 if (UNLIKELY(!dst || dst_capacity == 0 || (src_size > 0 && !src))) return ZXC_ERROR_NULL_INPUT;
646
647 const int checksum_enabled = opts ? opts->checksum_enabled : 0;
648 const int seekable = opts ? opts->seekable : 0;
649 const int level = ZXC_OPTS_LEVEL(opts, ZXC_LEVEL_DEFAULT);
650 const size_t block_size = ZXC_OPTS_BLOCK_SIZE(opts, ZXC_BLOCK_SIZE_DEFAULT);
651 const uint8_t* dict = opts ? (const uint8_t*)opts->dict : NULL;
652 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
653 const uint8_t* dict_huf = ZXC_OPTS_DICT_HUF(opts);
654
655 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
656 if (UNLIKELY(!zxc_validate_block_size(block_size))) return ZXC_ERROR_BAD_BLOCK_SIZE;
657
658 const uint32_t did = (dict && dict_size > 0) ? zxc_dict_id(dict, dict_size, dict_huf) : 0;
659
660 if (UNLIKELY(src_size == 0))
661 return zxc_write_empty_frame((uint8_t*)dst, dst_capacity, block_size, checksum_enabled, did,
662 seekable);
663
664 const uint8_t* ip = (const uint8_t*)src;
665 uint8_t* op = (uint8_t*)dst;
666 const uint8_t* op_start = op;
667 const uint8_t* op_end = op + dst_capacity;
668 zxc_cctx_t ctx;
669
670 const size_t eff_chunk =
671 dict_size > 0 ? zxc_block_size_ceil(dict_size + block_size) : block_size;
672 // LCOV_EXCL_START
673 if (UNLIKELY(zxc_cctx_init(&ctx, eff_chunk, 1, level, checksum_enabled, dict_size) != ZXC_OK))
674 return ZXC_ERROR_MEMORY;
675 // LCOV_EXCL_STOP
676 if (UNLIKELY(zxc_cctx_attach_dict_huf(&ctx, dict_huf) != ZXC_OK)) {
677 // LCOV_EXCL_START
678 zxc_cctx_free(&ctx);
680 // LCOV_EXCL_STOP
681 }
682
683 // Dict input buffer: [dict_content | block_data] for the encoder, carved
684 // into the cctx workspace (NULL when no dictionary is active).
685 uint8_t* const dict_input = ctx.dict_buffer;
686 if (dict_input) ZXC_MEMCPY(dict_input, dict, dict_size);
687
688 const int h_val = zxc_write_file_header(op, (size_t)(op_end - op), block_size, checksum_enabled,
689 did, seekable);
690 // LCOV_EXCL_START
691 if (UNLIKELY(h_val < 0)) {
692 zxc_cctx_free(&ctx);
693 return h_val;
694 }
695 // LCOV_EXCL_STOP
696 op += h_val;
697
698 // Seekable: one compressed size per block, at most block_count + 1 of them.
699 uint32_t* seek_comp = NULL;
700 uint64_t seek_count = 0;
701 if (seekable) {
702 const size_t block_count = src_size / block_size;
703 if (UNLIKELY(block_count + 2 > SIZE_MAX / sizeof(uint32_t))) {
704 // LCOV_EXCL_START
705 zxc_cctx_free(&ctx);
707 // LCOV_EXCL_STOP
708 }
709 seek_comp = (uint32_t*)ZXC_MALLOC((block_count + 2) * sizeof(uint32_t));
710 // LCOV_EXCL_START
711 if (UNLIKELY(!seek_comp)) {
712 zxc_cctx_free(&ctx);
713 return ZXC_ERROR_MEMORY;
714 }
715 // LCOV_EXCL_STOP
716 }
717
718 uint64_t digest = 0;
719 size_t pos = 0;
720 for (uint64_t bi = 0; pos < src_size; bi++) {
721 const size_t chunk_len = (src_size - pos > block_size) ? block_size : (src_size - pos);
722 const size_t rem_cap = (size_t)(op_end - op);
723
724 int res;
725 if (dict_input) {
726 ZXC_MEMCPY(dict_input + dict_size, ip + pos, chunk_len);
727 res = zxc_compress_chunk_wrapper(&ctx, dict_input, dict_size + chunk_len, op, rem_cap,
728 bi);
729 } else {
730 res = zxc_compress_chunk_wrapper(&ctx, ip + pos, chunk_len, op, rem_cap, bi);
731 }
732 if (UNLIKELY(res < 0)) {
733 ZXC_FREE(seek_comp);
734 zxc_cctx_free(&ctx);
735 return res;
736 }
737
738 // Seekable: record compressed block size
739 if (seekable) seek_comp[seek_count++] = (uint32_t)res;
740 if (checksum_enabled && LIKELY(res >= ZXC_BLOCK_CHECKSUM_SIZE))
741 digest = zxc_digest_combine(digest, zxc_le32(op + res - ZXC_BLOCK_CHECKSUM_SIZE));
742
743 op += res;
744 pos += chunk_len;
745 }
746
747 zxc_cctx_free(&ctx);
748
749 // Write EOF Block
750 const size_t rem_cap = (size_t)(op_end - op);
751 const zxc_block_header_t eof_bh = {
752 .block_type = ZXC_BLOCK_EOF, .block_flags = 0, .reserved = 0, .comp_size = 0};
753 const int eof_val = zxc_write_block_header(op, rem_cap, &eof_bh);
754 // LCOV_EXCL_START
755 if (UNLIKELY(eof_val < 0)) {
756 ZXC_FREE(seek_comp);
757 return eof_val;
758 }
759 // LCOV_EXCL_STOP
760 op += eof_val;
761
762 // Seekable: write seek table between EOF block and footer
763 if (seekable) {
764 const size_t st_cap = (size_t)(op_end - op);
765 const int64_t st_val = zxc_write_seek_table(op, st_cap, seek_comp, seek_count);
766 ZXC_FREE(seek_comp);
767 if (UNLIKELY(st_val < 0)) return st_val; // LCOV_EXCL_LINE
768 op += st_val;
769 }
770
771 const int footer_val = zxc_write_file_footer(
772 op, (size_t)(op_end - op), (uint64_t)(op - op_start), src_size, digest, checksum_enabled);
773 if (UNLIKELY(footer_val < 0)) return footer_val; // LCOV_EXCL_LINE
774 op += footer_val;
775
776 return (int64_t)(op - op_start);
777}
778
779// One-shot decode for zxc_decompress and the no-destination probe.
780static int64_t zxc_decompress_oneshot(const uint8_t* src, size_t src_size, uint8_t* dst,
781 size_t dst_capacity, const zxc_decompress_opts_t* opts);
782
783// The frame walk behind every buffer decode, in place when @p inplace is set.
784static int64_t zxc_dctx_decode_frame(zxc_dctx* dctx, const uint8_t* src, size_t src_size,
785 uint8_t* dst, size_t dst_capacity,
786 const zxc_decompress_opts_t* opts, int inplace,
787 size_t* frame_end);
788
789// The container walk: frames back to back.
790static int64_t zxc_dctx_decode_container(zxc_dctx* dctx, const uint8_t* src, size_t src_size,
791 uint8_t* dst, size_t dst_capacity,
792 const zxc_decompress_opts_t* opts);
793
806static int zxc_buffer_frame_info(const uint8_t* src, const size_t src_size, zxc_frame_info_t* info,
807 size_t* footer_len) {
808 if (UNLIKELY(!src)) return ZXC_ERROR_NULL_INPUT;
809 const zxc_scan_src_t scan = zxc_scan_src_mem(src, src_size);
810 return zxc_scan_whole_frame(&scan, info, footer_len);
811}
812
820static int64_t zxc_probe_without_dst(const uint8_t* RESTRICT src, const size_t src_size,
821 const zxc_decompress_opts_t* opts,
822 const zxc_container_info_t* scanned) {
823 // Only an empty archive succeeds without a buffer. @p scanned: the caller's walk, if any.
825 if (!scanned) {
826 const zxc_scan_src_t scan = zxc_scan_src_mem(src, src_size);
827 const int rc = zxc_scan_container(&scan, 0, &info);
828 if (UNLIKELY(rc != ZXC_OK)) return rc;
829 scanned = &info;
830 }
831 if (scanned->dsize != 0) return ZXC_ERROR_DST_TOO_SMALL;
832 uint8_t probe_dst[1];
833 return zxc_decompress_oneshot(src, src_size, probe_dst, 0, opts);
834}
835
842// cppcheck-suppress unusedFunction
843int64_t zxc_decompress(const void* RESTRICT src, const size_t src_size, void* RESTRICT dst,
844 const size_t dst_capacity, const zxc_decompress_opts_t* opts) {
845 if (UNLIKELY(!src || (!dst && dst_capacity != 0))) return ZXC_ERROR_NULL_INPUT;
847 if (UNLIKELY(src_size < ZXC_FRAME_MIN_SIZE))
848 return zxc_short_input_verdict((const uint8_t*)src, src_size);
849
850 if (UNLIKELY(!dst || dst_capacity == 0))
851 return zxc_probe_without_dst(src, src_size, opts, NULL);
852
853 return zxc_decompress_oneshot((const uint8_t*)src, src_size, (uint8_t*)dst, dst_capacity, opts);
854}
855
885static uint64_t zxc_inplace_margin(const uint64_t dsize, const size_t chunk_size, const int has_cs,
886 const int has_seek, const size_t footer_len) {
887 const uint64_t nblocks = zxc_seek_block_count(dsize, chunk_size);
888 const uint64_t per_block =
889 (uint64_t)ZXC_BLOCK_HEADER_SIZE + (has_cs ? (uint64_t)ZXC_BLOCK_CHECKSUM_SIZE : 0);
890 const uint64_t seek_table =
891 has_seek ? (uint64_t)ZXC_BLOCK_HEADER_SIZE + zxc_seek_table_bytes(nblocks) : 0;
892 const uint64_t trailing = (uint64_t)ZXC_BLOCK_HEADER_SIZE + // EOF block header
893 seek_table + (uint64_t)footer_len;
894 return (uint64_t)chunk_size + nblocks * per_block + trailing +
895 (uint64_t)ZXC_DECOMPRESS_TAIL_PAD;
896}
897
919static int zxc_inplace_probe(const uint8_t* comp, const size_t comp_size, uint64_t* dsize,
920 uint64_t* margin, uint64_t* floor) {
921 zxc_frame_info_t info;
922 size_t footer_len = 0;
923
924 // Own mapping: only a wrong magic word and a forged footer keep their code,
925 // everything else reads as a bad header.
926 const int rc = zxc_buffer_frame_info(comp, comp_size, &info, &footer_len);
927 if (UNLIKELY(rc != ZXC_OK))
928 return (rc == ZXC_ERROR_BAD_MAGIC || rc == ZXC_ERROR_CORRUPT_DATA) ? rc
930
931 *dsize = info.decompressed_size;
933 info.has_seek_table, footer_len);
934 *floor = (uint64_t)info.block_size + (uint64_t)ZXC_DECOMPRESS_TAIL_PAD;
935 return ZXC_OK;
936}
937
953// cppcheck-suppress unusedFunction
954size_t zxc_decompress_inplace_bound(const void* src, const size_t src_size) {
955 if (UNLIKELY(!src || src_size < ZXC_FRAME_MIN_SIZE)) return 0;
956 uint64_t dsize = 0;
957 uint64_t margin = 0;
958 uint64_t floor = 0;
959 if (UNLIKELY(zxc_inplace_probe((const uint8_t*)src, src_size, &dsize, &margin, &floor) !=
960 ZXC_OK))
961 return 0;
962 if (UNLIKELY(margin > (uint64_t)SIZE_MAX || dsize > (uint64_t)SIZE_MAX - margin)) return 0;
963 const uint64_t by_payload = dsize + margin;
964
965 if (UNLIKELY(floor > (uint64_t)SIZE_MAX - (uint64_t)src_size)) return 0;
966 const uint64_t by_placement = (uint64_t)src_size + floor;
967
968 return (size_t)(by_payload > by_placement ? by_payload : by_placement);
969}
970
984static int zxc_inplace_prepare(const void* buffer, const size_t buffer_capacity,
985 const size_t comp_size, const zxc_decompress_opts_t* opts,
986 const uint8_t** comp) {
987 if (UNLIKELY(!buffer || comp_size < ZXC_FRAME_MIN_SIZE || comp_size > buffer_capacity))
989 const uint8_t* const c = (const uint8_t*)buffer + (buffer_capacity - comp_size);
990 uint64_t dsize = 0;
991 uint64_t margin = 0;
992 uint64_t floor = 0;
993 const int rc = zxc_inplace_probe(c, comp_size, &dsize, &margin, &floor);
994 if (UNLIKELY(rc != ZXC_OK)) return rc;
995 if (UNLIKELY(dsize > (uint64_t)buffer_capacity || (uint64_t)buffer_capacity - dsize < margin))
997 /* The check above sizes the buffer against the payload, this one against the
998 * archive where it lies: block 0 starts a full block clear. */
999 if (UNLIKELY((uint64_t)(buffer_capacity - comp_size) < floor)) return ZXC_ERROR_DST_TOO_SMALL;
1001 *comp = c;
1002 return ZXC_OK;
1003}
1004
1012// cppcheck-suppress unusedFunction
1013int64_t zxc_decompress_inplace(void* buffer, const size_t buffer_capacity, const size_t comp_size,
1014 const zxc_decompress_opts_t* opts) {
1015 const uint8_t* comp = NULL;
1016 const int rc = zxc_inplace_prepare(buffer, buffer_capacity, comp_size, opts, &comp);
1017 if (UNLIKELY(rc != ZXC_OK)) return rc;
1018 zxc_dctx* const dctx = zxc_create_dctx();
1019 if (UNLIKELY(!dctx)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
1020 const int64_t res = zxc_dctx_decode_frame(dctx, comp, comp_size, (uint8_t*)buffer,
1021 buffer_capacity, opts, 1, NULL);
1022 zxc_free_dctx(dctx);
1023 return res;
1024}
1025
1032uint64_t zxc_get_decompressed_size(const void* src, const size_t src_size) {
1033 if (UNLIKELY(!src)) return 0;
1034 const zxc_scan_src_t scan = zxc_scan_src_mem((const uint8_t*)src, src_size);
1036 if (UNLIKELY(zxc_scan_container(&scan, 0, &info) != ZXC_OK)) return 0;
1037 return info.dsize;
1038}
1039
1046// cppcheck-suppress unusedFunction
1047uint32_t zxc_get_dict_id(const void* src, const size_t src_size) {
1048 if (UNLIKELY(!src || src_size < ZXC_FILE_HEADER_SIZE)) return 0;
1049
1050 const uint8_t* const p = (const uint8_t*)src;
1051 if (UNLIKELY(zxc_le32(p) != ZXC_MAGIC_WORD)) return 0;
1052
1053 return (p[6] & ZXC_FILE_FLAG_HAS_DICTIONARY) ? zxc_le32(p + 7) : 0;
1054}
1055
1061// cppcheck-suppress unusedFunction
1062int zxc_get_frame_info(const void* src, const size_t src_size, zxc_frame_info_t* info,
1063 const size_t info_size) {
1064 if (UNLIKELY(!info)) return ZXC_ERROR_NULL_INPUT;
1065 zxc_frame_info_t got;
1066 const int rc = zxc_buffer_frame_info((const uint8_t*)src, src_size, &got, NULL);
1067 if (rc == ZXC_OK) zxc_frame_info_copy(info, info_size, &got);
1068 return rc;
1069}
1070
1076// cppcheck-suppress unusedFunction
1077int zxc_get_last_frame_info(const void* src, const size_t src_size, zxc_frame_info_t* info,
1078 const size_t info_size) {
1079 if (UNLIKELY(!src || !info)) return ZXC_ERROR_NULL_INPUT;
1080 const zxc_scan_src_t scan = zxc_scan_src_mem((const uint8_t*)src, src_size);
1081 zxc_frame_info_t got;
1082 uint64_t start = 0;
1083 const int rc = zxc_scan_frame(&scan, src_size, &start, &got, NULL);
1084 if (rc == ZXC_OK) zxc_frame_info_copy(info, info_size, &got);
1085 return rc;
1086}
1087
1088// ============================================================================
1089// REUSABLE CONTEXT API (Opaque)
1090// ============================================================================
1091//
1092// Provides heap-allocated, opaque contexts that integrators can reuse across
1093// multiple compress / decompress calls, eliminating per-call malloc/free
1094// overhead.
1095
1096// --- Compression ---------------------------------------------------------
1097
1105struct zxc_cctx_s {
1106 zxc_cctx_t inner; /* existing internal context */
1107 int initialized; /* 1 if inner has live allocations */
1108 int owns_workspace; /* 0 = library-allocated (free in zxc_free_cctx),
1109 1 = caller-supplied static workspace (no-op free,
1110 block_size pinned at init) */
1111 size_t last_block_size; /* block size used for last init */
1112 // Sticky options (remembered from create or last compress call).
1113 int stored_level;
1114 int stored_checksum;
1115 int stored_seekable;
1116 size_t stored_block_size;
1117 int huf_cached; /* inner carries the table below */
1118 uint8_t huf_cache[ZXC_HUF_TABLE_SIZE]; /* last table attached */
1119};
1120
1128static int zxc_ctx_sync_dict_huf(zxc_cctx_t* RESTRICT inner, uint8_t* RESTRICT cache,
1129 int* RESTRICT cached, const uint8_t* RESTRICT dict_huf) {
1130 if (dict_huf) {
1131 if (!*cached || memcmp(cache, dict_huf, ZXC_HUF_TABLE_SIZE) != 0) {
1132 *cached = 0; /* a failed attach leaves inner without a tree */
1133 if (UNLIKELY(zxc_cctx_attach_dict_huf(inner, dict_huf) != ZXC_OK))
1135 ZXC_MEMCPY(cache, dict_huf, ZXC_HUF_TABLE_SIZE);
1136 *cached = 1;
1137 }
1138 } else if (*cached) {
1139 (void)zxc_cctx_attach_dict_huf(inner, NULL);
1140 *cached = 0;
1141 }
1142 return ZXC_OK;
1143}
1144
1146 zxc_cctx* const cctx = (zxc_cctx*)ZXC_CALLOC(1, sizeof(zxc_cctx));
1147 if (UNLIKELY(!cctx)) return NULL; // LCOV_EXCL_LINE
1148
1149 // Resolve and store sticky defaults.
1150 cctx->stored_level = ZXC_OPTS_LEVEL(opts, ZXC_LEVEL_DEFAULT);
1151 cctx->stored_block_size = ZXC_OPTS_BLOCK_SIZE(opts, ZXC_BLOCK_SIZE_DEFAULT);
1152 cctx->stored_checksum = opts ? opts->checksum_enabled : 0;
1153 cctx->stored_seekable = opts ? opts->seekable : 0;
1154
1155 if (opts) {
1156 // LCOV_EXCL_START
1157 if (UNLIKELY(!zxc_validate_block_size(cctx->stored_block_size) ||
1158 zxc_cctx_init(&cctx->inner, cctx->stored_block_size, 1, cctx->stored_level,
1159 cctx->stored_checksum, 0) != ZXC_OK)) {
1160 ZXC_FREE(cctx);
1161 return NULL;
1162 }
1163 // LCOV_EXCL_STOP
1164 cctx->last_block_size = cctx->stored_block_size;
1165 cctx->initialized = 1;
1166 }
1167
1168 return cctx;
1169}
1170
1179 if (UNLIKELY(!cctx)) return;
1180 // Static cctx: handle + inner buffers live inside the caller's workspace,
1181 // which we do not own. Free is a no-op; the caller owns the workspace.
1182 if (cctx->owns_workspace) return;
1183 if (cctx->initialized) zxc_cctx_free(&cctx->inner);
1184 ZXC_FREE(cctx);
1185}
1186
1195int64_t zxc_compress_cctx(zxc_cctx* cctx, const void* RESTRICT src, const size_t src_size,
1196 void* RESTRICT dst, const size_t dst_capacity,
1197 const zxc_compress_opts_t* opts) {
1198 if (UNLIKELY(!cctx)) return ZXC_ERROR_NULL_INPUT;
1199 if (UNLIKELY(!dst || dst_capacity == 0 || (src_size > 0 && !src))) return ZXC_ERROR_NULL_INPUT;
1200
1201 const int checksum_enabled = opts ? opts->checksum_enabled : cctx->stored_checksum;
1202 const int seekable = opts ? opts->seekable : cctx->stored_seekable;
1203 const int level = ZXC_OPTS_LEVEL(opts, cctx->stored_level);
1204 const size_t block_size = ZXC_OPTS_BLOCK_SIZE(opts, cctx->stored_block_size);
1205 // Dictionary options are never sticky: a remembered pointer would dangle.
1206 const uint8_t* dict = opts ? (const uint8_t*)opts->dict : NULL;
1207 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
1208 const uint8_t* dict_huf = ZXC_OPTS_DICT_HUF(opts);
1209
1210 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
1211 if (UNLIKELY(!zxc_validate_block_size(block_size))) return ZXC_ERROR_BAD_BLOCK_SIZE;
1212
1213 // Static cctx: no room for a dictionary prefix, and the workspace cannot
1214 // grow, so reject a block_size change or a level raise into the
1215 // optimal-parser tier it was carved without.
1216 if (UNLIKELY(cctx->owns_workspace && dict_size > 0)) return ZXC_ERROR_DICT_UNSUPPORTED;
1217 if (UNLIKELY(cctx->owns_workspace && block_size != cctx->last_block_size))
1219 if (UNLIKELY(cctx->owns_workspace && level >= ZXC_LEVEL_DENSITY && !cctx->inner.opt_scratch))
1220 return ZXC_ERROR_BAD_LEVEL;
1221
1222 cctx->stored_level = level;
1223 cctx->stored_block_size = block_size;
1224 cctx->stored_checksum = checksum_enabled;
1225 cctx->stored_seekable = seekable;
1226
1227 // The encoder sees [dict | block], so the carved chunk covers both.
1228 const size_t eff_chunk =
1229 dict_size > 0 ? zxc_block_size_ceil(dict_size + block_size) : block_size;
1230 const uint32_t did = (dict && dict_size > 0) ? zxc_dict_id(dict, dict_size, dict_huf) : 0;
1231
1232 // The sticky settings above are already stored, so an empty source is done:
1233 // same writer as the one-shot, and the workspace is left alone.
1234 if (UNLIKELY(src_size == 0))
1235 return zxc_write_empty_frame((uint8_t*)dst, dst_capacity, block_size, checksum_enabled, did,
1236 seekable);
1237
1238 // Re-init when the chunk changed, a level raise needs the optimal-parser
1239 // scratch, or a dictionary arrives on a context carved without its prefix.
1240 if (UNLIKELY(!cctx->initialized || cctx->last_block_size != eff_chunk ||
1241 (level >= ZXC_LEVEL_DENSITY && !cctx->inner.opt_scratch) ||
1242 (dict_size > 0 && !cctx->inner.dict_buffer))) {
1243 if (cctx->initialized) {
1244 zxc_cctx_free(&cctx->inner);
1245 cctx->initialized = 0;
1246 }
1247 // LCOV_EXCL_START
1248 if (UNLIKELY(zxc_cctx_init(&cctx->inner, eff_chunk, 1, level, checksum_enabled,
1249 dict_size) != ZXC_OK))
1250 return ZXC_ERROR_MEMORY;
1251 // LCOV_EXCL_STOP
1252 cctx->last_block_size = eff_chunk;
1253 cctx->initialized = 1;
1254 cctx->huf_cached = 0;
1255 } else {
1256 // Same chunk: update level + checksum without realloc.
1257 cctx->inner.compression_level = level;
1258 cctx->inner.checksum_enabled = checksum_enabled;
1259 }
1260
1261 zxc_cctx_t* const ctx = &cctx->inner;
1262 ctx->dict_size = dict_size;
1263 if (UNLIKELY(zxc_ctx_sync_dict_huf(ctx, cctx->huf_cache, &cctx->huf_cached, dict_huf) !=
1264 ZXC_OK))
1266
1267 // [dict | block] input for the encoder, NULL without a dictionary.
1268 uint8_t* const dict_input = dict_size > 0 ? ctx->dict_buffer : NULL;
1269 if (dict_input) ZXC_MEMCPY(dict_input, dict, dict_size);
1270
1271 uint8_t* op = (uint8_t*)dst;
1272 const uint8_t* const op_start = op;
1273 const uint8_t* const op_end = op + dst_capacity;
1274 const uint8_t* const ip = (const uint8_t*)src;
1275
1276 const int h_val = zxc_write_file_header(op, (size_t)(op_end - op), block_size, checksum_enabled,
1277 did, seekable);
1278 if (UNLIKELY(h_val < 0)) return h_val; // LCOV_EXCL_LINE
1279 op += h_val;
1280
1281 uint64_t digest = 0;
1282 size_t pos = 0;
1283 uint64_t bi = 0;
1284 for (; pos < src_size; bi++) {
1285 const size_t chunk_len = (src_size - pos > block_size) ? block_size : (src_size - pos);
1286 const size_t rem_cap = (size_t)(op_end - op);
1287
1288 int res;
1289 if (dict_input) {
1290 ZXC_MEMCPY(dict_input + dict_size, ip + pos, chunk_len);
1291 res =
1292 zxc_compress_chunk_wrapper(ctx, dict_input, dict_size + chunk_len, op, rem_cap, bi);
1293 } else {
1294 res = zxc_compress_chunk_wrapper(ctx, ip + pos, chunk_len, op, rem_cap, bi);
1295 }
1296 if (UNLIKELY(res < 0)) return res;
1297
1298 if (checksum_enabled && LIKELY(res >= ZXC_BLOCK_CHECKSUM_SIZE))
1299 digest = zxc_digest_combine(digest, zxc_le32(op + res - ZXC_BLOCK_CHECKSUM_SIZE));
1300
1301 op += res;
1302 pos += chunk_len;
1303 }
1304 // EOF block
1305 uint8_t* const eof_at = op;
1306 const size_t rem_cap = (size_t)(op_end - op);
1307 const zxc_block_header_t eof_bh = {
1308 .block_type = ZXC_BLOCK_EOF, .block_flags = 0, .reserved = 0, .comp_size = 0};
1309 const int eof_val = zxc_write_block_header(op, rem_cap, &eof_bh);
1310 if (UNLIKELY(eof_val < 0)) return eof_val; // LCOV_EXCL_LINE
1311 op += eof_val;
1312
1313 // Seek table between the EOF block and the footer.
1314 if (seekable) {
1315 const int64_t st_val = zxc_write_seek_table_from_blocks(
1316 op, (size_t)(op_end - op), op_start + h_val, eof_at, bi, checksum_enabled);
1317 if (UNLIKELY(st_val < 0)) return st_val;
1318 op += st_val;
1319 }
1320
1321 const int footer_val = zxc_write_file_footer(
1322 op, (size_t)(op_end - op), (uint64_t)(op - op_start), src_size, digest, checksum_enabled);
1323 if (UNLIKELY(footer_val < 0)) return footer_val;
1324 op += footer_val;
1325
1326 return (int64_t)(op - op_start);
1327}
1328
1329// --- Decompression -------------------------------------------------------
1330
1337struct zxc_dctx_s {
1338 zxc_cctx_t inner; /* reuses the same internal context type */
1339 size_t last_block_size; /* block size from last header parse */
1340 size_t last_dict_size; /* dict_size the inner buffer was carved for (drives re-init) */
1341 int initialized; /* 1 if inner has live allocations */
1342 int owns_workspace; /* 0 = library-allocated (free in zxc_free_dctx),
1343 1 = caller-supplied static workspace (no-op free,
1344 block_size pinned at init) */
1345 int huf_cached; /* inner carries the table below */
1346 uint8_t huf_cache[ZXC_HUF_TABLE_SIZE]; /* last table attached */
1347};
1348
1350 zxc_dctx* const dctx = (zxc_dctx*)ZXC_CALLOC(1, sizeof(zxc_dctx));
1351 return dctx;
1352}
1353
1361 if (UNLIKELY(!dctx)) return;
1362 // Static dctx: handle + inner buffers live inside the caller's workspace,
1363 // which we do not own. Free is a no-op; the caller owns the workspace.
1364 if (dctx->owns_workspace) return;
1365 if (dctx->initialized) zxc_cctx_free(&dctx->inner);
1366 ZXC_FREE(dctx);
1367}
1368
1377static int64_t zxc_dctx_probe(const zxc_dctx* dctx, const uint8_t* RESTRICT src,
1378 const size_t src_size, const zxc_decompress_opts_t* opts) {
1379 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
1380 const zxc_scan_src_t scan = zxc_scan_src_mem(src, src_size);
1382 const int rc =
1383 zxc_scan_container(&scan, dctx->owns_workspace ? dctx->last_block_size : 0, &info);
1384 if (UNLIKELY(rc != ZXC_OK)) return rc;
1385 if (UNLIKELY(dctx->owns_workspace && (info.uses_dict || dict_size != 0)))
1387 return zxc_probe_without_dst(src, src_size, opts, &info);
1388}
1389
1397int64_t zxc_decompress_dctx(zxc_dctx* dctx, const void* RESTRICT src, const size_t src_size,
1398 void* RESTRICT dst, const size_t dst_capacity,
1399 const zxc_decompress_opts_t* opts) {
1400 if (UNLIKELY(!dctx || !src || (!dst && dst_capacity != 0))) return ZXC_ERROR_NULL_INPUT;
1402 if (UNLIKELY(src_size < ZXC_FRAME_MIN_SIZE))
1403 return zxc_short_input_verdict((const uint8_t*)src, src_size);
1404 if (UNLIKELY(!dst || dst_capacity == 0)) return zxc_dctx_probe(dctx, src, src_size, opts);
1405 return zxc_dctx_decode_container(dctx, (const uint8_t*)src, src_size, (uint8_t*)dst,
1406 dst_capacity, opts);
1407}
1408
1417int64_t zxc_decompress_inplace_dctx(zxc_dctx* dctx, void* buffer, const size_t buffer_capacity,
1418 const size_t comp_size, const zxc_decompress_opts_t* opts) {
1419 if (UNLIKELY(!dctx)) return ZXC_ERROR_NULL_INPUT;
1420 const uint8_t* comp = NULL;
1421 const int rc = zxc_inplace_prepare(buffer, buffer_capacity, comp_size, opts, &comp);
1422 if (UNLIKELY(rc != ZXC_OK)) return rc;
1423 return zxc_dctx_decode_frame(dctx, comp, comp_size, (uint8_t*)buffer, buffer_capacity, opts, 1,
1424 NULL);
1425}
1426
1434static int zxc_dctx_carve(zxc_dctx* dctx, const size_t chunk_size, const size_t dict_size,
1435 const int checksum, const uint8_t* dict_huf) {
1436 if (UNLIKELY(!dctx->initialized || dctx->last_block_size != chunk_size ||
1437 dctx->last_dict_size != dict_size)) {
1438 if (dctx->initialized) {
1439 zxc_cctx_free(&dctx->inner);
1440 dctx->initialized = 0;
1441 }
1442 // LCOV_EXCL_START
1443 if (UNLIKELY(zxc_cctx_init(&dctx->inner, chunk_size, 0, 0, checksum, dict_size) != ZXC_OK))
1444 return ZXC_ERROR_MEMORY;
1445 // LCOV_EXCL_STOP
1446 dctx->last_block_size = chunk_size;
1447 dctx->last_dict_size = dict_size;
1448 dctx->initialized = 1;
1449 dctx->huf_cached = 0;
1450 } else {
1451 dctx->inner.checksum_enabled = checksum;
1452 }
1453 if (UNLIKELY(zxc_ctx_sync_dict_huf(&dctx->inner, dctx->huf_cache, &dctx->huf_cached,
1454 dict_huf) != ZXC_OK))
1456 return ZXC_OK;
1457}
1458
1468static int64_t zxc_dctx_decode_frame(zxc_dctx* dctx, const uint8_t* src, const size_t src_size,
1469 uint8_t* dst, const size_t dst_capacity,
1470 const zxc_decompress_opts_t* opts, const int inplace,
1471 size_t* frame_end) {
1472 const int checksum_enabled = opts ? opts->checksum_enabled : 0;
1473 const uint8_t* dict = opts ? (const uint8_t*)opts->dict : NULL;
1474 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
1475 const uint8_t* dict_huf = ZXC_OPTS_DICT_HUF(opts);
1476
1477 const uint8_t* ip = (const uint8_t*)src;
1478 const uint8_t* const ip_end = ip + src_size;
1479 uint8_t* op = (uint8_t*)dst;
1480 const uint8_t* const op_start = op;
1481 const uint8_t* const op_end = op + dst_capacity;
1482 size_t runtime_chunk_size = 0;
1483 int file_has_checksums = 0;
1484 int file_has_seek = 0;
1485 uint32_t header_dict_id = 0;
1486
1487 const int hrc = zxc_read_file_header(ip, src_size, &runtime_chunk_size, &file_has_checksums,
1488 &header_dict_id, &file_has_seek);
1489 if (UNLIKELY(hrc != ZXC_OK)) return hrc;
1490
1491 // Static dctx: block_size is locked at workspace init; reject any
1492 // archive whose declared block_size would require a re-partition.
1493 if (UNLIKELY(dctx->owns_workspace && runtime_chunk_size != dctx->last_block_size))
1495 // Static dctx: no room for a dictionary prefix.
1496 if (UNLIKELY(dctx->owns_workspace && (header_dict_id != 0 || dict_size != 0)))
1498
1499 // Dictionary binding: same contract as zxc_decompress().
1500 if (header_dict_id != 0) {
1501 if (UNLIKELY(!dict || dict_size == 0)) return ZXC_ERROR_DICT_REQUIRED;
1502 if (UNLIKELY(zxc_dict_id(dict, dict_size, dict_huf) != header_dict_id))
1504 }
1505
1507
1508 // work_buf is sized to this by the carve below.
1509 const size_t work_sz = runtime_chunk_size + ZXC_DECOMPRESS_TAIL_PAD;
1510 zxc_cctx_t* const ctx = &dctx->inner;
1511
1512 // Carved on the first block that needs it: an archive storing nothing, and a
1513 // no-destination probe of one, never allocate.
1514 int ctx_ready = 0;
1515 // [dict | decode + PAD] scratch, NULL without a dictionary.
1516 uint8_t* dict_dec = NULL;
1517
1518 uint64_t block_index = 0;
1519 uint64_t digest = 0;
1520 for (;;) {
1521 // Only the EOF block may end the walk.
1522 if (UNLIKELY(ip >= ip_end)) return ZXC_ERROR_CORRUPT_DATA;
1523 const size_t rem_src = (size_t)(ip_end - ip);
1525 if (UNLIKELY(zxc_read_block_header(ip, rem_src, &bh) != ZXC_OK))
1526 return ZXC_ERROR_BAD_HEADER;
1527
1528 if (UNLIKELY(bh.block_type == ZXC_BLOCK_EOF)) {
1529 if (UNLIKELY(zxc_check_eof_header(ip) != ZXC_OK)) return ZXC_ERROR_BAD_HEADER;
1530
1531 // After the EOF block: the SEK block when announced, then the footer
1532 // the frame implies. Another frame may follow it.
1533 const uint64_t total_out = (uint64_t)(op - op_start);
1534 size_t pos = (size_t)(ip - src) + ZXC_BLOCK_HEADER_SIZE;
1535 if (file_has_seek) {
1536 uint64_t sek_bytes = 0;
1537 const int src_rc = zxc_check_seek_header(src + pos, src_size - pos, total_out,
1538 runtime_chunk_size, &sek_bytes);
1539 if (UNLIKELY(src_rc != ZXC_OK)) return src_rc;
1540 pos += ZXC_BLOCK_HEADER_SIZE;
1541 if (UNLIKELY(sek_bytes > (uint64_t)(src_size - pos)))
1543 pos += (size_t)sek_bytes;
1544 }
1545 const zxc_footer_layout_t fl = zxc_footer_layout(pos, total_out, file_has_checksums);
1546 uint64_t stored_digest = 0;
1547 const int frc =
1548 zxc_check_file_footer(src + pos, src_size - pos, &fl, total_out, &stored_digest);
1549 if (UNLIKELY(frc != ZXC_OK)) return frc;
1550 if (checksum_enabled && file_has_checksums && UNLIKELY(stored_digest != digest))
1552 pos += fl.len;
1553 // In place, the archive is one frame flush against the buffer's end.
1554 if (frame_end)
1555 *frame_end = pos;
1556 else if (UNLIKELY(pos != src_size))
1558 break; // EOF reached, stop decoding
1559 }
1560
1561 // The decoder only requires the payload, the step also covers the
1562 // checksum.
1563 const size_t advance = ZXC_BLOCK_HEADER_SIZE + bh.comp_size +
1564 (file_has_checksums ? ZXC_BLOCK_CHECKSUM_SIZE : 0);
1565 if (UNLIKELY(advance > rem_src)) return ZXC_ERROR_SRC_TOO_SMALL;
1566
1567 if (UNLIKELY(!ctx_ready)) {
1568 const int crc = zxc_dctx_carve(dctx, runtime_chunk_size, dict_size,
1569 file_has_checksums && checksum_enabled, dict_huf);
1570 if (UNLIKELY(crc != ZXC_OK)) return crc;
1571 dict_dec = ctx->dict_buffer;
1572 if (dict_dec) ZXC_MEMCPY(dict_dec, dict, dict_size);
1573 ctx_ready = 1;
1574 }
1575
1576 // The block's room: the rest of dst, in place cut at the unread input. The
1577 // fast decoder never writes past its capacity; a block that does not fit
1578 // decodes aside and copies only its real output.
1579 const size_t rem_cap = (size_t)(op_end - op);
1580 size_t room = rem_cap;
1581 if (inplace) {
1582 const size_t ahead = ip > op ? (size_t)(ip - op) : 0;
1583 if (ahead < room) room = ahead;
1584 }
1585 int res;
1586 const uint8_t* bounce = NULL;
1587 if (dict_dec) {
1588 // Decode behind the prefix so back-references into it resolve.
1589 res = zxc_decompress_chunk_wrapper(ctx, ip, rem_src, dict_dec + dict_size, work_sz,
1590 block_index);
1591 bounce = dict_dec + dict_size;
1592 } else if (LIKELY(room >= work_sz)) {
1593 res = zxc_decompress_chunk_wrapper(ctx, ip, rem_src, op, work_sz, block_index);
1594 } else {
1595 res = zxc_decompress_chunk_wrapper(ctx, ip, rem_src, ctx->work_buf, ctx->work_buf_cap,
1596 block_index);
1597 bounce = ctx->work_buf;
1598 }
1599 if (bounce && LIKELY(res > 0)) {
1600 // A no-destination probe lands here too. In place, output past the
1601 // room means padding or forged sizes.
1602 if (UNLIKELY((size_t)res > room))
1603 return (size_t)res > rem_cap ? ZXC_ERROR_DST_TOO_SMALL : ZXC_ERROR_CORRUPT_DATA;
1604 ZXC_MEMCPY(op, bounce, (size_t)res);
1605 }
1606 if (UNLIKELY(res < 0)) return res;
1607
1608 if (checksum_enabled && file_has_checksums)
1609 digest =
1611
1612 ip += advance;
1613 op += res;
1614 block_index++;
1615 }
1616
1617 return (int64_t)(op - op_start);
1618}
1619
1621static int64_t zxc_dctx_decode_container(zxc_dctx* dctx, const uint8_t* src, const size_t src_size,
1622 uint8_t* dst, const size_t dst_capacity,
1623 const zxc_decompress_opts_t* opts) {
1624 const zxc_scan_src_t scan = zxc_scan_src_mem(src, src_size);
1625 uint64_t at = 0;
1626 size_t out = 0;
1627 int rc;
1628 while ((rc = zxc_container_next(&scan, at)) == 1) {
1629 size_t frame_size = 0;
1630 const int64_t res = zxc_dctx_decode_frame(dctx, src + at, src_size - (size_t)at, dst + out,
1631 dst_capacity - out, opts, 0, &frame_size);
1632 if (UNLIKELY(res < 0)) return res;
1633 out += (size_t)res;
1634 at += frame_size;
1635 }
1636 if (UNLIKELY(rc < 0)) return rc;
1637 if (UNLIKELY(at == 0)) return ZXC_ERROR_SRC_TOO_SMALL;
1638 return (int64_t)out;
1639}
1640
1641// One-shot decode: the same walk, on a context that lives for the call.
1642static int64_t zxc_decompress_oneshot(const uint8_t* src, const size_t src_size, uint8_t* dst,
1643 const size_t dst_capacity,
1644 const zxc_decompress_opts_t* opts) {
1645 zxc_dctx dctx;
1646 ZXC_MEMSET(&dctx, 0, sizeof(dctx));
1647 const int64_t res = zxc_dctx_decode_container(&dctx, src, src_size, dst, dst_capacity, opts);
1648 if (dctx.initialized) zxc_cctx_free(&dctx.inner);
1649 return res;
1650}
1651
1652// =========================================================================
1653// Block-Level API (no file framing)
1654// =========================================================================
1655
1668int64_t zxc_compress_block(zxc_cctx* cctx, const void* RESTRICT src, const size_t src_size,
1669 void* RESTRICT dst, const size_t dst_capacity,
1670 const zxc_compress_opts_t* opts) {
1671 return zxc_compress_block_at(cctx, src, src_size, dst, dst_capacity, opts, 0);
1672}
1673
1674int64_t zxc_compress_block_at(zxc_cctx* cctx, const void* RESTRICT src, const size_t src_size,
1675 void* RESTRICT dst, const size_t dst_capacity,
1676 const zxc_compress_opts_t* opts, const uint64_t block_index) {
1677 if (UNLIKELY(!cctx || !src || !dst || src_size == 0 || dst_capacity == 0))
1678 return ZXC_ERROR_NULL_INPUT;
1679
1680 // Block API processes a single format-conformant block: src_size must not
1681 // exceed ZXC_BLOCK_SIZE_MAX. Callers with larger inputs should use the
1682 // frame or streaming APIs which chunk transparently.
1683 if (UNLIKELY(src_size > ZXC_BLOCK_SIZE_MAX)) return ZXC_ERROR_BAD_BLOCK_SIZE;
1684
1685 const int checksum_enabled = opts ? opts->checksum_enabled : cctx->stored_checksum;
1686 const int level = ZXC_OPTS_LEVEL(opts, cctx->stored_level);
1687 // For block API, block_size == src_size (the caller compresses one block at a time).
1688 const size_t block_size = ZXC_OPTS_BLOCK_SIZE(opts, cctx->stored_block_size);
1689 const size_t min_bs = zxc_block_size_ceil(src_size);
1690
1691 // Always ensure internal buffers can hold src_size.
1692 // When a dictionary is active, offset_bits must accommodate dict + block.
1693 const uint8_t* b_dict = opts ? (const uint8_t*)opts->dict : NULL;
1694 const size_t b_dict_size = ZXC_OPTS_DICT_SIZE(opts);
1695 if (UNLIKELY(b_dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
1696 const size_t base_block_size = (block_size > min_bs) ? block_size : min_bs;
1697 const size_t effective_block_size =
1698 b_dict_size > 0 ? zxc_block_size_ceil(b_dict_size + base_block_size) : base_block_size;
1699
1700 // Static cctx: the workspace cannot grow, so reject anything forcing a
1701 // re-partition - another block size, or a level raise into the
1702 // optimal-parser tier it carries no opt_scratch for. Re-initing on the heap
1703 // would break the no-allocation contract and leak: zxc_free_cctx is a no-op
1704 // for static contexts.
1705 if (UNLIKELY(cctx->owns_workspace && b_dict_size > 0)) return ZXC_ERROR_DICT_UNSUPPORTED;
1706 if (UNLIKELY(cctx->owns_workspace && effective_block_size != cctx->last_block_size))
1707 return ZXC_ERROR_BAD_BLOCK_SIZE; // LCOV_EXCL_LINE
1708 if (UNLIKELY(cctx->owns_workspace && level >= ZXC_LEVEL_DENSITY && !cctx->inner.opt_scratch))
1709 return ZXC_ERROR_BAD_LEVEL;
1710
1711 cctx->stored_level = level;
1712 cctx->stored_block_size = base_block_size;
1713 cctx->stored_checksum = checksum_enabled;
1714
1715 // Re-init when block_size changed, a level raise needs the optimal-parser
1716 // scratch, or a dictionary arrives on a context carved without its prefix
1717 // (a block_size switch can round [dict | block] back to the same size).
1718 if (UNLIKELY(!cctx->initialized || cctx->last_block_size != effective_block_size ||
1719 (level >= ZXC_LEVEL_DENSITY && !cctx->inner.opt_scratch) ||
1720 (b_dict_size > 0 && !cctx->inner.dict_buffer))) {
1721 if (cctx->initialized) {
1722 // LCOV_EXCL_START
1723 zxc_cctx_free(&cctx->inner);
1724 cctx->initialized = 0;
1725 // LCOV_EXCL_STOP
1726 }
1727 // LCOV_EXCL_START
1728 if (UNLIKELY(zxc_cctx_init(&cctx->inner, effective_block_size, 1, level, checksum_enabled,
1729 b_dict_size) != ZXC_OK))
1730 return ZXC_ERROR_MEMORY;
1731 // LCOV_EXCL_STOP
1732 cctx->last_block_size = effective_block_size;
1733 cctx->initialized = 1;
1734 cctx->huf_cached = 0;
1735 } else {
1736 cctx->inner.compression_level = level;
1737 cctx->inner.checksum_enabled = checksum_enabled;
1738 }
1739
1740 cctx->inner.dict_size = b_dict_size;
1741 if (UNLIKELY(zxc_ctx_sync_dict_huf(&cctx->inner, cctx->huf_cache, &cctx->huf_cached,
1742 ZXC_OPTS_DICT_HUF(opts)) != ZXC_OK))
1743 return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
1744
1745 int res;
1746 if (b_dict && b_dict_size > 0) {
1747 // [dict | block] assembled in the cctx-owned dict_buffer
1748 uint8_t* const combined = cctx->inner.dict_buffer;
1749 ZXC_MEMCPY(combined, b_dict, b_dict_size);
1750 ZXC_MEMCPY(combined + b_dict_size, src, src_size);
1751 res = zxc_compress_chunk_wrapper(&cctx->inner, combined, b_dict_size + src_size,
1752 (uint8_t*)dst, dst_capacity, block_index);
1753 } else {
1754 res = zxc_compress_chunk_wrapper(&cctx->inner, (const uint8_t*)src, src_size, (uint8_t*)dst,
1755 dst_capacity, block_index);
1756 }
1757 if (UNLIKELY(res < 0)) return res;
1758 return (int64_t)res;
1759}
1760
1766static int64_t zxc_decode_bounce(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT src,
1767 const size_t src_size, uint8_t* RESTRICT dst,
1768 const size_t dst_capacity, const size_t carved) {
1769 // Block API only: frameless blocks carry index 0.
1770 const int res =
1771 zxc_decompress_chunk_wrapper(ctx, src, src_size, ctx->work_buf, ctx->work_buf_cap, 0);
1772 if (UNLIKELY(res < 0)) return res;
1773 if (UNLIKELY(carved != 0 && (size_t)res > carved)) return ZXC_ERROR_BAD_BLOCK_SIZE;
1774 if (UNLIKELY((size_t)res > dst_capacity)) return ZXC_ERROR_DST_TOO_SMALL;
1775 ZXC_MEMCPY(dst, ctx->work_buf, (size_t)res);
1776 return res;
1777}
1778
1786static int64_t zxc_static_decompress_block(zxc_dctx* RESTRICT dctx, const uint8_t* RESTRICT src,
1787 const size_t src_size, uint8_t* RESTRICT dst,
1788 const size_t dst_capacity, const size_t dict_size,
1789 const int checksum_enabled, const int strict) {
1790 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
1791 if (UNLIKELY(dict_size > 0)) return ZXC_ERROR_DICT_UNSUPPORTED;
1792 const size_t carved = dctx->last_block_size;
1793 const size_t work_sz = carved + ZXC_DECOMPRESS_TAIL_PAD;
1794 zxc_cctx_t* const ctx = &dctx->inner;
1795 ctx->checksum_enabled = checksum_enabled;
1796 ctx->dict_size = 0;
1797 int res;
1798 if (strict) {
1799 const size_t cap = dst_capacity < work_sz ? dst_capacity : work_sz;
1800 res = zxc_decompress_chunk_wrapper_safe_public(ctx, src, src_size, dst, cap, 0);
1801 } else if (dst_capacity >= work_sz) {
1802 res = zxc_decompress_chunk_wrapper(ctx, src, src_size, dst, work_sz, 0);
1803 } else {
1804 // Bounce through work_buf when dst cannot absorb wild copies.
1805 return zxc_decode_bounce(ctx, src, src_size, dst, dst_capacity, carved);
1806 }
1807 if (UNLIKELY(res > 0 && (size_t)res > carved)) return ZXC_ERROR_BAD_BLOCK_SIZE;
1808 return res;
1809}
1810
1815static int zxc_dctx_prepare(zxc_dctx* RESTRICT dctx, const size_t block_size,
1816 const size_t dict_size, const int checksum_enabled) {
1817 if (LIKELY(dctx->initialized && dctx->last_block_size == block_size &&
1818 dctx->last_dict_size == dict_size)) {
1819 dctx->inner.checksum_enabled = checksum_enabled;
1820 return ZXC_OK;
1821 }
1822 if (dctx->initialized) {
1823 zxc_cctx_free(&dctx->inner);
1824 dctx->initialized = 0;
1825 }
1826 // LCOV_EXCL_START
1827 if (UNLIKELY(zxc_cctx_init(&dctx->inner, block_size, 0, 0, checksum_enabled, dict_size) !=
1828 ZXC_OK))
1829 return ZXC_ERROR_MEMORY;
1830 // LCOV_EXCL_STOP
1831 dctx->last_block_size = block_size;
1832 dctx->last_dict_size = dict_size;
1833 dctx->initialized = 1;
1834 dctx->huf_cached = 0;
1835 return ZXC_OK;
1836}
1837
1848int64_t zxc_decompress_block(zxc_dctx* dctx, const void* RESTRICT src, const size_t src_size,
1849 void* RESTRICT dst, const size_t dst_capacity,
1850 const zxc_decompress_opts_t* opts) {
1851 if (UNLIKELY(!dctx || !src || !dst || src_size < ZXC_BLOCK_HEADER_SIZE || dst_capacity == 0))
1852 return ZXC_ERROR_NULL_INPUT;
1853
1854 // One format-conformant block, so the payload cannot exceed
1855 // ZXC_BLOCK_SIZE_MAX; dst_capacity is bounded to that plus the tail-pad the
1856 // wild copies need. Larger outputs belong to the frame or streaming APIs.
1857 if (UNLIKELY(dst_capacity > ZXC_BLOCK_SIZE_MAX + ZXC_DECOMPRESS_TAIL_PAD))
1859
1860 const int checksum_enabled = opts ? opts->checksum_enabled : 0;
1861
1862 const uint8_t* dict = opts ? (const uint8_t*)opts->dict : NULL;
1863 const size_t dict_size = ZXC_OPTS_DICT_SIZE(opts);
1864 if (UNLIKELY(dict_size > ZXC_DICT_SIZE_MAX)) return ZXC_ERROR_DICT_TOO_LARGE;
1865 if (UNLIKELY(dctx->owns_workspace))
1866 return zxc_static_decompress_block(dctx, (const uint8_t*)src, src_size, (uint8_t*)dst,
1867 dst_capacity, dict_size, checksum_enabled, 0);
1868
1869 // Carved for dst_capacity: the context holds any block dst can hold.
1870 const size_t block_size = zxc_block_size_ceil(dst_capacity);
1871 const int rc = zxc_dctx_prepare(dctx, block_size, dict_size, checksum_enabled);
1872 if (UNLIKELY(rc != ZXC_OK)) return rc; // LCOV_EXCL_LINE
1873
1874 zxc_cctx_t* const ctx = &dctx->inner;
1875 ctx->dict_size = dict_size;
1876 if (UNLIKELY(zxc_ctx_sync_dict_huf(ctx, dctx->huf_cache, &dctx->huf_cached,
1877 ZXC_OPTS_DICT_HUF(opts)) != ZXC_OK))
1878 return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
1879
1880 // The [dict | decode] buffer carries block_size + ZXC_DECOMPRESS_TAIL_PAD.
1881 const size_t work_sz = block_size + ZXC_DECOMPRESS_TAIL_PAD;
1882
1883 int res;
1884 if (dict && dict_size > 0) {
1885 // [dict | decode] assembled in the cctx-owned dict_buffer
1886 uint8_t* const dec_buf = ctx->dict_buffer;
1887 ZXC_MEMCPY(dec_buf, dict, dict_size);
1888 res = zxc_decompress_chunk_wrapper(ctx, (const uint8_t*)src, src_size, dec_buf + dict_size,
1889 work_sz, 0);
1890 if (LIKELY(res > 0)) {
1891 if (UNLIKELY((size_t)res > dst_capacity)) return ZXC_ERROR_DST_TOO_SMALL;
1892 ZXC_MEMCPY(dst, dec_buf + dict_size, (size_t)res);
1893 }
1894 } else {
1895 res = zxc_decompress_chunk_wrapper(ctx, (const uint8_t*)src, src_size, (uint8_t*)dst,
1896 dst_capacity, 0);
1897 // A tight tail aborts a block that fits: work_buf has the margin.
1898 // Corrupt blocks report OVERFLOW too, and pay the second decode.
1899 if (UNLIKELY(res == ZXC_ERROR_OVERFLOW))
1900 return zxc_decode_bounce(ctx, (const uint8_t*)src, src_size, (uint8_t*)dst,
1901 dst_capacity, 0);
1902 }
1903 if (UNLIKELY(res < 0)) return res;
1904 return (int64_t)res;
1905}
1906
1916int64_t zxc_decompress_block_safe(zxc_dctx* dctx, const void* RESTRICT src, const size_t src_size,
1917 void* RESTRICT dst, const size_t dst_capacity,
1918 const zxc_decompress_opts_t* opts) {
1919 if (UNLIKELY(!dctx || !src || !dst || src_size < ZXC_BLOCK_HEADER_SIZE || dst_capacity == 0))
1920 return ZXC_ERROR_NULL_INPUT;
1921
1922 // Strict-tail variant: dst_capacity matches the exact uncompressed size
1923 if (UNLIKELY(dst_capacity > ZXC_BLOCK_SIZE_MAX)) return ZXC_ERROR_BAD_BLOCK_SIZE;
1924 if (UNLIKELY(dctx->owns_workspace))
1925 return zxc_static_decompress_block(dctx, (const uint8_t*)src, src_size, (uint8_t*)dst,
1926 dst_capacity, ZXC_OPTS_DICT_SIZE(opts),
1927 opts ? opts->checksum_enabled : 0, 1);
1928
1929 // A dict needs the [dict|payload] bounce; route to the bounce-capable path.
1930 if (opts && opts->dict && opts->dict_size > 0) {
1931 return zxc_decompress_block(dctx, src, src_size, dst, dst_capacity, opts);
1932 }
1933
1934 const uint8_t type = ((const uint8_t*)src)[0];
1935 // RAW never wild-writes past dst_capacity: route to the existing fast API.
1936 if (type == ZXC_BLOCK_RAW) {
1937 return zxc_decompress_block(dctx, src, src_size, dst, dst_capacity, opts);
1938 }
1939
1940 // GLO/GHI: use the strict-tail decoder (no bounce buffer required).
1941 const int checksum_enabled = opts ? opts->checksum_enabled : 0;
1942 const int rc = zxc_dctx_prepare(dctx, zxc_block_size_ceil(dst_capacity), 0, checksum_enabled);
1943 if (UNLIKELY(rc != ZXC_OK)) return rc; // LCOV_EXCL_LINE
1944 dctx->inner.dict_size = 0;
1945
1947 &dctx->inner, (const uint8_t*)src, src_size, (uint8_t*)dst, dst_capacity, 0);
1948 if (UNLIKELY(res < 0)) return res;
1949 return (int64_t)res;
1950}
1951
1952// ============================================================================
1953// STATIC CONTEXT API (caller-allocated workspace)
1954// ============================================================================
1955// Places the public handle struct at the start of the workspace, then carves
1956// the persistent buffer (via zxc_cctx_init_in_workspace) in the remaining
1957// cache-line-aligned tail. The caller owns the whole workspace; free
1958// functions become no-ops via the owns_workspace flag.
1959
1960// Size occupied by the opaque handle at the start of the workspace, rounded
1961// up to a cache-line boundary so the persistent buffer (which expects 64 B
1962// alignment for the hot zones) starts aligned.
1963#define ZXC_STATIC_CCTX_HDR_SIZE ZXC_ALIGN_CL(sizeof(struct zxc_cctx_s))
1964#define ZXC_STATIC_DCTX_HDR_SIZE ZXC_ALIGN_CL(sizeof(struct zxc_dctx_s))
1965
1973size_t zxc_static_cctx_workspace_size(const size_t block_size, const int level) {
1974 if (UNLIKELY(!zxc_validate_block_size(block_size))) return 0;
1975 if (UNLIKELY(level < ZXC_LEVEL_FASTEST || level > ZXC_LEVEL_ULTRA)) return 0;
1976 const size_t inner_sz = zxc_cctx_compute_workspace_size(block_size, 1, level, 0);
1977 if (UNLIKELY(inner_sz == 0)) return 0;
1978 return ZXC_STATIC_CCTX_HDR_SIZE + inner_sz;
1979}
1980
1981zxc_cctx* zxc_init_static_cctx(void* RESTRICT workspace, const size_t workspace_size,
1982 const zxc_compress_opts_t* RESTRICT opts) {
1983 if (UNLIKELY(!workspace || !opts)) return NULL;
1984
1985 const int level = (opts->level > 0) ? opts->level : ZXC_LEVEL_DEFAULT;
1986 const size_t block_size = (opts->block_size > 0) ? opts->block_size : ZXC_BLOCK_SIZE_DEFAULT;
1987 const int checksum_enabled = opts->checksum_enabled;
1988
1989 if (UNLIKELY(!zxc_validate_block_size(block_size))) return NULL;
1990 if (UNLIKELY(level < ZXC_LEVEL_FASTEST || level > ZXC_LEVEL_ULTRA)) return NULL;
1991
1992 const size_t inner_sz = zxc_cctx_compute_workspace_size(block_size, 1, level, 0);
1993 if (UNLIKELY(inner_sz == 0)) return NULL;
1994 if (UNLIKELY(workspace_size < ZXC_STATIC_CCTX_HDR_SIZE + inner_sz)) return NULL;
1995
1996 zxc_cctx* const cctx = (zxc_cctx*)workspace;
1997 ZXC_MEMSET(cctx, 0, sizeof(*cctx));
1998
1999 uint8_t* const inner_ws = (uint8_t*)workspace + ZXC_STATIC_CCTX_HDR_SIZE;
2000 if (UNLIKELY(zxc_cctx_init_in_workspace(&cctx->inner, inner_ws, inner_sz, block_size, 1, level,
2001 checksum_enabled, 0, 0) != ZXC_OK))
2002 return NULL;
2003
2004 cctx->owns_workspace = 1;
2005 cctx->initialized = 1;
2006 cctx->last_block_size = block_size;
2007 cctx->stored_level = level;
2008 cctx->stored_block_size = block_size;
2009 cctx->stored_checksum = checksum_enabled;
2010 cctx->stored_seekable = opts->seekable;
2011 return cctx;
2012}
2013
2021size_t zxc_static_dctx_workspace_size(const size_t block_size) {
2022 if (UNLIKELY(!zxc_validate_block_size(block_size))) return 0;
2023 const size_t inner_sz = zxc_cctx_compute_workspace_size(block_size, 0, 0, 0);
2024 if (UNLIKELY(inner_sz == 0)) return 0;
2025 return ZXC_STATIC_DCTX_HDR_SIZE + inner_sz;
2026}
2027
2028zxc_dctx* zxc_init_static_dctx(void* RESTRICT workspace, const size_t workspace_size,
2029 const size_t block_size) {
2030 if (UNLIKELY(!workspace)) return NULL;
2031 if (UNLIKELY(!zxc_validate_block_size(block_size))) return NULL;
2032
2033 const size_t inner_sz = zxc_cctx_compute_workspace_size(block_size, 0, 0, 0);
2034 if (UNLIKELY(inner_sz == 0)) return NULL;
2035 if (UNLIKELY(workspace_size < ZXC_STATIC_DCTX_HDR_SIZE + inner_sz)) return NULL;
2036
2037 zxc_dctx* const dctx = (zxc_dctx*)workspace;
2038 ZXC_MEMSET(dctx, 0, sizeof(*dctx));
2039
2040 uint8_t* const inner_ws = (uint8_t*)workspace + ZXC_STATIC_DCTX_HDR_SIZE;
2041 // mode == 0 init: checksum_enabled is updated per-call from the file
2042 // header flags, so it does not need to be locked at workspace init.
2043 if (UNLIKELY(zxc_cctx_init_in_workspace(&dctx->inner, inner_ws, inner_sz, block_size, 0, 0, 0,
2044 0, 0) != ZXC_OK))
2045 return NULL;
2046
2047 dctx->owns_workspace = 1;
2048 dctx->initialized = 1;
2049 dctx->last_block_size = block_size;
2050 return dctx;
2051}
struct zxc_cctx_s zxc_cctx
Opaque reusable compression context (see zxc_create_cctx).
Definition zxc_buffer.h:327
int64_t zxc_compress_block(zxc_cctx *cctx, const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_compress_opts_t *opts)
Compresses a single block (no file framing), reusing cctx.
struct zxc_dctx_s zxc_dctx
Opaque reusable decompression context (see zxc_create_dctx).
Definition zxc_buffer.h:329
int64_t zxc_decompress_block_safe(zxc_dctx *dctx, const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_decompress_opts_t *opts)
Safe-variant block decompressor: accepts dst_capacity == uncompressed_size.
int64_t zxc_decompress_block(zxc_dctx *dctx, const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_decompress_opts_t *opts)
Decompresses a single block (no file framing), reusing dctx.
#define ZXC_BLOCK_SIZE_MAX
Maximum allowed block size (2 MB = 2^21).
#define ZXC_BLOCK_SIZE_DEFAULT
Default block size (512 KB).
int64_t zxc_decompress(const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_decompress_opts_t *opts)
Decompresses an entire buffer in one call.
int zxc_get_frame_info(const void *src, const size_t src_size, zxc_frame_info_t *info, const size_t info_size)
Reads a frame's header and footer, without decoding.
uint64_t zxc_get_decompressed_size(const void *src, const size_t src_size)
Reads the decompressed size from a ZXC-compressed buffer.
int64_t zxc_compress(const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_compress_opts_t *opts)
Compresses an entire buffer in one call.
int64_t zxc_decompress_inplace(void *buffer, const size_t buffer_capacity, const size_t comp_size, const zxc_decompress_opts_t *opts)
Decompresses in place, inside a single caller-owned buffer.
size_t zxc_decompress_inplace_bound(const void *src, const size_t src_size)
Minimum single-buffer size for a safe in-place decode of src.
uint32_t zxc_get_dict_id(const void *src, const size_t src_size)
Reads the dictionary id from a compressed archive's file header.
int zxc_get_last_frame_info(const void *src, const size_t src_size, zxc_frame_info_t *info, const size_t info_size)
Reads the frame that ends src, without decoding.
zxc_cctx * zxc_create_cctx(const zxc_compress_opts_t *opts)
Creates a reusable compression context.
int64_t zxc_compress_cctx(zxc_cctx *cctx, const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_compress_opts_t *opts)
Compresses data using a reusable context.
void zxc_free_cctx(zxc_cctx *cctx)
Releases a reusable compression context.
zxc_dctx * zxc_create_dctx(void)
Creates a reusable decompression context.
int64_t zxc_decompress_dctx(zxc_dctx *dctx, const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_decompress_opts_t *opts)
Decompresses a framed archive into dst, reusing dctx.
int64_t zxc_decompress_inplace_dctx(zxc_dctx *dctx, void *buffer, const size_t buffer_capacity, const size_t comp_size, const zxc_decompress_opts_t *opts)
Decompresses in place inside one caller buffer, reusing dctx.
void zxc_free_dctx(zxc_dctx *dctx)
Releases a reusable decompression context.
uint32_t zxc_dict_id(const void *dict, size_t dict_size, const void *huf_lengths)
Computes the dictionary ID for the given content and optional table.
Definition zxc_dict.c:29
#define ZXC_DICT_SIZE_MAX
Maximum dictionary content size in bytes (64 KB - 1).
#define ZXC_HUF_TABLE_SIZE
Size of a packed literal Huffman code-lengths table (256 symbols, 4 bits each): the shared table in a...
@ ZXC_ERROR_BAD_MAGIC
Invalid magic word in file header.
Definition zxc_error.h:48
@ ZXC_ERROR_OVERFLOW
Buffer overflow detected during processing.
Definition zxc_error.h:58
@ ZXC_ERROR_CORRUPT_DATA
Corrupted compressed data.
Definition zxc_error.h:56
@ ZXC_ERROR_NULL_INPUT
Required input pointer is NULL.
Definition zxc_error.h:62
@ ZXC_ERROR_DICT_MISMATCH
Provided dictionary ID does not match the file header.
Definition zxc_error.h:70
@ ZXC_ERROR_MEMORY
Memory allocation failure.
Definition zxc_error.h:41
@ ZXC_ERROR_BAD_HEADER
Corrupted or invalid header (checksum mismatch).
Definition zxc_error.h:50
@ ZXC_ERROR_DST_TOO_SMALL
Destination buffer too small.
Definition zxc_error.h:44
@ ZXC_ERROR_SRC_TOO_SMALL
Source buffer too small or truncated input.
Definition zxc_error.h:45
@ ZXC_ERROR_DICT_REQUIRED
File requires a dictionary but none was provided.
Definition zxc_error.h:69
@ ZXC_ERROR_DICT_UNSUPPORTED
Dictionary not supported by this context's workspace.
Definition zxc_error.h:75
@ ZXC_ERROR_BAD_CHECKSUM
A block's decoded bytes fail its checksum, or the archive digest mismatches.
Definition zxc_error.h:51
@ ZXC_ERROR_BAD_LEVEL
Compression level not supported by this context's workspace.
Definition zxc_error.h:74
@ ZXC_OK
Success (no error).
Definition zxc_error.h:38
@ ZXC_ERROR_BAD_BLOCK_SIZE
Invalid block size.
Definition zxc_error.h:66
@ ZXC_ERROR_DICT_TOO_LARGE
Dictionary exceeds maximum allowed size.
Definition zxc_error.h:71
#define ZXC_MAGIC_WORD
Magic word opening every frame, stored little-endian (F5 2E B0 9C).
#define ZXC_FILE_HEADER_SIZE
File header size: Magic(4) + Version(1) + Chunk(1) + Flags(1) + Reserved(7) + Checksum(2).
#define ZXC_MALLOC(size)
Heap allocator.
Definition zxc_deps.h:68
#define ZXC_SEEK_GROUP
Blocks per group.
#define ZXC_ALWAYS_INLINE
Forces a function to be inlined (fallback for non-GCC/Clang/MSVC compilers).
static uint64_t zxc_seek_table_bytes(const uint64_t nblocks)
Byte size of the groups for nblocks blocks, in 64 bits.
#define ZXC_SEEK_SIZE_ENTRY
One block's on-disk size, u32.
#define ZXC_CALLOC(nmemb, size)
Zero-initialised heap allocator.
Definition zxc_deps.h:74
#define ZXC_FREE(ptr)
Heap deallocator.
Definition zxc_deps.h:86
#define ZXC_FILE_FLAG_HAS_DICTIONARY
Bit flag in the Flags byte indicating a dictionary is required (bit 6).
#define ZXC_BLOCK_HEADER_SIZE
Block header size: Type(1)+Flags(1)+Reserved(1)+Checksum(1)+CompSize(4).
#define ZXC_NOINLINE
Prevents inlining (best-effort no-op fallback for unknown compilers).
#define ZXC_BLOCK_CHECKSUM_SIZE
Size of the per-block checksum field in bytes.
#define ZXC_SEEK_ANCHOR_SIZE
Group anchor: offset of its first block, u64.
#define ZXC_DECOMPRESS_TAIL_PAD
Tail padding required on the decompression destination buffer.
@ ZXC_LEVEL_ULTRA
Maximum density: Huffman-coded literals and sequence tokens.
@ ZXC_LEVEL_DEFAULT
Recommended default: better ratio and decode speed than LZ4.
@ ZXC_LEVEL_DENSITY
Higher density: adds Huffman-coded literals on top of COMPACT.
int64_t zxc_write_seek_table(uint8_t *dst, const size_t dst_capacity, const uint32_t *comp_sizes, const uint64_t num_blocks)
Writes a seek table to the destination buffer.
Definition zxc_common.c:786
size_t zxc_seek_table_size(const uint64_t num_blocks)
Returns the encoded size of a seek table for the given block count.
Definition zxc_common.c:731
size_t zxc_static_dctx_workspace_size(const size_t block_size)
Workspace size needed for a static decompression context.
size_t zxc_static_cctx_workspace_size(const size_t block_size, const int level)
Workspace size needed for a static compression context.
zxc_dctx * zxc_init_static_dctx(void *workspace, const size_t workspace_size, const size_t block_size)
Initialises a decompression context inside a caller-supplied workspace.
zxc_cctx * zxc_init_static_cctx(void *workspace, const size_t workspace_size, const zxc_compress_opts_t *opts)
Initialises a compression context inside a caller-supplied workspace.
On-disk header structure for a ZXC block (8 bytes, little-endian).
uint32_t comp_size
Compressed size excluding this header.
uint8_t block_type
Block type (see zxc_block_type_t).
Compression / decompression context.
size_t work_buf_cap
Capacity of the work buffer.
uint8_t * work_buf
Padded scratch buffer for buffer-API decompression.
uint8_t * dict_buffer
[dict | data] concat scratch carved from memory_block when dict_size > 0 (NULL otherwise).
size_t dict_size
Dictionary prefill size (0 = no dictionary).
int checksum_enabled
1 if checksum calculation/verification is enabled.
Options for compression.
Definition zxc_opts.h:58
int seekable
1 to append a seek table for random-access decompression.
Definition zxc_opts.h:64
const void * dict
Pre-trained dictionary content (NULL = none).
Definition zxc_opts.h:65
int checksum_enabled
1 to append the block checksums and archive digest, 0 to disable.
Definition zxc_opts.h:63
What zxc_scan_container learns about a container.
int uses_dict
A frame bound to a dictionary.
uint64_t dsize
Sum of the stored decompressed sizes.
Options for decompression.
Definition zxc_opts.h:86
int checksum_enabled
1 to verify the block checksums and archive digest, 0 to skip.
Definition zxc_opts.h:88
size_t dict_size
Dictionary size in bytes (0 = none).
Definition zxc_opts.h:90
const void * dict
Pre-trained dictionary content (NULL = none).
Definition zxc_opts.h:89
What a frame's header and footer declare, read without decoding.
Definition zxc_buffer.h:235
uint8_t has_checksum
1 when blocks carry checksums and the footer a digest.
Definition zxc_buffer.h:242
size_t block_size
Block size, 4 KB to 2 MB.
Definition zxc_buffer.h:239
uint64_t decompressed_size
Source bytes the frame decodes to.
Definition zxc_buffer.h:236
uint8_t has_seek_table
1 when a seek table precedes the footer.
Definition zxc_buffer.h:243
A PivCo code tree: its canonical shape, by depth.
Where a container scan reads from: memory, or a positioned callback (the FILE* size query).
int zxc_write_file_header(uint8_t *dst, const size_t dst_capacity, const size_t chunk_size, const int has_checksum, const uint32_t dict_id, const int has_seek)
Serialises a ZXC file header into dst.
Definition zxc_common.c:486
int zxc_read_block_header(const uint8_t *src, const size_t src_size, zxc_block_header_t *bh)
Parses and validates a block header from src.
Definition zxc_common.c:703
void zxc_cctx_free(zxc_cctx_t *ctx)
Releases all resources owned by a compression context.
Definition zxc_common.c:400
int zxc_write_block_header(uint8_t *dst, const size_t dst_capacity, const zxc_block_header_t *bh)
Serialises a block header (8 bytes) into dst.
Definition zxc_common.c:684
int zxc_scan_container(const zxc_scan_src_t *src, const size_t req_chunk, zxc_container_info_t *info)
Measures a whole container without decoding: frames from the last back to the first through zxc_scan_...
Definition zxc_common.c:613
int zxc_seek_table_header(uint8_t *dst, const size_t dst_capacity, const uint64_t num_blocks)
Writes a seek table's block header for num_blocks entries.
Definition zxc_common.c:738
int zxc_cctx_init_in_workspace(zxc_cctx_t *ctx, void *workspace, const size_t workspace_size, const size_t chunk_size, const int mode, const int level, const int checksum_enabled, const size_t dict_size, const int defer_entropy_scratch)
Partitions a caller-supplied workspace into a ready-to-use context.
Definition zxc_common.c:260
int zxc_read_file_header(const uint8_t *src, const size_t src_size, size_t *out_block_size, int *out_has_checksum, uint32_t *out_dict_id, int *out_has_seek)
Parses and validates a ZXC file header from src.
Definition zxc_common.c:516
int zxc_scan_frame(const zxc_scan_src_t *src, const uint64_t end, uint64_t *start, zxc_frame_info_t *info, size_t *footer_len)
Measures the frame ending at end without decoding: its footer's compressed size gives where it starts...
Definition zxc_common.c:569
int zxc_scan_whole_frame(const zxc_scan_src_t *src, zxc_frame_info_t *info, size_t *footer_len)
The frame that must span all of src: zxc_get_frame_info() and its FILE* twin.
Definition zxc_common.c:601
size_t zxc_cctx_compute_workspace_size(const size_t chunk_size, const int mode, const int level, const size_t dict_size)
Returns the workspace byte count required for the given parameters.
Definition zxc_common.c:245
int zxc_container_next(const zxc_scan_src_t *src, const uint64_t pos)
Tells whether a frame starts at pos.
Definition zxc_common.c:558
int zxc_check_eof_header(const uint8_t *hdr)
Checks an EOF block header against its one valid form: type 255, every other field zero,...
Definition zxc_common.c:748
int zxc_cctx_init(zxc_cctx_t *ctx, const size_t chunk_size, const int mode, const int level, const int checksum_enabled, const size_t dict_size)
Initialises a compression / decompression context, allocating the persistent buffer with ZXC_ALIGNED_...
Definition zxc_common.c:339
int zxc_write_file_footer(uint8_t *dst, const size_t dst_capacity, const uint64_t prefix, const uint64_t src_size, const uint64_t digest, const int checksum_enabled)
Writes the file footer: the archive digest when checksum_enabled, then the two sizes and their length...
Definition zxc_common.c:832
int zxc_cctx_attach_dict_huf(zxc_cctx_t *ctx, const uint8_t *lengths)
Attach the shared dictionary literal Huffman table to a context.
Definition zxc_common.c:453
int zxc_check_file_footer(const uint8_t *footer, const size_t avail, const zxc_footer_layout_t *l, const uint64_t src_size, uint64_t *digest)
Checks a footer against the one the frame implies.
Definition zxc_common.c:847
int zxc_check_seek_header(const uint8_t *hdr, const size_t avail, const uint64_t total_out, const size_t block_size, uint64_t *sek_bytes)
Checks the SEK block header the file header announced, on the avail bytes present.
Definition zxc_common.c:757
static _Atomic zxc_decompress_func_t zxc_decompress_safe_ptr
The _safe decoder in use, NULL until the first call.
static int64_t zxc_static_decompress_block(zxc_dctx *dctx, const uint8_t *src, const size_t src_size, uint8_t *dst, const size_t dst_capacity, const size_t dict_size, const int checksum_enabled, const int strict)
Block decode on a static dctx: the carved block is the effective capacity.
#define ZXC_VARIANT_SET(sfx)
Defines the constant variant set of suffix sfx.
static int zxc_ctx_sync_dict_huf(zxc_cctx_t *inner, uint8_t *cache, int *cached, const uint8_t *dict_huf)
Attaches the shared literal table to inner; NULL detaches.
static int zxc_decompress_chunk_wrapper_safe_public(const zxc_cctx_t *ctx, const uint8_t *src, const size_t src_sz, uint8_t *dst, const size_t dst_cap, const uint64_t block_index)
Internal safe-decompression dispatcher (strict dst_capacity == uncompressed_size).
static int64_t zxc_write_seek_table_from_blocks(uint8_t *dst, const size_t dst_capacity, const uint8_t *blocks, const uint8_t *const blocks_end, const uint64_t num_blocks, const int checksum_enabled)
Writes the seek table of the num_blocks blocks in [blocks, blocks_end), sizes read back from their he...
static const zxc_variant_set_t * zxc_select_variants(void)
Detects the CPU tier and returns the matching variant set.
int(* zxc_decompress_func_t)(const zxc_cctx_t *, const uint8_t *, const size_t, uint8_t *, const size_t, const uint64_t)
Function pointer type for the chunk decompressor.
size_t zxc_huf_calc_size(const uint32_t *freq, const uint8_t *code_len, const int with_header)
Un-suffixed forwarders for the PivCo section codec (tests, tools).
zxc_cpu_feature_t
Detected CPU SIMD capability level.
@ ZXC_CPU_GENERIC
Scalar-only fallback.
@ ZXC_CPU_SSE2
x86 SSE2 available (no AVX2); x86-64 baseline, served by _default (no dedicated variant).
@ ZXC_CPU_AVX512
x86-64 AVX-512F+BW available.
@ ZXC_CPU_AVX2
x86-64 AVX2 available.
@ ZXC_CPU_NEON
ARM NEON available (dedicated variant on 32-bit ARM only; AArch64 baseline, served by _default there)...
static int64_t zxc_dctx_probe(const zxc_dctx *dctx, const uint8_t *src, const size_t src_size, const zxc_decompress_opts_t *opts)
Answers a no-destination decode on a reusable context.
static uint64_t zxc_inplace_margin(const uint64_t dsize, const size_t chunk_size, const int has_cs, const int has_seek, const size_t footer_len)
Bytes an in-place decode needs on top of the decompressed size.
static int zxc_inplace_prepare(const void *buffer, const size_t buffer_capacity, const size_t comp_size, const zxc_decompress_opts_t *opts, const uint8_t **comp)
Argument and buffer checks shared by the in-place entry points.
int(* zxc_compress_func_t)(zxc_cctx_t *, const uint8_t *, const size_t, uint8_t *, const size_t, const uint64_t)
Function pointer type for the chunk compressor.
static int64_t zxc_decode_bounce(zxc_cctx_t *ctx, const uint8_t *src, const size_t src_size, uint8_t *dst, const size_t dst_capacity, const size_t carved)
Block decode through work_buf, for a dst too tight for the speculative writes, then copied out.
static int64_t zxc_dctx_decode_frame(zxc_dctx *dctx, const uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_capacity, const zxc_decompress_opts_t *opts, int inplace, size_t *frame_end)
The frame walk behind every buffer decode: zxc_decompress(), its no-destination probe,...
int zxc_huf_decode_section_dict(const uint8_t *payload, const size_t payload_size, uint8_t *dst, const size_t n, const zxc_pivco_tree_t *tree, uint8_t *scratch)
Decode a PivCo dict section against a prebuilt dict tree.
static int64_t zxc_write_empty_frame(uint8_t *dst, const size_t dst_capacity, const size_t block_size, const int checksum_enabled, const uint32_t did, const int seekable)
Writes the whole archive an empty source produces: file header, EOF block, the empty seek table when ...
int zxc_huf_build_code_lengths(const uint32_t *freq, uint8_t *code_len, void *scratch, const int max_code_len)
Build length-limited per-symbol Huffman code lengths from frequencies.
static const zxc_variant_set_t * zxc_variants(void)
The variant set in use, selected on the first call.
size_t zxc_huf_calc_size_dict(const uint32_t *freq, const uint8_t *code_len, const zxc_pivco_tree_t *tree)
zxc_huf_calc_size for a dict section: prebuilt tree, no header.
static const zxc_variant_set_t * zxc_variants_resolve(void)
First call: selects and publishes.
int zxc_huf_encode_section_dict(const uint8_t *literals, const size_t n_literals, const uint32_t *freq, const uint8_t *code_len, const zxc_pivco_tree_t *tree, const uint32_t *codes, uint8_t *dst, const size_t dst_cap)
Encode a PivCo section against a prebuilt dict tree/codes (no header).
int64_t zxc_compress_block_at(zxc_cctx *cctx, const void *src, const size_t src_size, void *dst, const size_t dst_capacity, const zxc_compress_opts_t *opts, const uint64_t block_index)
zxc_compress_block, its checksum seeded with block_index (public entry: 0).
static int64_t zxc_dctx_decode_container(zxc_dctx *dctx, const uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_capacity, const zxc_decompress_opts_t *opts)
The container walk behind zxc_decompress() and zxc_decompress_dctx().
int zxc_compress_chunk_wrapper(zxc_cctx_t *ctx, const uint8_t *src, const size_t src_sz, uint8_t *dst, const size_t dst_cap, const uint64_t block_index)
Public compression dispatcher.
static int zxc_inplace_probe(const uint8_t *comp, const size_t comp_size, uint64_t *dsize, uint64_t *margin, uint64_t *floor)
Shared archive probe for the in-place entry points: validates the magic + file header,...
static zxc_cpu_feature_t zxc_detect_cpu_features(void)
Probes the running CPU for SIMD support.
static int zxc_buffer_frame_info(const uint8_t *src, const size_t src_size, zxc_frame_info_t *info, size_t *footer_len)
Validates a frame without decoding it: the check of zxc_read_frame_info() on a whole buffer,...
static int64_t zxc_probe_without_dst(const uint8_t *src, const size_t src_size, const zxc_decompress_opts_t *opts, const zxc_container_info_t *scanned)
Answers a decode request that carries no destination.
static const zxc_variant_set_t *_Atomic zxc_variants_ptr
The selection, NULL until the first call.
static zxc_decompress_func_t zxc_decompress_safe_variant(void)
The _safe decoder in use, selected on the first call.
static int zxc_dctx_prepare(zxc_dctx *dctx, const size_t block_size, const size_t dict_size, const int checksum_enabled)
Recarves a heap dctx when block_size or dict_size changed; otherwise only refreshes the checksum flag...
int zxc_decompress_chunk_wrapper(const zxc_cctx_t *ctx, const uint8_t *src, const size_t src_sz, uint8_t *dst, const size_t dst_cap, const uint64_t block_index)
Public decompression dispatcher.
static int zxc_dctx_carve(zxc_dctx *dctx, const size_t chunk_size, const size_t dict_size, const int checksum, const uint8_t *dict_huf)
Readies dctx for a frame: re-carves the inner buffers when the block or dictionary size changed (the ...
int zxc_huf_decode_section(const uint8_t *payload, const size_t payload_size, uint8_t *dst, const size_t n, uint8_t *scratch)
Decode a PivCo literal section into dst (exactly n bytes).
void zxc_huf_pack_lengths(const uint8_t *code_len, uint8_t *out)
Pack per-symbol code lengths into the 128-byte nibble header.
int zxc_huf_encode_section(const uint8_t *literals, const size_t n_literals, const uint32_t *freq, const uint8_t *code_len, uint8_t *dst, const size_t dst_cap)
Encode a PivCo literal section (128-byte lengths header + payload).
int zxc_huf_unpack_lengths(const uint8_t *in, uint8_t *code_len)
Unpack and validate a 128-byte packed lengths header.
static zxc_decompress_func_t zxc_decompress_safe_resolve(void)
First call: the _safe decoder of the selected set, published.
Internal definitions, constants, SIMD helpers, and utility functions.
static uint32_t zxc_le32(const void *p)
Reads a little-endian 32-bit value from a possibly unaligned address.
static void zxc_frame_info_copy(zxc_frame_info_t *info, const size_t info_size, const zxc_frame_info_t *got)
Copies the fields of got that fit the caller's info_size bytes.
static int zxc_validate_block_size(const size_t bs)
Validates a block size.
static zxc_scan_src_t zxc_scan_src_mem(const uint8_t *data, const size_t size)
A scan source over size bytes of memory at data.
static void zxc_store_le64(void *p, const uint64_t v)
Writes a 64-bit value little-endian to a possibly unaligned address.
static uint64_t zxc_digest_combine(const uint64_t acc, const uint32_t block_checksum)
Folds a data block's 32-bit checksum into the running archive digest.
static size_t zxc_block_size_ceil(const size_t v)
Branchless bit_ceil: smallest power of two >= v, clamped to ZXC_BLOCK_SIZE_MIN.
#define ZXC_OPTS_DICT_HUF(o)
Shared literal Huffman table, gated on dict the same way.
#define ZXC_FRAME_MIN_SIZE
Smallest frame: header, the EOF block and the shortest footer.
#define ZXC_OPTS_LEVEL(o, dflt)
Compression level, 0 meaning the default, clamped to the highest level the encoder implements.
static uint64_t zxc_seek_block_count(const uint64_t total_decomp, const size_t block_size)
Blocks in total_decomp bytes of block_size: the seek table's entry count, which the SEK header's fiel...
static void zxc_store_le32(void *p, const uint32_t v)
Writes a 32-bit value little-endian to a possibly unaligned address.
#define ZXC_OPTS_DICT_SIZE(o)
Dictionary length from a (possibly NULL) options struct.
#define ZXC_OPTS_BLOCK_SIZE(o, dflt)
Block size, 0 meaning the default.
static zxc_footer_layout_t zxc_footer_layout(const uint64_t prefix, const uint64_t src_size, const int checksum_enabled)
The footer of a frame whose first prefix bytes precede it.
static int zxc_short_input_verdict(const uint8_t *p, const size_t n)
The verdict on n bytes too short for a frame: the header speaks first, then ZXC_ERROR_SRC_TOO_SMALL.