ZXC 0.15.0
High-performance asymmetric lossless compression library
Loading...
Searching...
No Matches
zxc_pstream.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
25#include "../../include/zxc_pstream.h"
26
27#include "../../include/zxc_buffer.h"
28#include "../../include/zxc_constants.h"
29#include "../../include/zxc_error.h"
30#include "zxc_internal.h"
31
32// =====================================================================
33// Compression
34// =====================================================================
35
80
124struct zxc_cstream_s {
126 zxc_cctx* cctx;
127 size_t block_size;
128
129 uint8_t* in_block;
130 size_t in_used;
131
132 uint8_t* pending;
133 size_t pending_cap;
134 size_t pending_len;
135 size_t pending_pos;
136
137 uint64_t block_index;
138 uint64_t total_in;
139 uint64_t total_out;
140 uint64_t digest;
141
142 cstream_state_t state;
143 int error_code;
144};
145
158static int cs_set_error(zxc_cstream* cs, const int code) {
159 cs->error_code = code;
160 cs->state = CS_ERRORED;
161 return code;
162}
163
181static int cs_compress_block_from(zxc_cstream* cs, const uint8_t* RESTRICT src, const size_t len) {
182 const uint64_t bound = zxc_compress_block_bound(len);
183 // LCOV_EXCL_START
184 if (UNLIKELY(bound == 0 || bound > SIZE_MAX)) return ZXC_ERROR_OVERFLOW;
185 if (UNLIKELY(bound > cs->pending_cap)) {
186 uint8_t* nb = (uint8_t*)ZXC_REALLOC(cs->pending, (size_t)bound);
187 if (UNLIKELY(!nb)) return ZXC_ERROR_MEMORY;
188 cs->pending = nb;
189 cs->pending_cap = (size_t)bound;
190 }
191 // LCOV_EXCL_STOP
192 const int64_t csize = zxc_compress_block_at(cs->cctx, src, len, cs->pending, cs->pending_cap,
193 &cs->opts, cs->block_index);
194 if (UNLIKELY(csize < 0)) return (int)csize; // LCOV_EXCL_LINE
195 cs->block_index++;
196
197 cs->pending_len = (size_t)csize;
198 cs->pending_pos = 0;
199 cs->total_in += len;
200 cs->total_out += (uint64_t)csize;
201 if (cs->opts.checksum_enabled && cs->pending_len >= ZXC_BLOCK_CHECKSUM_SIZE)
202 cs->digest = zxc_digest_combine(
203 cs->digest, zxc_le32(cs->pending + cs->pending_len - ZXC_BLOCK_CHECKSUM_SIZE));
204 return ZXC_OK;
205}
206
221 const int rc = cs_compress_block_from(cs, cs->in_block, cs->in_used);
222 if (LIKELY(rc == ZXC_OK)) cs->in_used = 0;
223 return rc;
224}
225
232static size_t zxc_outbuf_push(zxc_outbuf_t* out, const uint8_t* RESTRICT src, size_t* RESTRICT pos,
233 const size_t len) {
234 const size_t avail_out = out->size - out->pos;
235 const size_t avail_src = len - *pos;
236 const size_t n = avail_out < avail_src ? avail_out : avail_src;
237 if (n) {
238 ZXC_MEMCPY((uint8_t*)out->dst + out->pos, src + *pos, n);
239 out->pos += n;
240 *pos += n;
241 }
242 return n;
243}
244
247 zxc_outbuf_push(out, cs->pending, &cs->pending_pos, cs->pending_len);
248 return cs->pending_pos == cs->pending_len;
249}
250
267 zxc_cstream* cs = (zxc_cstream*)ZXC_CALLOC(1, sizeof(*cs));
268 if (UNLIKELY(!cs)) return NULL; // LCOV_EXCL_LINE
269
270 if (opts) cs->opts = *opts;
271
272 // Push streams take no dictionary yet (deferred until a need arises): the
273 // header id stays 0, so a dictionary here would produce archives that
274 // decode wrong elsewhere.
275 if (UNLIKELY(cs->opts.dict || cs->opts.dict_size || cs->opts.dict_huf)) {
276 ZXC_FREE(cs);
277 return NULL;
278 }
279
280 cs->opts.level = ZXC_OPTS_LEVEL(&cs->opts, ZXC_LEVEL_DEFAULT);
281 cs->opts.block_size = ZXC_OPTS_BLOCK_SIZE(&cs->opts, ZXC_BLOCK_SIZE_DEFAULT);
282 // n_threads is ignored on this single-threaded path.
283 cs->opts.n_threads = 0;
284 cs->opts.progress_cb = NULL;
285 cs->opts.user_data = NULL;
286 cs->opts.seekable = 0;
287 cs->block_size = cs->opts.block_size;
288
289 cs->cctx = zxc_create_cctx(&cs->opts);
290 // LCOV_EXCL_START
291 if (UNLIKELY(!cs->cctx)) {
292 ZXC_FREE(cs);
293 return NULL;
294 }
295 cs->in_block = (uint8_t*)ZXC_MALLOC(cs->block_size);
296 if (UNLIKELY(!cs->in_block)) {
297 zxc_free_cctx(cs->cctx);
298 ZXC_FREE(cs);
299 return NULL;
300 }
301 // LCOV_EXCL_STOP
302 // Pre-size pending so the file header and footer paths never need realloc.
306 cs->pending = (uint8_t*)ZXC_MALLOC(cs->pending_cap);
307 // LCOV_EXCL_START
308 if (UNLIKELY(!cs->pending)) {
309 ZXC_FREE(cs->in_block);
310 zxc_free_cctx(cs->cctx);
311 ZXC_FREE(cs);
312 return NULL;
313 }
314 // LCOV_EXCL_STOP
315 cs->state = CS_INIT;
316 return cs;
317}
318
326 const int w = zxc_write_file_header(cs->pending, cs->pending_cap, cs->block_size,
327 cs->opts.checksum_enabled, 0, 0);
328 if (UNLIKELY(w < 0)) return w; // LCOV_EXCL_LINE
329 cs->pending_len = (size_t)w;
330 cs->pending_pos = 0;
331 cs->total_out = (uint64_t)w;
332 return ZXC_OK;
333}
334
344static int cs_stage_eof(zxc_cstream* cs) {
345 // LCOV_EXCL_START
346 if (UNLIKELY(ZXC_BLOCK_HEADER_SIZE > cs->pending_cap)) {
347 uint8_t* nb = (uint8_t*)ZXC_REALLOC(cs->pending, ZXC_BLOCK_HEADER_SIZE);
348 if (UNLIKELY(!nb)) return ZXC_ERROR_MEMORY;
349 cs->pending = nb;
350 cs->pending_cap = ZXC_BLOCK_HEADER_SIZE;
351 }
352 // LCOV_EXCL_STOP
353 const zxc_block_header_t eof = {
354 .block_type = (uint8_t)ZXC_BLOCK_EOF,
355 .block_flags = 0,
356 .reserved = 0,
357 .header_checksum = 0,
358 .comp_size = 0,
359 };
360 const int w = zxc_write_block_header(cs->pending, cs->pending_cap, &eof);
361 if (UNLIKELY(w < 0)) return w; // LCOV_EXCL_LINE
362 cs->pending_len = (size_t)w;
363 cs->pending_pos = 0;
364 cs->total_out += (uint64_t)w;
365 return ZXC_OK;
366}
367
378 const int w = zxc_write_file_footer(cs->pending, cs->pending_cap, cs->total_out, cs->total_in,
379 cs->digest, cs->opts.checksum_enabled);
380 if (UNLIKELY(w < 0)) return w; // LCOV_EXCL_LINE
381 cs->pending_len = (size_t)w;
382 cs->pending_pos = 0;
383 return ZXC_OK;
384}
385
387 if (!cs) return;
388 ZXC_FREE(cs->pending);
389 ZXC_FREE(cs->in_block);
390 zxc_free_cctx(cs->cctx);
391 ZXC_FREE(cs);
392}
393
397size_t zxc_cstream_in_size(const zxc_cstream* cs) { return cs ? cs->block_size : 0; }
398
407 if (UNLIKELY(!cs)) return 0; // LCOV_EXCL_LINE
408 const uint64_t b = zxc_compress_block_bound(cs->block_size);
409 return (b == 0 || b > SIZE_MAX) ? cs->block_size : (size_t)b;
410}
411
426 if (UNLIKELY(!cs || !out || !in || in->pos > in->size || out->pos > out->size ||
427 (in->size > in->pos && !in->src) || (out->size > out->pos && !out->dst) ||
428 cs->state == CS_DONE)) {
430 }
431 if (UNLIKELY(cs->state == CS_ERRORED)) return cs->error_code;
432
433 for (;;) {
434 switch (cs->state) {
435 case CS_INIT: {
436 const int rc = cs_stage_file_header(cs);
437 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
438 cs->state = CS_DRAIN_HEADER;
439 break;
440 }
441
442 case CS_DRAIN_HEADER:
443 case CS_DRAIN_BLOCK: {
444 if (!cs_drain_pending(cs, out)) return (int64_t)(cs->pending_len - cs->pending_pos);
445 cs->state = CS_ACCUMULATE;
446 break;
447 }
448
449 case CS_ACCUMULATE: {
450 const size_t avail_in = in->size - in->pos;
451 if (cs->in_used == 0 && avail_in >= cs->block_size) {
452 const int rc = cs_compress_block_from(cs, (const uint8_t*)in->src + in->pos,
453 cs->block_size);
454 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
455 in->pos += cs->block_size;
456 cs->state = CS_DRAIN_BLOCK;
457 break;
458 }
459
460 const size_t room = cs->block_size - cs->in_used;
461 const size_t n = avail_in < room ? avail_in : room;
462 if (n) {
463 ZXC_MEMCPY(cs->in_block + cs->in_used, (const uint8_t*)in->src + in->pos, n);
464 in->pos += n;
465 cs->in_used += n;
466 }
467
468 if (cs->in_used == cs->block_size) {
469 const int rc = cs_compress_one_block(cs);
470 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
471 cs->state = CS_DRAIN_BLOCK;
472 break;
473 }
474 // Block not yet full either in is empty or we made no progress.
475 return 0;
476 }
477
478 case CS_DRAIN_LAST:
479 case CS_DRAIN_EOF:
480 case CS_DRAIN_FOOTER:
481 case CS_DONE:
482 case CS_ERRORED:
483 // These states are owned by _end().
485 }
486 }
487}
488
499 if (UNLIKELY(!cs || !out || cs->state == CS_DONE)) return ZXC_ERROR_NULL_INPUT;
500 if (UNLIKELY(cs->state == CS_ERRORED)) return cs->error_code;
501
502 for (;;) {
503 switch (cs->state) {
504 case CS_INIT: {
505 // _end before any input, still need to emit file header.
506 const int rc = cs_stage_file_header(cs);
507 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
508 cs->state = CS_DRAIN_HEADER;
509 break;
510 }
511
512 case CS_DRAIN_HEADER:
513 case CS_DRAIN_BLOCK: {
514 if (!cs_drain_pending(cs, out)) return (int64_t)(cs->pending_len - cs->pending_pos);
515 cs->state = CS_ACCUMULATE;
516 break;
517 }
518
519 case CS_ACCUMULATE: {
520 // Compress the residual partial block (if any), then EOF + footer.
521 if (cs->in_used > 0) {
522 const int rc = cs_compress_one_block(cs);
523 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
524 cs->state = CS_DRAIN_LAST;
525 break;
526 }
527 // No residual data: go straight to EOF.
528 {
529 const int rc = cs_stage_eof(cs);
530 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
531 cs->state = CS_DRAIN_EOF;
532 break;
533 }
534 }
535
536 case CS_DRAIN_LAST: {
537 if (!cs_drain_pending(cs, out)) return (int64_t)(cs->pending_len - cs->pending_pos);
538 // After last data block -> EOF.
539 const int rc = cs_stage_eof(cs);
540 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
541 cs->state = CS_DRAIN_EOF;
542 break;
543 }
544
545 case CS_DRAIN_EOF: {
546 if (!cs_drain_pending(cs, out)) return (int64_t)(cs->pending_len - cs->pending_pos);
547 const int rc = cs_stage_footer(cs);
548 if (UNLIKELY(rc < 0)) return cs_set_error(cs, rc); // LCOV_EXCL_LINE
549 cs->state = CS_DRAIN_FOOTER;
550 break;
551 }
552
553 case CS_DRAIN_FOOTER: {
554 if (!cs_drain_pending(cs, out)) return (int64_t)(cs->pending_len - cs->pending_pos);
555 cs->state = CS_DONE;
556 return 0;
557 }
558
559 case CS_DONE:
560 case CS_ERRORED:
561 return cs->state == CS_ERRORED ? cs->error_code : 0; // LCOV_EXCL_LINE
562 }
563 }
564}
565
566// =====================================================================
567// Decompression
568// =====================================================================
569
617
689struct zxc_dstream_s {
691 zxc_cctx_t inner;
692 int inner_initialized;
693 size_t block_size;
694 int file_has_checksum;
695 int file_has_seek;
696
697 uint8_t scratch[32];
698 size_t scratch_used;
699 size_t scratch_need;
700
701 uint8_t* payload;
702 size_t payload_cap;
703 size_t payload_used;
704 size_t payload_need;
705
706 uint8_t* decoded;
707 size_t decoded_cap;
708 size_t decoded_size;
709 size_t decoded_pos;
710
711 zxc_block_header_t cur_bh;
712 uint64_t sek_remaining;
713
714 uint64_t block_index;
715 uint64_t total_out;
716 uint64_t frame_in;
717 uint64_t digest;
718 int after_frame;
719 size_t alloc_block_size;
720
721 dstream_state_t state;
722 int error_code;
723};
724
737static int ds_set_error(zxc_dstream* ds, const int code) {
738 ds->error_code = code;
739 ds->state = DS_ERRORED;
740 return code;
741}
742
750static int ds_pull(uint8_t* RESTRICT dst, size_t* RESTRICT used, const size_t need,
751 zxc_inbuf_t* in) {
752 const size_t want = need - *used;
753 const size_t avail = in->size - in->pos;
754 const size_t n = want < avail ? want : avail;
755 if (n) {
756 ZXC_MEMCPY(dst + *used, (const uint8_t*)in->src + in->pos, n);
757 in->pos += n;
758 *used += n;
759 }
760 return *used == need;
761}
762
764static void ds_free_buffers(zxc_dstream* ds) {
765 ZXC_FREE(ds->payload);
766 ZXC_FREE(ds->decoded);
767 if (ds->inner_initialized) zxc_cctx_free(&ds->inner);
768 ds->payload = NULL;
769 ds->decoded = NULL;
770 ds->payload_cap = ds->decoded_cap = 0;
771 ds->inner_initialized = 0;
772 ds->alloc_block_size = 0;
773}
774
776static void ds_start_frame(zxc_dstream* ds) {
777 ds->state = DS_NEED_FILE_HEADER;
778 ds->scratch_used = 0;
779 ds->scratch_need = ZXC_FILE_HEADER_SIZE;
780 ds->payload_used = ds->payload_need = 0;
781 ds->decoded_size = ds->decoded_pos = 0;
782 ds->sek_remaining = 0;
783 ds->block_index = 0;
784 ds->total_out = 0;
785 ds->frame_in = 0;
786 ds->digest = 0;
787}
788
799 zxc_dstream* ds = (zxc_dstream*)ZXC_CALLOC(1, sizeof(*ds));
800 if (UNLIKELY(!ds)) return NULL; // LCOV_EXCL_LINE
801 if (opts) ds->opts = *opts;
802 if (UNLIKELY(ds->opts.dict || ds->opts.dict_size || ds->opts.dict_huf)) {
803 ZXC_FREE(ds);
804 return NULL;
805 }
806 ds->opts.n_threads = 0;
807 ds->opts.progress_cb = NULL;
808 ds->opts.user_data = NULL;
809 ds_start_frame(ds);
810 return ds;
811}
812
819 if (UNLIKELY(!ds)) return; // LCOV_EXCL_LINE
820 ds_free_buffers(ds);
821 ZXC_FREE(ds);
822}
823
827int zxc_dstream_finished(const zxc_dstream* ds) { return (ds && ds->state == DS_DONE) ? 1 : 0; }
828
837 if (UNLIKELY(!ds)) return 0; // LCOV_EXCL_LINE
838 if (ds->block_size == 0) return ZXC_BLOCK_SIZE_DEFAULT;
839 const uint64_t b = zxc_compress_block_bound(ds->block_size);
840 return (b == 0 || b > SIZE_MAX) ? ds->block_size : (size_t)b;
841}
842
850 if (UNLIKELY(!ds)) return 0; // LCOV_EXCL_LINE
851 return ds->block_size == 0 ? ZXC_BLOCK_SIZE_DEFAULT : ds->block_size;
852}
853
855static int ds_drain_decoded(zxc_dstream* ds, zxc_outbuf_t* out, size_t* produced) {
856 const size_t n = zxc_outbuf_push(out, ds->decoded, &ds->decoded_pos, ds->decoded_size);
857 ds->total_out += n;
858 if (produced) *produced += n;
859 return ds->decoded_pos == ds->decoded_size;
860}
861
879 const int full = ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in);
880 // After a frame, bytes that open no other are corrupt, checked as they arrive.
881 if (UNLIKELY(ds->after_frame && !zxc_magic_prefix_ok(ds->scratch, ds->scratch_used)))
883 if (!full) return 1;
884
885 size_t bs = 0;
886 int has_csum = 0;
887 int has_seek = 0;
888 uint32_t dict_id = 0;
889 const int rc =
890 zxc_read_file_header(ds->scratch, ds->scratch_used, &bs, &has_csum, &dict_id, &has_seek);
891 if (UNLIKELY(rc != ZXC_OK)) return ds_set_error(ds, rc); // LCOV_EXCL_LINE
892 // Push streams take no dictionary yet, so an archive requiring one is refused.
893 if (UNLIKELY(dict_id != 0)) return ds_set_error(ds, ZXC_ERROR_DICT_REQUIRED);
894 ds->block_size = bs;
895 ds->file_has_checksum = has_csum;
896 ds->file_has_seek = has_seek;
897
898 // Payload + decoded buffers sized from block_size; a frame declaring the
899 // block size of the one before keeps them.
900 if (ds->alloc_block_size != ds->block_size) {
901 ds_free_buffers(ds);
902 const uint64_t pb = zxc_compress_block_bound(ds->block_size);
903 // LCOV_EXCL_START
904 if (UNLIKELY(pb == 0 || pb > SIZE_MAX)) return ds_set_error(ds, ZXC_ERROR_OVERFLOW);
905 // LCOV_EXCL_STOP
906 ds->payload_cap = (size_t)pb;
907 ds->payload = (uint8_t*)ZXC_MALLOC(ds->payload_cap);
908
909 ds->decoded_cap = ds->block_size + ZXC_DECOMPRESS_TAIL_PAD;
910 ds->decoded = (uint8_t*)ZXC_MALLOC(ds->decoded_cap);
911 // LCOV_EXCL_START
912 if (UNLIKELY(!ds->payload || !ds->decoded)) return ds_set_error(ds, ZXC_ERROR_MEMORY);
913 if (UNLIKELY(zxc_cctx_init(&ds->inner, ds->block_size, 0, 0, 0, 0) != ZXC_OK))
914 return ds_set_error(ds, ZXC_ERROR_MEMORY);
915 // LCOV_EXCL_STOP
916 ds->inner_initialized = 1;
917 ds->alloc_block_size = ds->block_size;
918 }
919 ds->inner.checksum_enabled = ds->file_has_checksum && ds->opts.checksum_enabled;
920
921 ds->frame_in = ZXC_FILE_HEADER_SIZE;
922 ds->state = DS_NEED_BLOCK_HEADER;
923 ds->scratch_used = 0;
924 ds->scratch_need = ZXC_BLOCK_HEADER_SIZE;
925 return 0;
926}
927
945 if (!ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in)) return 1;
946
947 const int rc = zxc_read_block_header(ds->scratch, ds->scratch_used, &ds->cur_bh);
948 if (UNLIKELY(rc != ZXC_OK)) return ds_set_error(ds, rc); // LCOV_EXCL_LINE
949 ds->frame_in += ZXC_BLOCK_HEADER_SIZE;
950
951 if (ds->cur_bh.block_type == (uint8_t)ZXC_BLOCK_EOF) {
952 // EOF block: one valid header, no payload, no checksum.
953 if (UNLIKELY(zxc_check_eof_header(ds->scratch) != ZXC_OK))
955 ds->state = ds->file_has_seek ? DS_NEED_SEK_HEADER : DS_NEED_FOOTER;
956 ds->scratch_used = 0;
957 ds->scratch_need =
958 ds->file_has_seek
960 : zxc_footer_layout(ds->frame_in, ds->total_out, ds->file_has_checksum).len;
961 return 0;
962 }
963
964 // Data block: validate comp_size against the block_size negotiated in the file header.
965 if (UNLIKELY((uint64_t)ds->cur_bh.comp_size > (uint64_t)ds->block_size))
967
968 // Read comp_size [+ ZXC_BLOCK_CHECKSUM_SIZE if file-level checksums].
969 const uint64_t need = (uint64_t)ds->cur_bh.comp_size +
970 (ds->file_has_checksum ? (uint64_t)ZXC_BLOCK_CHECKSUM_SIZE : 0U);
971 if (UNLIKELY(need > ds->payload_cap)) return ds_set_error(ds, ZXC_ERROR_BAD_BLOCK_SIZE);
972
973 // Feed the full block (header + payload + opt csum) to zxc_decompress_block,
974 // so prefix with the 8-byte header we just parsed.
975 ZXC_MEMCPY(ds->payload, ds->scratch, ZXC_BLOCK_HEADER_SIZE);
976 ds->payload_used = ZXC_BLOCK_HEADER_SIZE;
977 ds->payload_need = (size_t)need + ZXC_BLOCK_HEADER_SIZE;
978 // LCOV_EXCL_START
979 if (UNLIKELY(ds->payload_need > ds->payload_cap)) {
980 // grow
981 uint8_t* nb = (uint8_t*)ZXC_REALLOC(ds->payload, ds->payload_need);
982 if (UNLIKELY(!nb)) return ds_set_error(ds, ZXC_ERROR_MEMORY);
983 ds->payload = nb;
984 ds->payload_cap = ds->payload_need;
985 }
986 // LCOV_EXCL_STOP
987 ds->state = DS_NEED_BLOCK_PAYLOAD;
988 return 0;
989}
990
1011 if (UNLIKELY(!ds || !out || !in || in->pos > in->size || out->pos > out->size ||
1012 (in->size > in->pos && !in->src) || (out->size > out->pos && !out->dst))) {
1013 return ZXC_ERROR_NULL_INPUT;
1014 }
1015 if (UNLIKELY(ds->state == DS_ERRORED)) return ds->error_code;
1016 if (ds->state == DS_DONE) {
1017 if (in->pos == in->size) return 0;
1018 ds_start_frame(ds); // concatenated archives: what follows is another frame
1019 }
1020
1021 size_t produced = 0;
1022
1023 for (;;) {
1024 switch (ds->state) {
1025 case DS_NEED_FILE_HEADER: {
1026 const int rc = ds_handle_need_file_header(ds, in);
1027 if (rc == 1) return (int64_t)produced;
1028 if (rc < 0) return rc;
1029 break;
1030 }
1031
1032 case DS_NEED_BLOCK_HEADER: {
1033 const int rc = ds_handle_need_block_header(ds, in);
1034 if (rc == 1) return (int64_t)produced;
1035 if (rc < 0) return rc;
1036 break;
1037 }
1038
1039 case DS_NEED_BLOCK_PAYLOAD: {
1040 if (!ds_pull(ds->payload, &ds->payload_used, ds->payload_need, in))
1041 return (int64_t)produced;
1042 ds->frame_in += ds->payload_need - ZXC_BLOCK_HEADER_SIZE;
1043 ds->state = DS_DECODE_BLOCK;
1044 break;
1045 }
1046
1047 case DS_DECODE_BLOCK: {
1048 const int direct = (out->size - out->pos) >= ds->decoded_cap;
1049 uint8_t* const ddst = direct ? (uint8_t*)out->dst + out->pos : ds->decoded;
1050 const int dsz =
1051 zxc_decompress_chunk_wrapper(&ds->inner, ds->payload, ds->payload_used, ddst,
1052 ds->decoded_cap, ds->block_index);
1053 if (UNLIKELY(dsz < 0)) return ds_set_error(ds, dsz);
1054 ds->block_index++;
1055 // Folded only when DS_VALIDATE_FOOTER will compare it.
1056 if (ds->file_has_checksum && ds->opts.checksum_enabled)
1057 ds->digest = zxc_digest_combine(
1058 ds->digest,
1059 zxc_le32(ds->payload + ds->payload_used - ZXC_BLOCK_CHECKSUM_SIZE));
1060
1061 if (direct) {
1062 out->pos += (size_t)dsz;
1063 produced += (size_t)dsz;
1064 ds->total_out += (uint64_t)dsz;
1065 ds->decoded_size = 0;
1066 ds->decoded_pos = 0;
1067 ds->state = DS_NEED_BLOCK_HEADER;
1068 ds->scratch_used = 0;
1069 ds->scratch_need = ZXC_BLOCK_HEADER_SIZE;
1070 break;
1071 }
1072
1073 ds->decoded_size = (size_t)dsz;
1074 ds->decoded_pos = 0;
1075 ds->state = DS_EMIT_DECODED;
1076 break;
1077 }
1078
1079 case DS_EMIT_DECODED: {
1080 const int done = ds_drain_decoded(ds, out, &produced);
1081 if (!done) return (int64_t)produced;
1082 ds->state = DS_NEED_BLOCK_HEADER;
1083 ds->scratch_used = 0;
1084 ds->scratch_need = ZXC_BLOCK_HEADER_SIZE;
1085 break;
1086 }
1087
1088 case DS_NEED_SEK_HEADER: {
1089 // Checked as bytes arrive: a wrong header fails without waiting.
1090 const int full = ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in);
1091 const int rc = zxc_check_seek_header(ds->scratch, ds->scratch_used, ds->total_out,
1092 ds->block_size, &ds->sek_remaining);
1093 if (UNLIKELY(rc == ZXC_ERROR_CORRUPT_DATA)) return ds_set_error(ds, rc);
1094 if (!full) return (int64_t)produced;
1095 ds->frame_in += ZXC_BLOCK_HEADER_SIZE + ds->sek_remaining;
1096 ds->state = DS_DRAIN_SEK_PAYLOAD;
1097 break;
1098 }
1099
1100 case DS_DRAIN_SEK_PAYLOAD: {
1101 const size_t avail = in->size - in->pos;
1102 const size_t n =
1103 (uint64_t)avail < ds->sek_remaining ? avail : (size_t)ds->sek_remaining;
1104 in->pos += n;
1105 ds->sek_remaining -= n;
1106 if (ds->sek_remaining > 0) return (int64_t)produced;
1107 ds->state = DS_NEED_FOOTER;
1108 ds->scratch_used = 0;
1109 ds->scratch_need =
1110 zxc_footer_layout(ds->frame_in, ds->total_out, ds->file_has_checksum).len;
1111 break;
1112 }
1113
1114 case DS_NEED_FOOTER: {
1115 // Checked as bytes arrive, like the SEK header.
1116 const int full = ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in);
1117 const zxc_footer_layout_t fl =
1118 zxc_footer_layout(ds->frame_in, ds->total_out, ds->file_has_checksum);
1119 const int rc =
1120 zxc_check_file_footer(ds->scratch, ds->scratch_used, &fl, ds->total_out, NULL);
1121 if (UNLIKELY(rc == ZXC_ERROR_CORRUPT_DATA)) return ds_set_error(ds, rc);
1122 if (!full) return (int64_t)produced;
1123 ds->state = DS_VALIDATE_FOOTER;
1124 break;
1125 }
1126
1127 case DS_VALIDATE_FOOTER: {
1128 const uint64_t stored_digest = ds->file_has_checksum ? zxc_le64(ds->scratch) : 0;
1129 if (ds->file_has_checksum && ds->opts.checksum_enabled &&
1130 UNLIKELY(stored_digest != ds->digest))
1132 // The call ends with the frame, its output handed over before
1133 // anything that follows is looked at.
1134 ds->after_frame = 1;
1135 ds->state = DS_DONE;
1136 return (int64_t)produced;
1137 }
1138
1139 case DS_DONE:
1140 return (int64_t)produced; // LCOV_EXCL_LINE
1141
1142 case DS_ERRORED:
1143 return ds->error_code;
1144 }
1145 }
1146}
struct zxc_cctx_s zxc_cctx
Opaque reusable compression context (see zxc_create_cctx).
Definition zxc_buffer.h:327
uint64_t zxc_compress_block_bound(size_t input_size)
Maximum compressed size for a single block.
#define ZXC_BLOCK_SIZE_DEFAULT
Default block size (512 KB).
zxc_cctx * zxc_create_cctx(const zxc_compress_opts_t *opts)
Creates a reusable compression context.
void zxc_free_cctx(zxc_cctx *cctx)
Frees a compression context and its resources.
@ 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_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_DICT_REQUIRED
File requires a dictionary but none was provided.
Definition zxc_error.h:69
@ ZXC_ERROR_BAD_CHECKSUM
A block's decoded bytes fail its checksum, or the archive digest mismatches.
Definition zxc_error.h:51
@ ZXC_OK
Success (no error).
Definition zxc_error.h:38
@ ZXC_ERROR_BAD_BLOCK_SIZE
Invalid block size.
Definition zxc_error.h:66
#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_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_BLOCK_HEADER_SIZE
Block header size: Type(1)+Flags(1)+Reserved(1)+Checksum(1)+CompSize(4).
#define ZXC_REALLOC(ptr, size)
In-place / move heap reallocator.
Definition zxc_deps.h:80
#define ZXC_BLOCK_CHECKSUM_SIZE
Size of the per-block checksum field in bytes.
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_DEFAULT
Recommended default: better ratio and decode speed than LZ4.
struct zxc_dstream_s zxc_dstream
Opaque push-model decompression stream (see zxc_dstream_create).
size_t zxc_dstream_out_size(const zxc_dstream *ds)
Returns the suggested output chunk size for the decompressor.
void zxc_cstream_free(zxc_cstream *cs)
Releases a compression stream and all internal buffers.
struct zxc_cstream_s zxc_cstream
Opaque push-model compression stream (see zxc_cstream_create).
size_t zxc_dstream_in_size(const zxc_dstream *ds)
Returns the suggested input chunk size for the decompressor.
size_t zxc_cstream_out_size(const zxc_cstream *cs)
Returns the suggested output chunk size.
int64_t zxc_dstream_decompress(zxc_dstream *ds, zxc_outbuf_t *out, zxc_inbuf_t *in)
Push-side entry point: feeds compressed input and drains decoded output.
size_t zxc_cstream_in_size(const zxc_cstream *cs)
Returns the suggested input chunk size (configured block size).
void zxc_dstream_free(zxc_dstream *ds)
Releases a decompression stream and all internal buffers.
zxc_dstream * zxc_dstream_create(const zxc_decompress_opts_t *opts)
Allocates and initialises a push decompression stream.
zxc_cstream * zxc_cstream_create(const zxc_compress_opts_t *opts)
Allocates and initialises a push compression stream.
int64_t zxc_cstream_compress(zxc_cstream *cs, zxc_outbuf_t *out, zxc_inbuf_t *in)
Push-side entry point: feeds input and drains compressed output.
int64_t zxc_cstream_end(zxc_cstream *cs, zxc_outbuf_t *out)
Finalises the stream: residual block (if any), EOF, and footer.
int zxc_dstream_finished(const zxc_dstream *ds)
Returns 1 iff the stream has reached DS_DONE.
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.
Options for compression.
Definition zxc_opts.h:58
Options for decompression.
Definition zxc_opts.h:86
Input buffer descriptor for push streaming.
Definition zxc_pstream.h:81
size_t size
Total bytes available in src.
Definition zxc_pstream.h:83
size_t pos
Bytes already consumed by the library (in/out).
Definition zxc_pstream.h:84
const void * src
Caller-owned input bytes.
Definition zxc_pstream.h:82
Output buffer descriptor for push streaming.
size_t size
Total capacity available at dst.
void * dst
Caller-owned output region.
size_t pos
Bytes already produced by the library (in/out).
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_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_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_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
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 no-dict chunk decoder (decompression hot path).
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).
Internal definitions, constants, SIMD helpers, and utility functions.
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.
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.
#define ZXC_OPTS_LEVEL(o, dflt)
Compression level, 0 meaning the default, clamped to the highest level the encoder implements.
#define ZXC_FOOTER_MAX_SIZE_WITH_DIGEST
Most bytes a footer spans: the digest and the two longest sizes.
#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 ds_handle_need_file_header(zxc_dstream *ds, zxc_inbuf_t *in)
Handles the DS_NEED_FILE_HEADER state.
static int cs_stage_eof(zxc_cstream *cs)
Stages the 8-byte EOF block into the pending buffer.
cstream_state_t
Lifecycle states of the push compression stream.
Definition zxc_pstream.c:69
@ CS_DRAIN_FOOTER
Draining the file footer; transitions to CS_DONE.
Definition zxc_pstream.c:76
@ CS_INIT
Initial state; nothing has been emitted yet.
Definition zxc_pstream.c:70
@ CS_ACCUMULATE
Copying input bytes into the internal block accumulator until it is full (or the stream is finalised)...
Definition zxc_pstream.c:72
@ CS_DRAIN_BLOCK
A full data block was just compressed; draining it to out, then back to CS_ACCUMULATE.
Definition zxc_pstream.c:73
@ CS_DRAIN_EOF
Draining the EOF block; transitions to CS_DRAIN_FOOTER.
Definition zxc_pstream.c:75
@ CS_ERRORED
Sticky error state; subsequent calls return the latched error code.
Definition zxc_pstream.c:78
@ CS_DRAIN_HEADER
File header staged in pending; draining to out, then transitions to CS_ACCUMULATE.
Definition zxc_pstream.c:71
@ CS_DONE
Finalisation complete; further _compress / _end calls are rejected.
Definition zxc_pstream.c:77
@ CS_DRAIN_LAST
Draining the final partial block produced inside zxc_cstream_end; transitions to CS_DRAIN_EOF.
Definition zxc_pstream.c:74
static void ds_start_frame(zxc_dstream *ds)
Puts ds at the start of a frame, its buffers kept for reuse.
static int cs_set_error(zxc_cstream *cs, const int code)
Latches a sticky error on the compression stream.
static int cs_compress_block_from(zxc_cstream *cs, const uint8_t *src, const size_t len)
Compresses one block from src into cs->pending.
static int ds_handle_need_block_header(zxc_dstream *ds, zxc_inbuf_t *in)
Handles the DS_NEED_BLOCK_HEADER state.
static int cs_stage_file_header(zxc_cstream *cs)
Stages the 16-byte file header into the pending buffer.
static int ds_set_error(zxc_dstream *ds, const int code)
Latches a sticky error on the decompression stream.
static int cs_stage_footer(zxc_cstream *cs)
Stages the file footer into the pending buffer.
static int ds_pull(uint8_t *dst, size_t *used, const size_t need, zxc_inbuf_t *in)
Accumulates caller input into a buffer until it holds the wanted size.
static size_t zxc_outbuf_push(zxc_outbuf_t *out, const uint8_t *src, size_t *pos, const size_t len)
Copies what fits of a staged buffer into the caller's output.
static int ds_drain_decoded(zxc_dstream *ds, zxc_outbuf_t *out, size_t *produced)
Flushes the decoded block; 1 once fully drained.
static int cs_compress_one_block(zxc_cstream *cs)
Compresses the accumulated (full or partial) block.
static void ds_free_buffers(zxc_dstream *ds)
Releases the per-frame buffers; the next header allocates them anew.
static int cs_drain_pending(zxc_cstream *cs, zxc_outbuf_t *out)
Flushes the pending staging buffer; 1 once fully drained.
dstream_state_t
Lifecycle states of the push decompression stream.
@ DS_ERRORED
Sticky error state; subsequent calls return the latched code.
@ DS_VALIDATE_FOOTER
Validating total_out.
@ DS_NEED_FOOTER
Pulling the file footer.
@ DS_NEED_FILE_HEADER
Pulling the 16-byte file header into scratch.
@ DS_NEED_SEK_HEADER
Just past the EOF block of an archive whose header announced a seek table: pulling and validating its...
@ DS_NEED_BLOCK_PAYLOAD
Pulling a data block payload (and optional checksum) into payload.
@ DS_EMIT_DECODED
Draining decoded bytes from decoded into out.
@ DS_NEED_BLOCK_HEADER
Pulling an 8-byte block header into scratch.
@ DS_DRAIN_SEK_PAYLOAD
Skipping the SEK payload bytes.
@ DS_DONE
A frame fully consumed and validated; a call with more input starts the next one.
@ DS_DECODE_BLOCK
Calling the underlying block decoder on the accumulated payload.