ZXC 0.15.0
High-performance asymmetric lossless compression library
Loading...
Searching...
No Matches
zxc_common.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
17#include "../../include/zxc_buffer.h"
18#include "../../include/zxc_error.h"
19#include "zxc_internal.h"
20
21// ============================================================================
22// CONTEXT MANAGEMENT
23// ============================================================================
24
33size_t zxc_compress_opts_size(void) { return sizeof(zxc_compress_opts_t); }
34
43size_t zxc_decompress_opts_size(void) { return sizeof(zxc_decompress_opts_t); }
44
50// cppcheck-suppress unusedFunction
51size_t zxc_frame_info_size(void) { return sizeof(zxc_frame_info_t); }
52
53// Offset table of the persistent buffer carved by every cctx/dctx init. Both
54// modes compute it identically, for the workspace sizer and the in-place init.
55typedef struct {
56 size_t total;
57 // mode == 0 (decompress)
58 size_t off_work;
59 size_t off_lit_dctx;
60 // mode == 0: scratch for a Huffman-coded GLO token section (enc_tok == HUFFMAN).
61 size_t off_tok_dctx;
62 size_t sz_tok_dctx;
63 // mode == 0: PivCo decode level scratch (one chunk-sized ping-pong buffer).
64 size_t off_pivco_dctx;
65 size_t sz_pivco_dctx;
66 // mode == 1 (compress)
67 size_t off_hash_pos;
68 size_t off_hash_tags;
69 size_t off_chain;
70 size_t off_seq_union;
71 size_t off_split_hist;
72 size_t off_extras;
73 size_t off_lit_cctx;
74 // meaningful only when sz_opt > 0 (level >= ZXC_LEVEL_DENSITY).
75 size_t off_opt;
76 // both modes: [dict | data] concat scratch, present only when dict_size > 0.
77 size_t off_dict;
78 // both modes: dict Huffman tree-at-attach state, present only when dict_size > 0.
79 size_t off_dict_huf;
80 // Sub-buffer sizes (re-used by the partitioning step + zero-init).
81 size_t sz_hash_pos;
82 size_t sz_hash_tags;
83 size_t sz_opt;
84 size_t sz_dict; /* 0 = no dictionary buffer. */
85 size_t max_seq;
86} zxc_cctx_layout_t;
87
94static ZXC_ALWAYS_INLINE size_t zxc_cctx_max_seq(const size_t chunk_size) {
95 return chunk_size / ZXC_LZ_MIN_MATCH_LEN + 16;
96}
97
105static void zxc_dctx_entropy_sizes(const size_t chunk_size, size_t* RESTRICT sz_tok,
106 size_t* RESTRICT sz_pivco) {
107 *sz_tok = zxc_cctx_max_seq(chunk_size) + ZXC_PAD_SIZE;
108 *sz_pivco = chunk_size + ZXC_PIVCO_SCRATCH_PAD;
109}
110
140static zxc_cctx_layout_t compute_cctx_layout(const size_t chunk_size, const int mode,
141 const int level, const size_t dict_size,
142 const int defer_entropy_scratch) {
143 zxc_cctx_layout_t layout = {0};
144
145 const size_t max_seq = zxc_cctx_max_seq(chunk_size);
146
147 if (mode == 0) {
148 // Decompress: work_buf + lit_buffer, padded for wild-copy overshoot and
149 // sized worst-case. lit_buffer is provisioned at every level - the decoder
150 // cannot predict a block's literal encoding (RAW / RLE / HUFFMAN).
151 const size_t sz_work = chunk_size + ZXC_DECOMPRESS_TAIL_PAD;
152 const size_t sz_lit = chunk_size + ZXC_PAD_SIZE;
153
154 layout.off_work = layout.total;
155 layout.total += ZXC_ALIGN_CL(sz_work);
156 layout.off_lit_dctx = layout.total;
157 layout.total += ZXC_ALIGN_CL(sz_lit);
158 // Token-section decode scratch (level-7 GLO Huffman-codes the tokens) +
159 // PivCo ping-pong scratch. enc_lit/enc_tok are unpredictable, so static
160 // workspaces provision both up front (no-alloc contract); heap contexts
161 // defer them to the first entropy section, sparing L1-5 archives ~1.2x
162 // chunk_size. See zxc_cctx_alloc_entropy_scratch.
163 if (!defer_entropy_scratch) {
164 size_t sz_tok = 0;
165 size_t sz_pivco = 0;
166 zxc_dctx_entropy_sizes(chunk_size, &sz_tok, &sz_pivco);
167 layout.sz_tok_dctx = sz_tok;
168 layout.off_tok_dctx = layout.total;
169 layout.total += ZXC_ALIGN_CL(layout.sz_tok_dctx);
170 layout.sz_pivco_dctx = sz_pivco;
171 layout.off_pivco_dctx = layout.total;
172 layout.total += ZXC_ALIGN_CL(layout.sz_pivco_dctx);
173 }
174 } else {
175 // Compress: 7 partitions + optional opt_scratch at level >= ZXC_LEVEL_DENSITY.
176 const uint32_t offset_bits = zxc_log2_u32((uint32_t)chunk_size);
177 layout.max_seq = max_seq;
178 layout.sz_hash_pos = ZXC_LZ_HASH_SIZE * sizeof(uint32_t);
179 layout.sz_hash_tags = ZXC_LZ_HASH_SIZE * sizeof(uint8_t);
180 const size_t sz_chain = ZXC_LZ_WINDOW_SIZE * sizeof(uint16_t);
181 // buf_sequences (GHI, level <= ZXC_LEVEL_FAST) aliases buf_offsets, buf_tokens and
182 // buf_split (GLO, level >= ZXC_LEVEL_DEFAULT): 4 bytes per sequence either way.
183 const size_t sz_seq_union = layout.max_seq * sizeof(uint32_t);
184 const size_t vbyte_len = (offset_bits + 6) / 7;
185 const size_t sz_extras = layout.max_seq * 2 * vbyte_len;
186 const size_t sz_lit = chunk_size + ZXC_PAD_SIZE;
187
188 // opt_scratch (level >= ZXC_LEVEL_DENSITY): the optimal parser's DP arrays,
189 // reused transiently by the code-length builder and its nudge, so sized to
190 // the larger demand. Keep in sync with zxc_estimate_cctx_size() and its
191 // consumer in zxc_compress.c.
192 if (level >= ZXC_LEVEL_DENSITY) {
193 size_t sz_dp;
194 size_t sz_pl;
195 size_t sz_po;
196 size_t sz_bm;
197 zxc_opt_dp_sizes(chunk_size, &sz_dp, &sz_pl, &sz_po, &sz_bm);
198 const size_t dp_needed = sz_dp + sz_pl + sz_po + sz_bm;
199 layout.sz_opt =
200 (dp_needed > ZXC_HUF_NUDGE_SCRATCH_SIZE) ? dp_needed : ZXC_HUF_NUDGE_SCRATCH_SIZE;
201 }
202
203 layout.off_hash_pos = layout.total;
204 layout.total += ZXC_ALIGN_CL(layout.sz_hash_pos);
205 layout.off_hash_tags = layout.total;
206 layout.total += ZXC_ALIGN_CL(layout.sz_hash_tags);
207 layout.off_chain = layout.total;
208 layout.total += ZXC_ALIGN_CL(sz_chain);
209 layout.off_seq_union = layout.total;
210 layout.total += ZXC_ALIGN_CL(sz_seq_union);
211 layout.off_split_hist = layout.total;
212 layout.total += ZXC_ALIGN_CL(sizeof(zxc_glo_split_hist_t));
213 layout.off_extras = layout.total;
214 layout.total += ZXC_ALIGN_CL(sz_extras);
215 layout.off_lit_cctx = layout.total;
216 layout.total += ZXC_ALIGN_CL(sz_lit);
217 // opt_scratch is appended last so it is absent for levels 1..5 (zero
218 // waste on the common path) and only inflates the workspace at level 6.
219 if (layout.sz_opt) {
220 layout.off_opt = layout.total;
221 layout.total += ZXC_ALIGN_CL(layout.sz_opt);
222 }
223 }
224
225 // [dict | data] concat scratch (dict only). Compress chunk_size already
226 // spans [dict | block]; decompress prepends dict to a (chunk + PAD) region.
227 if (dict_size > 0) {
228 layout.sz_dict = (mode == 1) ? (chunk_size + ZXC_DECOMPRESS_TAIL_PAD)
229 : (dict_size + chunk_size + ZXC_DECOMPRESS_TAIL_PAD);
230 layout.off_dict = layout.total;
231 layout.total += ZXC_ALIGN_CL(layout.sz_dict);
232 layout.off_dict_huf = layout.total;
233 layout.total += ZXC_ALIGN_CL(sizeof(zxc_dict_huf_state_t));
234 }
235 return layout;
236}
237
245size_t zxc_cctx_compute_workspace_size(const size_t chunk_size, const int mode, const int level,
246 const size_t dict_size) {
247 if (UNLIKELY(chunk_size == 0)) return 0;
248 return compute_cctx_layout(chunk_size, mode, level, dict_size, 0).total;
249}
250
260int zxc_cctx_init_in_workspace(zxc_cctx_t* RESTRICT ctx, void* RESTRICT workspace,
261 const size_t workspace_size, const size_t chunk_size, const int mode,
262 const int level, const int checksum_enabled, const size_t dict_size,
263 const int defer_entropy_scratch) {
264 if (UNLIKELY(!ctx || !workspace || chunk_size == 0)) return ZXC_ERROR_NULL_INPUT;
265
266 const zxc_cctx_layout_t layout =
267 compute_cctx_layout(chunk_size, mode, level, dict_size, defer_entropy_scratch);
268 if (UNLIKELY(workspace_size < layout.total)) return ZXC_ERROR_DST_TOO_SMALL;
269
270 ZXC_MEMSET(ctx, 0, sizeof(zxc_cctx_t));
271 ctx->checksum_enabled = checksum_enabled;
272 ctx->chunk_size = chunk_size;
273 const uint32_t offset_bits = zxc_log2_u32((uint32_t)chunk_size);
274 ctx->offset_bits = offset_bits;
275 ctx->offset_mask = (uint32_t)((1ULL << offset_bits) - 1);
276 ctx->max_epoch = (uint32_t)(1ULL << (32 - offset_bits));
277
278 // memory_block stays NULL on the static-init path so zxc_cctx_free does
279 // not try to free the caller's workspace. Sub-buffer pointers carry the
280 // partition; ownership is implicit (the caller owns @p workspace).
281 uint8_t* const mem = (uint8_t*)workspace;
282
283 // Dictionary concat scratch (both modes); init owns dict_size now so callers
284 // no longer assign ctx->dict_size after init.
285 ctx->dict_size = dict_size;
286 if (dict_size > 0) {
287 ctx->dict_buffer = mem + layout.off_dict;
288 ctx->dict_buffer_cap = layout.sz_dict;
289 ctx->dict_huf = (zxc_dict_huf_state_t*)(void*)(mem + layout.off_dict_huf);
290 }
291
292 if (mode == 0) {
293 ctx->work_buf = mem + layout.off_work;
294 ctx->work_buf_cap = chunk_size + ZXC_DECOMPRESS_TAIL_PAD;
295 ctx->lit_buffer = mem + layout.off_lit_dctx;
296 ctx->lit_buffer_cap = chunk_size + ZXC_PAD_SIZE;
297 if (layout.sz_pivco_dctx) {
298 ctx->tok_buffer = mem + layout.off_tok_dctx;
299 ctx->tok_buffer_cap = layout.sz_tok_dctx;
300 ctx->pivco_scratch = mem + layout.off_pivco_dctx;
301 ctx->pivco_scratch_cap = layout.sz_pivco_dctx;
302 }
303 return ZXC_OK;
304 }
305
306 ctx->hash_table = (uint32_t*)(mem + layout.off_hash_pos);
307 ctx->hash_tags = mem + layout.off_hash_tags;
308 ctx->chain_table = (uint16_t*)(mem + layout.off_chain);
309 ctx->buf_sequences = (uint32_t*)(mem + layout.off_seq_union);
310 ctx->buf_offsets = (uint16_t*)(mem + layout.off_seq_union);
311 ctx->buf_tokens = mem + layout.off_seq_union + layout.max_seq * sizeof(uint16_t);
312 ctx->buf_split = ctx->buf_tokens + layout.max_seq;
313 ctx->buf_split_hist = (zxc_glo_split_hist_t*)(void*)(mem + layout.off_split_hist);
314 ctx->buf_extras = mem + layout.off_extras;
315 ctx->literals = mem + layout.off_lit_cctx;
316 if (layout.sz_opt) {
317 ctx->opt_scratch = mem + layout.off_opt;
318 ctx->opt_scratch_cap = layout.sz_opt;
319 }
320
321 ctx->compression_level = level;
322 ctx->epoch = 1;
323
324 ZXC_MEMSET(ctx->hash_table, 0, layout.sz_hash_pos);
325 ZXC_MEMSET(ctx->hash_tags, 0, layout.sz_hash_tags);
326 return ZXC_OK;
327}
328
339int zxc_cctx_init(zxc_cctx_t* RESTRICT ctx, const size_t chunk_size, const int mode,
340 const int level, const int checksum_enabled, const size_t dict_size) {
341 if (UNLIKELY(chunk_size == 0)) return ZXC_ERROR_NULL_INPUT;
342 // Heap contexts defer the decode-side entropy scratch to the first entropy
343 // section; static workspaces must have it already, they may never allocate.
344 const int defer_entropy = (mode == 0);
345 const size_t total =
346 compute_cctx_layout(chunk_size, mode, level, dict_size, defer_entropy).total;
347 if (UNLIKELY(total == 0)) return ZXC_ERROR_NULL_INPUT;
348
349 uint8_t* const mem = (uint8_t*)ZXC_ALIGNED_MALLOC(total, ZXC_CACHE_LINE_SIZE);
350 if (UNLIKELY(!mem)) return ZXC_ERROR_MEMORY;
351
352 const int rc = zxc_cctx_init_in_workspace(ctx, mem, total, chunk_size, mode, level,
353 checksum_enabled, dict_size, defer_entropy);
354 if (UNLIKELY(rc != ZXC_OK)) {
355 // LCOV_EXCL_START
356 ZXC_ALIGNED_FREE(mem);
357 return rc;
358 // LCOV_EXCL_STOP
359 }
360 // Library-owned buffer: record the allocation so zxc_cctx_free frees it.
361 ctx->memory_block = mem;
362 return ZXC_OK;
363}
364
374 if (LIKELY(ctx->pivco_scratch)) return ZXC_OK;
375
376 // Sized for the block the context was carved for, not chunk_size: a reader
377 // over frames of several block sizes lowers chunk_size per block.
378 size_t sz_tok = 0;
379 size_t sz_pivco = 0;
381 const size_t total = ZXC_ALIGN_CL(sz_tok) + ZXC_ALIGN_CL(sz_pivco);
382
383 uint8_t* const mem = (uint8_t*)ZXC_ALIGNED_MALLOC(total, ZXC_CACHE_LINE_SIZE);
384 if (UNLIKELY(!mem)) return ZXC_ERROR_MEMORY; // LCOV_EXCL_LINE
385
386 ctx->entropy_block = mem;
387 ctx->tok_buffer = mem;
388 ctx->tok_buffer_cap = sz_tok;
389 ctx->pivco_scratch = mem + ZXC_ALIGN_CL(sz_tok);
390 ctx->pivco_scratch_cap = sz_pivco;
391 return ZXC_OK;
392}
393
401 if (ctx->memory_block) {
403 ctx->memory_block = NULL;
404 }
405 if (ctx->entropy_block) {
407 ctx->entropy_block = NULL;
408 }
409
410 ctx->lit_buffer = NULL;
411 ctx->hash_table = NULL;
412 ctx->hash_tags = NULL;
413 ctx->chain_table = NULL;
414 ctx->buf_sequences = NULL;
415 ctx->buf_tokens = NULL;
416 ctx->buf_offsets = NULL;
417 ctx->buf_extras = NULL;
418 ctx->buf_split = NULL;
419 ctx->buf_split_hist = NULL;
420 ctx->literals = NULL;
421 ctx->work_buf = NULL;
422 ctx->tok_buffer = NULL;
423 ctx->pivco_scratch = NULL;
424 ctx->opt_scratch = NULL;
425 ctx->dict_buffer = NULL;
426 ctx->dict_huf = NULL;
427
428 ctx->epoch = 0;
429 ctx->lit_buffer_cap = 0;
430 ctx->work_buf_cap = 0;
431 ctx->tok_buffer_cap = 0;
432 ctx->pivco_scratch_cap = 0;
433 ctx->opt_scratch_cap = 0;
434 ctx->dict_buffer_cap = 0;
435 ctx->dict_size = 0;
436 ctx->dict_huf_tree_ok = 0;
437 ctx->lit_freq_acc = NULL;
438}
439
453int zxc_cctx_attach_dict_huf(zxc_cctx_t* RESTRICT ctx, const uint8_t* RESTRICT lengths) {
454 if (UNLIKELY(!ctx)) return ZXC_ERROR_NULL_INPUT;
455 ctx->dict_huf_tree_ok = 0;
456 if (!lengths) return ZXC_OK;
457
458 // Empty (all-zero) table from a low-entropy corpus: treat it as "no shared table".
459 int empty = 1;
460 for (size_t i = 0; i < ZXC_HUF_TABLE_SIZE; i++) {
461 if (lengths[i]) {
462 empty = 0;
463 break;
464 }
465 }
466 if (UNLIKELY(empty || !ctx->dict_huf)) return ZXC_OK;
467
468 // Tree-at-attach: built once, reused by every block.
469 const int rc = zxc_huf_dict_tree_build(lengths, &ctx->dict_huf->tree, ctx->dict_huf->codes,
470 ctx->dict_huf->code_len);
471 if (UNLIKELY(rc != ZXC_OK)) return rc;
472 ctx->dict_huf_tree_ok = 1;
473 return ZXC_OK;
474}
475
476// ============================================================================
477// HEADER I/O
478// ============================================================================
479
486int zxc_write_file_header(uint8_t* RESTRICT dst, const size_t dst_capacity, const size_t chunk_size,
487 const int has_checksum, const uint32_t dict_id, const int has_seek) {
488 if (UNLIKELY(dst_capacity < ZXC_FILE_HEADER_SIZE)) return ZXC_ERROR_DST_TOO_SMALL;
489
492
493 // Block size stored as log2 exponent (e.g. 18 = 256 KB)
494 dst[5] = (uint8_t)zxc_log2_u32((uint32_t)chunk_size);
495
496 uint8_t flags = has_checksum ? (ZXC_FILE_FLAG_HAS_CHECKSUM | ZXC_CHECKSUM_RAPIDHASH) : 0;
497 if (dict_id != 0) flags |= ZXC_FILE_FLAG_HAS_DICTIONARY;
498 if (has_seek) flags |= ZXC_FILE_FLAG_HAS_SEEK_TABLE;
499 dst[6] = flags;
500
501 // Bytes 7-13: Reserved / Dictionary ID
502 ZXC_MEMSET(dst + 7, 0, 7);
503 if (dict_id != 0) zxc_store_le32(dst + 7, dict_id);
504
505 // Bytes 14-15: Header Checksum (16-bit)
507
509}
510
516int zxc_read_file_header(const uint8_t* RESTRICT src, const size_t src_size,
517 size_t* RESTRICT out_block_size, int* RESTRICT out_has_checksum,
518 uint32_t* RESTRICT out_dict_id, int* RESTRICT out_has_seek) {
519 if (UNLIKELY(src_size < ZXC_FILE_HEADER_SIZE)) return ZXC_ERROR_SRC_TOO_SMALL;
520 if (UNLIKELY(zxc_le32(src) != ZXC_MAGIC_WORD)) return ZXC_ERROR_BAD_MAGIC;
521 if (UNLIKELY(src[4] != ZXC_FILE_FORMAT_VERSION)) return ZXC_ERROR_BAD_VERSION;
522
523 uint8_t temp[ZXC_FILE_HEADER_SIZE];
524 ZXC_MEMCPY(temp, src, ZXC_FILE_HEADER_SIZE);
525 // Zero out checksum bytes (14-15) before hash check
526 temp[14] = 0;
527 temp[15] = 0;
528 // Header checksum (integrity), then the checksum-algorithm id in flags bits 0-3
529 // (only 0 = RapidHash is defined). It is checked first via short-circuit.
530 if (UNLIKELY(zxc_le16(src + 14) != zxc_hash16(temp) ||
533
534 if (out_block_size) {
535 const uint8_t code = src[5];
536 if (UNLIKELY(code < ZXC_BLOCK_SIZE_MIN_LOG2 || code > ZXC_BLOCK_SIZE_MAX_LOG2))
538 // Exponent encoding: block_size = 2^code (4 KB - 2 MB)
539 *out_block_size = (size_t)1U << code;
540 }
541 if (out_has_checksum) *out_has_checksum = (src[6] & ZXC_FILE_FLAG_HAS_CHECKSUM) ? 1 : 0;
542 if (out_dict_id) *out_dict_id = (src[6] & ZXC_FILE_FLAG_HAS_DICTIONARY) ? zxc_le32(src + 7) : 0;
543 if (out_has_seek) *out_has_seek = (src[6] & ZXC_FILE_FLAG_HAS_SEEK_TABLE) ? 1 : 0;
544
545 return ZXC_OK;
546}
547
549static int zxc_scan_read(const zxc_scan_src_t* src, const uint64_t off, void* dst,
550 const size_t len) {
551 if (UNLIKELY(off > src->size || (uint64_t)len > src->size - off))
553 if (src->read_at) return src->read_at(src, off, dst, len);
554 ZXC_MEMCPY(dst, src->data + off, len);
555 return ZXC_OK;
556}
557
558int zxc_container_next(const zxc_scan_src_t* src, const uint64_t pos) {
559 if (pos == src->size) return 0;
560 uint8_t m[sizeof(uint32_t)];
561 if (src->size - pos >= sizeof(m)) {
562 const int rc = zxc_scan_read(src, pos, m, sizeof(m));
563 if (UNLIKELY(rc != ZXC_OK)) return rc;
564 if (zxc_magic_prefix_ok(m, sizeof(m))) return 1;
565 }
567}
568
569int zxc_scan_frame(const zxc_scan_src_t* src, const uint64_t end, uint64_t* start,
570 zxc_frame_info_t* info, size_t* footer_len) {
571 uint8_t h[ZXC_FILE_HEADER_SIZE];
572 int rc;
573 // Too short for a frame: what is left starts the input, its header speaks first.
574 if (UNLIKELY(end < ZXC_FRAME_MIN_SIZE)) {
575 const size_t n = end < sizeof(h) ? (size_t)end : sizeof(h);
576 rc = zxc_scan_read(src, 0, h, n);
577 return rc != ZXC_OK ? rc : zxc_short_input_verdict(h, n);
578 }
579 uint8_t tail[ZXC_FOOTER_MAX_SIZE_WITH_DIGEST]; // end > sizeof(tail)
580 rc = zxc_scan_read(src, end - sizeof(tail), tail, sizeof(tail));
581 if (UNLIKELY(rc != ZXC_OK)) return rc;
582 uint64_t dsize = 0;
583 uint64_t frame = 0;
584 size_t sizes = 0;
585 rc = zxc_parse_file_footer(tail + sizeof(tail), sizeof(tail), &dsize, &frame, &sizes);
586 if (UNLIKELY(rc != ZXC_OK)) return rc;
587 if (UNLIKELY(frame < ZXC_FRAME_MIN_SIZE || frame > end)) return ZXC_ERROR_CORRUPT_DATA;
588
589 const uint64_t at = end - frame;
590 rc = zxc_scan_read(src, at, h, sizeof(h));
591 if (UNLIKELY(rc != ZXC_OK)) return rc;
592 const size_t tl = zxc_frame_tail_len(frame);
593 rc = zxc_read_frame_info(h, tail + sizeof(tail) - tl, tl, frame, info, footer_len);
594 // Past the first frame, a wrong magic word is a broken container.
595 if (UNLIKELY(rc == ZXC_ERROR_BAD_MAGIC && at != 0)) return ZXC_ERROR_CORRUPT_DATA;
596 if (UNLIKELY(rc != ZXC_OK)) return rc;
597 *start = at;
598 return ZXC_OK;
599}
600
601int zxc_scan_whole_frame(const zxc_scan_src_t* src, zxc_frame_info_t* info, size_t* footer_len) {
602 uint8_t h[ZXC_FILE_HEADER_SIZE];
603 const size_t n = src->size < sizeof(h) ? (size_t)src->size : sizeof(h);
604 int rc = zxc_scan_read(src, 0, h, n);
605 if (UNLIKELY(rc != ZXC_OK)) return rc;
606 rc = zxc_short_input_verdict(h, n); // the header speaks first
607 if (UNLIKELY(rc != ZXC_ERROR_SRC_TOO_SMALL || src->size < ZXC_FRAME_MIN_SIZE)) return rc;
608 uint64_t start = 0;
609 rc = zxc_scan_frame(src, src->size, &start, info, footer_len);
610 return rc == ZXC_OK && start != 0 ? ZXC_ERROR_CORRUPT_DATA : rc;
611}
612
613int zxc_scan_container(const zxc_scan_src_t* src, const size_t req_chunk,
614 zxc_container_info_t* info) {
615 ZXC_MEMSET(info, 0, sizeof(*info));
616 // The first header speaks first, as for a single frame: junk is a bad magic word.
617 uint8_t h[ZXC_FILE_HEADER_SIZE];
618 const size_t n = src->size < sizeof(h) ? (size_t)src->size : sizeof(h);
619 size_t chunk = 0;
620 int hrc = zxc_scan_read(src, 0, h, n);
621 if (hrc == ZXC_OK) hrc = zxc_read_file_header(h, n, &chunk, NULL, NULL, NULL);
622 if (UNLIKELY(hrc != ZXC_OK)) return hrc;
623
624 uint64_t end = src->size;
625 do {
627 uint64_t start = 0;
628 const int rc = zxc_scan_frame(src, end, &start, &fi, NULL);
629 if (UNLIKELY(rc != ZXC_OK)) return rc;
630 if (UNLIKELY(req_chunk && fi.block_size != req_chunk)) return ZXC_ERROR_BAD_BLOCK_SIZE;
631 if (UNLIKELY(fi.decompressed_size > UINT64_MAX - info->dsize))
633 info->dsize += fi.decompressed_size;
634 info->frames++;
635 info->first_dict_id = fi.dict_id; // walking back, the last one seen is first
636 if (fi.dict_id) info->uses_dict = 1;
637 end = start;
638 } while (end > 0);
639 return ZXC_OK;
640}
641
648int zxc_read_frame_info(const uint8_t* header, const uint8_t* tail, const size_t tail_len,
649 const uint64_t total, zxc_frame_info_t* info, size_t* footer_len) {
650 size_t chunk = 0;
651 int cs = 0;
652 int seek = 0;
653 uint32_t did = 0;
654 const int hrc = zxc_read_file_header(header, ZXC_FILE_HEADER_SIZE, &chunk, &cs, &did, &seek);
655 if (UNLIKELY(hrc != ZXC_OK)) return hrc;
656
657 uint64_t stored = 0;
658 uint64_t frame = 0;
659 size_t sizes = 0;
660 const int frc = zxc_parse_file_footer(tail + tail_len, tail_len, &stored, &frame, &sizes);
661 if (UNLIKELY(frc != ZXC_OK)) return frc;
662 const size_t flen = sizes + (cs ? (size_t)ZXC_FILE_DIGEST_SIZE : 0U);
663 // All the frame's bytes are there: a footer longer than they allow is forged.
664 if (UNLIKELY(flen > tail_len)) return ZXC_ERROR_CORRUPT_DATA;
665 if (UNLIKELY(frame != total || !zxc_footer_dsize_plausible(stored, chunk, total)))
667
668 ZXC_MEMSET(info, 0, sizeof(*info)); // padding too: zxc_frame_info_copy() hands it out
669 info->decompressed_size = stored;
670 info->compressed_size = frame;
671 info->digest = cs ? zxc_le64(tail + tail_len - flen) : 0;
672 info->block_size = chunk;
673 info->dict_id = did;
674 info->format_version = header[4];
675 info->has_checksum = (uint8_t)cs;
676 info->has_seek_table = (uint8_t)seek;
677 if (footer_len) *footer_len = flen;
678 return ZXC_OK;
679}
680
684int zxc_write_block_header(uint8_t* RESTRICT dst, const size_t dst_capacity,
685 const zxc_block_header_t* RESTRICT bh) {
686 if (UNLIKELY(dst_capacity < ZXC_BLOCK_HEADER_SIZE)) return ZXC_ERROR_DST_TOO_SMALL;
687
688 dst[0] = bh->block_type;
689 dst[1] = 0; // Flags not used currently
690 dst[2] = 0; // Reserved
691 zxc_store_le32(dst + 3, bh->comp_size);
692 dst[7] = 0; // Zero before hashing
693 dst[7] = zxc_hash8(dst); // Checksum at the end
694
696}
697
703int zxc_read_block_header(const uint8_t* RESTRICT src, const size_t src_size,
704 zxc_block_header_t* RESTRICT bh) {
705 if (UNLIKELY(src_size < ZXC_BLOCK_HEADER_SIZE)) return ZXC_ERROR_SRC_TOO_SMALL;
706
707 uint8_t temp[ZXC_BLOCK_HEADER_SIZE];
708 ZXC_MEMCPY(temp, src, ZXC_BLOCK_HEADER_SIZE);
709 temp[7] = 0; // Zero out checksum byte before hashing
710 if (UNLIKELY(src[7] != zxc_hash8(temp))) return ZXC_ERROR_BAD_HEADER;
711
712 bh->block_type = src[0];
713 bh->block_flags = 0; // Flags not used currently
714 bh->reserved = src[2];
715 bh->comp_size = zxc_le32(src + 3);
716 bh->header_checksum = src[7];
717
718 return ZXC_OK;
719}
720
721// =========================================================================
722// SEEK TABLE WRITER (a frame block; zxc_seekable.c is the random-access reader)
723// =========================================================================
724
731size_t zxc_seek_table_size(const uint64_t num_blocks) {
732 if (UNLIKELY(num_blocks > UINT64_MAX / ZXC_SEEK_ANCHOR_SIZE)) return 0;
733 const uint64_t bytes = zxc_seek_table_bytes(num_blocks);
734 if (UNLIKELY(bytes > SIZE_MAX - ZXC_BLOCK_HEADER_SIZE)) return 0;
735 return ZXC_BLOCK_HEADER_SIZE + (size_t)bytes;
736}
737
738int zxc_seek_table_header(uint8_t* dst, const size_t dst_capacity, const uint64_t num_blocks) {
739 // The field keeps the table size modulo 2^32; readers derive the count from the footer.
740 const zxc_block_header_t bh = {
741 .block_type = ZXC_BLOCK_SEK,
742 .block_flags = 0,
743 .reserved = 0,
744 .comp_size = zxc_seek_size_field(zxc_seek_table_bytes(num_blocks))};
745 return zxc_write_block_header(dst, dst_capacity, &bh);
746}
747
748int zxc_check_eof_header(const uint8_t* hdr) {
749 const zxc_block_header_t eof = {
750 .block_type = ZXC_BLOCK_EOF, .block_flags = 0, .reserved = 0, .comp_size = 0};
751 uint8_t want[ZXC_BLOCK_HEADER_SIZE];
752 if (UNLIKELY(zxc_write_block_header(want, sizeof(want), &eof) < 0))
753 return ZXC_ERROR_BAD_HEADER; // LCOV_EXCL_LINE
754 return memcmp(hdr, want, sizeof(want)) == 0 ? ZXC_OK : ZXC_ERROR_BAD_HEADER;
755}
756
757int zxc_check_seek_header(const uint8_t* hdr, const size_t avail, const uint64_t total_out,
758 const size_t block_size, uint64_t* sek_bytes) {
759 const uint64_t n = zxc_seek_block_count(total_out, block_size);
760 uint8_t want[ZXC_BLOCK_HEADER_SIZE];
761 if (UNLIKELY(zxc_seek_table_header(want, sizeof(want), n) < 0))
762 return ZXC_ERROR_CORRUPT_DATA; // LCOV_EXCL_LINE
763 const size_t m = avail < sizeof(want) ? avail : sizeof(want);
764 if (memcmp(hdr, want, m) != 0) return ZXC_ERROR_CORRUPT_DATA;
765 if (avail < sizeof(want)) return ZXC_ERROR_SRC_TOO_SMALL;
766 if (sek_bytes) *sek_bytes = zxc_seek_table_bytes(n);
767 return ZXC_OK;
768}
769
770size_t zxc_seek_write_group(uint8_t* RESTRICT dst, uint64_t* RESTRICT anchor,
771 const uint32_t* RESTRICT sizes, const uint32_t cnt) {
772 zxc_store_le64(dst, *anchor);
773 uint8_t* p = dst + ZXC_SEEK_ANCHOR_SIZE;
774 for (uint32_t k = 0; k < cnt; k++, p += ZXC_SEEK_SIZE_ENTRY) {
775 zxc_store_le32(p, sizes[k]);
776 *anchor += sizes[k];
777 }
778 return (size_t)(p - dst);
779}
780
786int64_t zxc_write_seek_table(uint8_t* dst, const size_t dst_capacity, const uint32_t* comp_sizes,
787 const uint64_t num_blocks) {
788 const size_t total = zxc_seek_table_size(num_blocks);
789 if (UNLIKELY(total == 0)) return ZXC_ERROR_OVERFLOW;
790 if (UNLIKELY(dst_capacity < total)) return ZXC_ERROR_DST_TOO_SMALL;
791 if (UNLIKELY(!dst || !comp_sizes)) return ZXC_ERROR_NULL_INPUT;
792
793 const int hdr_res = zxc_seek_table_header(dst, dst_capacity, num_blocks);
794 if (UNLIKELY(hdr_res < 0)) return hdr_res;
795 uint8_t* p = dst + hdr_res;
796
797 uint64_t anchor = ZXC_FILE_HEADER_SIZE;
798 for (uint64_t i = 0; i < num_blocks; i += ZXC_SEEK_GROUP)
799 p += zxc_seek_write_group(p, &anchor, comp_sizes + i,
800 zxc_seek_group_len(num_blocks, i / ZXC_SEEK_GROUP));
801
802 return (int64_t)(p - dst);
803}
804
806static void zxc_store_le_n(uint8_t* dst, uint64_t v, const size_t n) {
807 for (size_t i = 0; i < n; i++, v >>= 8) dst[i] = (uint8_t)v;
808}
809
811static uint64_t zxc_load_le_n(const uint8_t* src, const size_t n) {
812 uint64_t v = 0;
813 for (size_t i = n; i > 0; i--) v = (v << 8) | src[i - 1];
814 return v;
815}
816
818static void zxc_encode_footer(uint8_t* dst, const zxc_footer_layout_t* l, const uint64_t src_size,
819 const uint64_t digest) {
820 if (l->skip) zxc_store_le64(dst, digest);
821 uint8_t* p = dst + l->skip;
822 zxc_store_le_n(p, src_size, l->nd);
823 p += l->nd;
825 p[l->nf] = (uint8_t)((l->nd - 1) | (l->nf - 1) << 4);
826}
827
832int zxc_write_file_footer(uint8_t* RESTRICT dst, const size_t dst_capacity, const uint64_t prefix,
833 const uint64_t src_size, const uint64_t digest,
834 const int checksum_enabled) {
835 const zxc_footer_layout_t l = zxc_footer_layout(prefix, src_size, checksum_enabled);
836 if (UNLIKELY(dst_capacity < l.len)) return ZXC_ERROR_DST_TOO_SMALL;
837 zxc_encode_footer(dst, &l, src_size, digest);
838 return (int)l.len;
839}
840
847int zxc_check_file_footer(const uint8_t* footer, const size_t avail, const zxc_footer_layout_t* l,
848 const uint64_t src_size, uint64_t* digest) {
850 zxc_encode_footer(want, l, src_size, 0);
851 const size_t n = avail < l->len ? avail : l->len;
852 if (n > l->skip && memcmp(footer + l->skip, want + l->skip, n - l->skip) != 0)
854 if (avail < l->len) return ZXC_ERROR_SRC_TOO_SMALL;
855 if (digest) *digest = l->skip ? zxc_le64(footer) : 0;
856 return ZXC_OK;
857}
858
865int zxc_parse_file_footer(const uint8_t* end, const size_t avail, uint64_t* src_size,
866 uint64_t* compressed_size, size_t* sizes_len) {
867 if (UNLIKELY(avail < ZXC_FILE_FOOTER_MIN_SIZE)) return ZXC_ERROR_SRC_TOO_SMALL;
868 const uint8_t L = end[-1];
869 if (UNLIKELY(L & 0x88U)) return ZXC_ERROR_CORRUPT_DATA;
870 const size_t nd = (size_t)(L & 0x07U) + 1;
871 const size_t nf = (size_t)(L >> 4) + 1;
872 const size_t need = nd + nf + 1;
873 if (UNLIKELY(avail < need)) return ZXC_ERROR_SRC_TOO_SMALL;
874 const uint64_t d = zxc_load_le_n(end - need, nd);
875 const uint64_t f = zxc_load_le_n(end - 1 - nf, nf);
876 // Minimal lengths: d on exactly nd bytes; f too, and nf the fewest that hold
877 // the total, which one byte less would not (f counts its own bytes).
878 if (UNLIKELY(zxc_uint_bytes(d) != nd || zxc_uint_bytes(f) != nf ||
879 (nf > 1 && zxc_uint_bytes(f - 1) < nf)))
881 *src_size = d;
882 *compressed_size = f;
883 *sizes_len = need;
884 return ZXC_OK;
885}
886
893static ZXC_ALWAYS_INLINE void zxc_write_gnr_header(uint8_t* RESTRICT dst,
894 const zxc_gnr_header_t* RESTRICT gh) {
895 zxc_store_le32(dst, gh->n_sequences);
896 zxc_store_le32(dst + 4, gh->n_literals);
897
898 dst[8] = gh->enc_lit;
899 dst[9] = gh->enc_tok;
900 dst[10] = gh->enc_mlen;
901 dst[11] = gh->enc_off;
902}
903
910static ZXC_ALWAYS_INLINE void zxc_read_gnr_header(const uint8_t* RESTRICT src,
911 zxc_gnr_header_t* RESTRICT gh) {
912 gh->n_sequences = zxc_le32(src);
913 gh->n_literals = zxc_le32(src + 4);
914 gh->enc_lit = src[8];
915 gh->enc_tok = src[9];
916 gh->enc_mlen = src[10];
917 gh->enc_off = src[11];
918}
919
926static ZXC_ALWAYS_INLINE size_t zxc_glo_desc_size(const uint8_t enc_lit, const uint8_t enc_tok) {
927 return ((enc_lit != ZXC_SECTION_ENCODING_RAW) ? sizeof(uint32_t) : 0) +
928 ((enc_tok == ZXC_SECTION_ENCODING_HUFFMAN) ? sizeof(uint32_t) : 0);
929}
930
940int zxc_write_glo_header_and_desc(uint8_t* RESTRICT dst, const size_t rem,
941 const zxc_gnr_header_t* RESTRICT gh, const uint32_t lit_comp,
942 const uint32_t tok_comp) {
943 const size_t desc_sz = zxc_glo_desc_size(gh->enc_lit, gh->enc_tok);
944 const size_t needed = ZXC_GLO_HEADER_BINARY_SIZE + desc_sz;
945
946 if (UNLIKELY(rem < needed)) return ZXC_ERROR_DST_TOO_SMALL;
947
948 zxc_write_gnr_header(dst, gh);
949 uint8_t* p = dst + ZXC_GLO_HEADER_BINARY_SIZE;
950
951 if (gh->enc_lit != ZXC_SECTION_ENCODING_RAW) {
952 zxc_store_le32(p, lit_comp);
953 p += sizeof(uint32_t);
954 }
955 if (gh->enc_tok == ZXC_SECTION_ENCODING_HUFFMAN) {
956 zxc_store_le32(p, tok_comp);
957 }
958
959 return (int)needed;
960}
961
965int zxc_read_glo_header_and_desc(const uint8_t* RESTRICT src, const size_t len,
966 zxc_gnr_header_t* RESTRICT gh, uint32_t* RESTRICT lit_comp,
967 uint32_t* RESTRICT tok_comp) {
968 if (UNLIKELY(len < ZXC_GLO_HEADER_BINARY_SIZE)) return ZXC_ERROR_SRC_TOO_SMALL;
969
970 zxc_read_gnr_header(src, gh);
971
972 const size_t desc_sz = zxc_glo_desc_size(gh->enc_lit, gh->enc_tok);
973 const size_t needed = ZXC_GLO_HEADER_BINARY_SIZE + desc_sz;
974 if (UNLIKELY(len < needed)) return ZXC_ERROR_SRC_TOO_SMALL;
975
976 const uint8_t* p = src + ZXC_GLO_HEADER_BINARY_SIZE;
977
978 if (gh->enc_lit != ZXC_SECTION_ENCODING_RAW) {
979 *lit_comp = zxc_le32(p);
980 p += sizeof(uint32_t);
981 } else {
982 *lit_comp = gh->n_literals;
983 }
984 *tok_comp = (gh->enc_tok == ZXC_SECTION_ENCODING_HUFFMAN) ? zxc_le32(p) : gh->n_sequences;
985
986 return (int)needed;
987}
988
997int zxc_write_ghi_header(uint8_t* RESTRICT dst, const size_t rem,
998 const zxc_gnr_header_t* RESTRICT gh) {
999 if (UNLIKELY(rem < ZXC_GHI_HEADER_BINARY_SIZE)) return ZXC_ERROR_DST_TOO_SMALL;
1000
1001 zxc_write_gnr_header(dst, gh);
1003}
1004
1008int zxc_read_ghi_header(const uint8_t* RESTRICT src, const size_t len,
1009 zxc_gnr_header_t* RESTRICT gh) {
1010 if (UNLIKELY(len < ZXC_GHI_HEADER_BINARY_SIZE)) return ZXC_ERROR_SRC_TOO_SMALL;
1011
1012 zxc_read_gnr_header(src, gh);
1013 return ZXC_OK;
1014}
1015
1016// ============================================================================
1017// COMPRESS BOUND CALCULATION
1018// ============================================================================
1029uint64_t zxc_compress_bound(const size_t input_size) {
1030 // Guard against uint64 overflow when summing per-block overhead
1031 // across very large inputs (input_size approaching SIZE_MAX).
1032 if (UNLIKELY(input_size > (SIZE_MAX - (SIZE_MAX >> 8)))) return 0;
1033 uint64_t n = ((uint64_t)input_size + ZXC_BLOCK_SIZE_MIN - 1) / ZXC_BLOCK_SIZE_MIN;
1034 if (n == 0) n = 1;
1035 return ZXC_FILE_HEADER_SIZE +
1037 (uint64_t)input_size + ZXC_BLOCK_HEADER_SIZE + /* EOF block */
1038 ZXC_BLOCK_HEADER_SIZE + /* SEK block header (seekable) */
1039 zxc_seek_table_bytes(n) + /* SEK groups (seekable) */
1040 ZXC_FOOTER_MAX_SIZE_WITH_DIGEST; /* optional digest + footer */
1041}
1042
1046uint64_t zxc_compress_block_bound(const size_t input_size) {
1047 // Mirrors the Block API contract: outside [1, ZXC_BLOCK_SIZE_MAX] the call
1048 // would fail anyway, so the bound is undefined and 0 says "unusable". The cap
1049 // also makes the addition below trivially overflow-free.
1050 if (UNLIKELY(input_size == 0 || input_size > ZXC_BLOCK_SIZE_MAX)) return 0;
1051 // Outer block header + payload (worst case: incompressible, raw bytes)
1052 // + inner format overhead + optional checksum.
1053 return (uint64_t)ZXC_BLOCK_HEADER_SIZE + (uint64_t)input_size + ZXC_BLOCK_FORMAT_OVERHEAD +
1055}
1056
1068uint64_t zxc_decompress_block_bound(const size_t uncompressed_size) {
1069 if (UNLIKELY(uncompressed_size > ZXC_BLOCK_SIZE_MAX)) return 0;
1070 return (uint64_t)uncompressed_size + ZXC_DECOMPRESS_TAIL_PAD;
1071}
1072
1087uint64_t zxc_estimate_cctx_size(const size_t src_size, const int level) {
1088 if (UNLIKELY(src_size == 0)) return 0;
1089 const size_t chunk_size = zxc_block_size_ceil(src_size);
1090 return (uint64_t)zxc_cctx_compute_workspace_size(chunk_size, 1, level, 0);
1091}
1092
1093// ============================================================================
1094// ERROR CODE UTILITIES
1095// ============================================================================
1096
1100const char* zxc_error_name(const int code) {
1101 switch ((zxc_error_t)code) {
1102 case ZXC_OK:
1103 return "ZXC_OK";
1104 case ZXC_ERROR_MEMORY:
1105 return "ZXC_ERROR_MEMORY";
1107 return "ZXC_ERROR_DST_TOO_SMALL";
1109 return "ZXC_ERROR_SRC_TOO_SMALL";
1111 return "ZXC_ERROR_BAD_MAGIC";
1113 return "ZXC_ERROR_BAD_VERSION";
1115 return "ZXC_ERROR_BAD_HEADER";
1117 return "ZXC_ERROR_BAD_CHECKSUM";
1119 return "ZXC_ERROR_CORRUPT_DATA";
1121 return "ZXC_ERROR_BAD_OFFSET";
1122 case ZXC_ERROR_OVERFLOW:
1123 return "ZXC_ERROR_OVERFLOW";
1124 case ZXC_ERROR_IO:
1125 return "ZXC_ERROR_IO";
1127 return "ZXC_ERROR_NULL_INPUT";
1129 return "ZXC_ERROR_BAD_BLOCK_TYPE";
1131 return "ZXC_ERROR_BAD_BLOCK_SIZE";
1133 return "ZXC_ERROR_DICT_REQUIRED";
1135 return "ZXC_ERROR_DICT_MISMATCH";
1137 return "ZXC_ERROR_DICT_TOO_LARGE";
1139 return "ZXC_ERROR_BAD_LEVEL";
1141 return "ZXC_ERROR_DICT_UNSUPPORTED";
1142 default:
1143 return "ZXC_UNKNOWN_ERROR";
1144 }
1145}
1146
1147// ============================================================================
1148// LIBRARY INFORMATION
1149// ============================================================================
1150
1159
1165int zxc_max_level(void) { return ZXC_LEVEL_ULTRA; }
1166
1173
1180const char* zxc_version_string(void) { return ZXC_LIB_VERSION_STR; }
uint64_t zxc_compress_block_bound(const size_t input_size)
Returns the maximum compressed size for a single block (no file framing).
uint64_t zxc_estimate_cctx_size(const size_t src_size, const int level)
Estimates the total buffer bytes allocated inside a cctx for a block.
uint64_t zxc_decompress_block_bound(const size_t uncompressed_size)
Returns the minimum dst_capacity required by zxc_decompress_block().
#define ZXC_BLOCK_SIZE_MAX_LOG2
log2(ZXC_BLOCK_SIZE_MAX) - exponent code for maximum block size.
#define ZXC_BLOCK_SIZE_MAX
Maximum allowed block size (2 MB = 2^21).
#define ZXC_BLOCK_SIZE_MIN
Minimum allowed block size (4 KB = 2^12).
size_t zxc_frame_info_size(void)
Returns sizeof(zxc_frame_info_t) for ABI-safe mirroring.
Definition zxc_common.c:51
uint64_t zxc_compress_bound(const size_t input_size)
Returns the maximum compressed size for a given input size.
#define ZXC_HUF_TABLE_SIZE
Size of a packed literal Huffman code-lengths table (256 symbols, 4 bits each): the shared table in a...
const char * zxc_error_name(const int code)
Returns a human-readable string for the given error code.
zxc_error_t
Error codes returned by ZXC library functions.
Definition zxc_error.h:37
@ ZXC_ERROR_BAD_MAGIC
Invalid magic word in file header.
Definition zxc_error.h:48
@ ZXC_ERROR_BAD_VERSION
Unsupported file format version.
Definition zxc_error.h:49
@ ZXC_ERROR_BAD_BLOCK_TYPE
Unknown or unexpected block type.
Definition zxc_error.h:65
@ ZXC_ERROR_OVERFLOW
Buffer overflow detected during processing.
Definition zxc_error.h:58
@ ZXC_ERROR_BAD_OFFSET
Invalid match offset during decompression.
Definition zxc_error.h:57
@ 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_IO
Read/write/seek failure on file.
Definition zxc_error.h:61
@ 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_DIGEST_SIZE
Archive digest, opening the footer when the header sets HAS_CHECKSUM.
#define ZXC_FILE_HEADER_SIZE
File header size: Magic(4) + Version(1) + Chunk(1) + Flags(1) + Reserved(7) + Checksum(2).
#define ZXC_FILE_FOOTER_MIN_SIZE
Smallest file footer: original_size(1) + compressed_size(1) + lengths(1).
#define ZXC_FILE_FORMAT_VERSION
Current on-disk file format version.
#define ZXC_FILE_FLAG_HAS_CHECKSUM
Bit flag in the Flags byte indicating checksum presence (bit 7).
static uint32_t zxc_seek_group_len(const uint64_t nblocks, const uint64_t g)
Blocks in group g of nblocks: ZXC_SEEK_GROUP, fewer for the last.
#define ZXC_SEEK_GROUP
Blocks per group.
#define ZXC_ALIGN_CL(x)
Round x up to the next cache-line boundary.
#define ZXC_GLO_HEADER_BINARY_SIZE
Binary size of a GLO block sub-header.
#define ZXC_ALWAYS_INLINE
Forces a function to be inlined (fallback for non-GCC/Clang/MSVC compilers).
#define ZXC_ALIGNED_FREE(ptr)
Counterpart deallocator for ZXC_ALIGNED_MALLOC.
Definition zxc_deps.h:137
#define ZXC_CHECKSUM_RAPIDHASH
Checksum algorithm id for RapidHash (default, sole implementation).
#define ZXC_BLOCK_FORMAT_OVERHEAD
Worst-case format overhead inside a single block beyond the outer 8-byte block header and the optiona...
static uint64_t zxc_seek_table_bytes(const uint64_t nblocks)
Byte size of the groups for nblocks blocks, in 64 bits.
#define ZXC_ALIGNED_MALLOC(size, alignment)
Cache-line-aligned allocator.
Definition zxc_deps.h:134
#define ZXC_LZ_HASH_SIZE
Maximum number of entries in the hash table.
#define ZXC_FILE_CHECKSUM_ALGO_MASK
Mask for the checksum algorithm id (bits 0-3).
#define ZXC_SEEK_SIZE_ENTRY
One block's on-disk size, u32.
#define ZXC_LZ_MIN_MATCH_LEN
Minimum match length for an LZ77 match.
#define ZXC_LZ_WINDOW_SIZE
Sliding window size (64 KB).
#define ZXC_PAD_SIZE
Safety padding appended to buffers to tolerate overruns.
#define ZXC_FILE_FLAG_HAS_DICTIONARY
Bit flag in the Flags byte indicating a dictionary is required (bit 6).
static uint32_t zxc_seek_size_field(const uint64_t table_bytes)
SEK header size field: table_bytes with its high half folded onto the low one.
#define ZXC_GHI_HEADER_BINARY_SIZE
Binary size of a GHI block sub-header.
#define ZXC_BLOCK_HEADER_SIZE
Block header size: Type(1)+Flags(1)+Reserved(1)+Checksum(1)+CompSize(4).
#define ZXC_FILE_FLAG_HAS_SEEK_TABLE
Bit flag in the Flags byte: a SEK block sits between the EOF block and the footer (bit 5).
#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_CACHE_LINE_SIZE
Assumed CPU cache line size for alignment.
static int zxc_magic_prefix_ok(const uint8_t *p, const size_t n)
Whether the first n bytes (up to 4) at p agree with the magic word, so a frame may start there: the c...
#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_FASTEST
Fastest compression; lowest ratio.
@ ZXC_LEVEL_DENSITY
Higher density: adds Huffman-coded literals on top of COMPACT.
const char * zxc_version_string(void)
Returns the human-readable library version string.
int zxc_default_level(void)
Returns the default compression level.
int zxc_max_level(void)
Returns the maximum supported compression level.
int zxc_min_level(void)
Returns the minimum supported compression level.
int64_t zxc_write_seek_table(uint8_t *dst, const size_t dst_capacity, const uint32_t *comp_sizes, const uint64_t num_blocks)
Serialises a seek table (a ZXC_BLOCK_SEK block) into dst.
Definition zxc_common.c:786
size_t zxc_seek_table_size(const uint64_t num_blocks)
Byte size of a seek table holding num_blocks blocks: header, then groups.
Definition zxc_common.c:731
#define ZXC_LIB_VERSION_STR
Human-readable version string in "MAJOR.MINOR.PATCH" form (e.g.
On-disk header structure for a ZXC block (8 bytes, little-endian).
uint8_t block_type
Block type (see zxc_block_type_t).
Compression / decompression context.
uint32_t * lit_freq_acc
Trainer hook: when non-NULL, the GLO encoder accumulates post-LZ literal byte frequencies here (256 e...
uint8_t * hash_tags
Split tag table for fast match rejection (8-bit tags).
zxc_dict_huf_state_t * dict_huf
Tree-at-attach state (PivCo tree + codes + code lengths), carved from the workspace only when dict_si...
uint8_t * buf_split
Match-splitting scratch, one byte per sequence.
size_t lit_buffer_cap
Current capacity of the scratch buffer.
void * memory_block
Single allocation block owner.
size_t opt_scratch_cap
Current capacity of opt_scratch in bytes.
size_t work_buf_cap
Capacity of the work buffer.
void * entropy_block
Lazy allocation backing tok_buffer + pivco_scratch (heap decode contexts, first entropy block only).
size_t pivco_scratch_cap
Capacity of pivco_scratch in bytes.
uint32_t epoch
Current epoch for lazy hash table invalidation.
uint32_t * buf_sequences
Buffer for sequence records (packed: LL(8)|ML(8)|Offset(16)).
uint8_t * lit_buffer
Scratch buffer for literals (RLE / Huffman).
uint8_t * work_buf
Padded scratch buffer for buffer-API decompression.
uint32_t * hash_table
Hash table for LZ77 match positions (epoch|pos).
size_t tok_buffer_cap
Capacity of tok_buffer in bytes.
uint8_t * buf_tokens
Buffer for token sequences.
uint8_t * buf_extras
Buffer for extra lengths (vbytes for LL/ML).
uint8_t * dict_buffer
[dict | data] concat scratch carved from memory_block when dict_size > 0 (NULL otherwise).
uint8_t * pivco_scratch
Level ping-pong scratch for PivCo decode.
zxc_glo_split_hist_t * buf_split_hist
Match-splitting escape histograms.
size_t dict_size
Dictionary prefill size (0 = no dictionary).
uint8_t * literals
Buffer for literal bytes.
uint8_t * opt_scratch
Optimal-parser DP scratch (level >= 6 only, lazy-allocated, packs dp/parent_len/parent_off/actions).
uint16_t * chain_table
Chain table for collision resolution.
int dict_huf_tree_ok
1 when *dict_huf is built and valid.
uint8_t * tok_buffer
Decode scratch for a Huffman-coded GLO token section (enc_tok == HUFFMAN); NULL on compress.
uint16_t * buf_offsets
Buffer for offsets.
size_t dict_buffer_cap
Capacity of dict_buffer in bytes (0 = none).
Options for compression.
Definition zxc_opts.h:58
What zxc_scan_container learns about a container.
int uses_dict
A frame bound to a dictionary.
uint64_t frames
ZXC frames.
uint64_t dsize
Sum of the stored decompressed sizes.
uint32_t first_dict_id
Dictionary id of the first frame, 0 without one.
Options for decompression.
Definition zxc_opts.h:86
Frame-constant dictionary Huffman state, prebuilt once at attach.
What a frame's header and footer declare, read without decoding.
Definition zxc_buffer.h:235
uint8_t format_version
Format version of the frame.
Definition zxc_buffer.h:241
uint64_t digest
Archive digest; 0 when has_checksum is 0.
Definition zxc_buffer.h:238
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
uint32_t dict_id
Dictionary the frame needs; 0 for none.
Definition zxc_buffer.h:240
uint8_t has_seek_table
1 when a seek table precedes the footer.
Definition zxc_buffer.h:243
uint64_t compressed_size
Compressed bytes of the frame, footer included.
Definition zxc_buffer.h:237
Header specific to General (LZ-based) compression blocks.
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_write_glo_header_and_desc(uint8_t *dst, const size_t rem, const zxc_gnr_header_t *gh, const uint32_t lit_comp, const uint32_t tok_comp)
Serialises a GLO block header followed by its section descriptors.
Definition zxc_common.c:940
int zxc_read_frame_info(const uint8_t *header, const uint8_t *tail, const size_t tail_len, const uint64_t total, zxc_frame_info_t *info, size_t *footer_len)
Validates a frame from its header and the end of its bytes.
Definition zxc_common.c:648
static uint64_t zxc_load_le_n(const uint8_t *src, const size_t n)
Loads n little-endian bytes.
Definition zxc_common.c:811
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_parse_file_footer(const uint8_t *end, const size_t avail, uint64_t *src_size, uint64_t *compressed_size, size_t *sizes_len)
Parses the two sizes back from the end of a frame.
Definition zxc_common.c:865
static size_t zxc_cctx_max_seq(const size_t chunk_size)
Worst-case sequence count for one block.
Definition zxc_common.c:94
int zxc_cctx_alloc_entropy_scratch(zxc_cctx_t *ctx)
Lazily allocates the deferred decode-side entropy scratch.
Definition zxc_common.c:373
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
static void zxc_read_gnr_header(const uint8_t *src, zxc_gnr_header_t *gh)
Reads the 12-byte GLO/GHI sub-header shared by both block types.
Definition zxc_common.c:910
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
static void zxc_write_gnr_header(uint8_t *dst, const zxc_gnr_header_t *gh)
Writes the 12-byte GLO/GHI sub-header shared by both block types.
Definition zxc_common.c:893
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
size_t zxc_decompress_opts_size(void)
Returns sizeof(zxc_decompress_opts_t) for ABI-safe allocation.
Definition zxc_common.c:43
int zxc_read_ghi_header(const uint8_t *src, const size_t len, zxc_gnr_header_t *gh)
Parses a GHI block header from src.
static void zxc_dctx_entropy_sizes(const size_t chunk_size, size_t *sz_tok, size_t *sz_pivco)
Decode-side entropy scratch sizes for one block: token scratch (worst-case sequence count + wild-read...
Definition zxc_common.c:105
int zxc_write_ghi_header(uint8_t *dst, const size_t rem, const zxc_gnr_header_t *gh)
Serialises a GHI block header.
Definition zxc_common.c:997
size_t zxc_seek_write_group(uint8_t *dst, uint64_t *anchor, const uint32_t *sizes, const uint32_t cnt)
Writes one group (*anchor, then cnt sizes) into dst, which holds ZXC_SEEK_GROUP_BYTES,...
Definition zxc_common.c:770
int zxc_read_glo_header_and_desc(const uint8_t *src, const size_t len, zxc_gnr_header_t *gh, uint32_t *lit_comp, uint32_t *tok_comp)
Parses a GLO block header and its section descriptors from src.
Definition zxc_common.c:965
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
static void zxc_store_le_n(uint8_t *dst, uint64_t v, const size_t n)
Stores the low n bytes of v, little-endian.
Definition zxc_common.c:806
static int zxc_scan_read(const zxc_scan_src_t *src, const uint64_t off, void *dst, const size_t len)
Reads a byte range of a scan source, refusing ranges past its end.
Definition zxc_common.c:549
static size_t zxc_glo_desc_size(const uint8_t enc_lit, const uint8_t enc_tok)
Size of the GLO section descriptors, implied by the encoding fields.
Definition zxc_common.c:926
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
static zxc_cctx_layout_t compute_cctx_layout(const size_t chunk_size, const int mode, const int level, const size_t dict_size, const int defer_entropy_scratch)
Computes the single-allocation memory layout for a compression / decompression context.
Definition zxc_common.c:140
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
size_t zxc_compress_opts_size(void)
Returns sizeof(zxc_compress_opts_t) for ABI-safe allocation.
Definition zxc_common.c:33
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 void zxc_encode_footer(uint8_t *dst, const zxc_footer_layout_t *l, const uint64_t src_size, const uint64_t digest)
Encodes the l footer: digest, the two sizes, then L.
Definition zxc_common.c:818
int zxc_huf_dict_tree_build(const uint8_t *packed_lengths, zxc_pivco_tree_t *tree, uint32_t *codes, uint8_t *code_len)
Prebuild a dict table's decode/encode state (tree-at-attach).
Internal definitions, constants, SIMD helpers, and utility functions.
#define ZXC_PIVCO_SCRATCH_PAD
Extra scratch slack required past n by the PivCo decoder.
static uint64_t zxc_le64(const void *p)
Reads a little-endian 64-bit value from a possibly unaligned address.
static uint32_t zxc_le32(const void *p)
Reads a little-endian 32-bit value from a possibly unaligned address.
#define ZXC_HUF_NUDGE_SCRATCH_SIZE
Scratch size (bytes) for zxc_huf_nudge_code_lengths: builder's, then workspace.
static void zxc_store_le64(void *p, const uint64_t v)
Writes a 64-bit value little-endian to a possibly unaligned address.
static uint32_t zxc_log2_u32(const uint32_t v)
Integer log-base-2 for a 32-bit value.
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_FRAME_MIN_SIZE
Smallest frame: header, the EOF block and the shortest footer.
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 uint16_t zxc_le16(const void *p)
Reads a little-endian 16-bit value from a possibly unaligned address.
static uint16_t zxc_hash16(const uint8_t *p)
Computes the 2-byte checksum for file and dictionary headers.
static uint8_t zxc_hash8(const uint8_t *p)
Computes the 1-byte checksum for block headers.
static int zxc_footer_dsize_plausible(const uint64_t dsize, const size_t chunk_size, const uint64_t comp_size)
Whether a footer's decompressed size is reachable for this archive.
uint32_t zxc_glo_split_hist_t[4][2][1U<< 8]
zxc_glo_split_analyze's escape histograms: four rotating lanes of [escape][context] counts,...
static void zxc_store_le32(void *p, const uint32_t v)
Writes a 32-bit value little-endian to a possibly unaligned address.
static size_t zxc_uint_bytes(const uint64_t v)
Fewest little-endian bytes, 1 to 8, that hold v.
static size_t zxc_frame_tail_len(const uint64_t total)
Bytes zxc_read_frame_info() takes from the end of total bytes (at least ZXC_FRAME_MIN_SIZE): all of t...
#define ZXC_FOOTER_MAX_SIZE_WITH_DIGEST
Most bytes a footer spans: the digest and the two longest sizes.
static void zxc_file_header_sign(uint8_t *h)
Writes the file header checksum (bytes 14-15) over the other 14 bytes.
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.
static void zxc_opt_dp_sizes(const size_t chunk_size, size_t *sz_dp, size_t *sz_pl, size_t *sz_po, size_t *sz_bm)
The four DP partitions the optimal parser carves out of opt_scratch.