25#include "../../include/zxc_pstream.h"
27#include "../../include/zxc_buffer.h"
28#include "../../include/zxc_constants.h"
29#include "../../include/zxc_error.h"
124struct zxc_cstream_s {
137 uint64_t block_index;
159 cs->error_code = code;
185 if (UNLIKELY(bound > cs->pending_cap)) {
186 uint8_t* nb = (uint8_t*)
ZXC_REALLOC(cs->pending, (
size_t)bound);
189 cs->pending_cap = (size_t)bound;
193 &cs->opts, cs->block_index);
194 if (UNLIKELY(csize < 0))
return (
int)csize;
197 cs->pending_len = (size_t)csize;
200 cs->total_out += (uint64_t)csize;
222 if (LIKELY(rc ==
ZXC_OK)) cs->in_used = 0;
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;
238 ZXC_MEMCPY((uint8_t*)out->
dst + out->
pos, src + *pos, n);
248 return cs->pending_pos == cs->pending_len;
268 if (UNLIKELY(!cs))
return NULL;
270 if (opts) cs->opts = *opts;
275 if (UNLIKELY(cs->opts.dict || cs->opts.dict_size || cs->opts.dict_huf)) {
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;
291 if (UNLIKELY(!cs->cctx)) {
295 cs->in_block = (uint8_t*)
ZXC_MALLOC(cs->block_size);
296 if (UNLIKELY(!cs->in_block)) {
306 cs->pending = (uint8_t*)
ZXC_MALLOC(cs->pending_cap);
308 if (UNLIKELY(!cs->pending)) {
327 cs->opts.checksum_enabled, 0, 0);
328 if (UNLIKELY(w < 0))
return w;
329 cs->pending_len = (size_t)w;
331 cs->total_out = (uint64_t)w;
357 .header_checksum = 0,
361 if (UNLIKELY(w < 0))
return w;
362 cs->pending_len = (size_t)w;
364 cs->total_out += (uint64_t)w;
379 cs->digest, cs->opts.checksum_enabled);
380 if (UNLIKELY(w < 0))
return w;
381 cs->pending_len = (size_t)w;
407 if (UNLIKELY(!cs))
return 0;
409 return (b == 0 || b > SIZE_MAX) ? cs->block_size : (size_t)b;
426 if (UNLIKELY(!cs || !out || !in || in->
pos > in->
size || out->
pos > out->
size ||
431 if (UNLIKELY(cs->state ==
CS_ERRORED))
return cs->error_code;
444 if (!
cs_drain_pending(cs, out))
return (int64_t)(cs->pending_len - cs->pending_pos);
450 const size_t avail_in = in->
size - in->
pos;
451 if (cs->in_used == 0 && avail_in >= cs->block_size) {
455 in->
pos += cs->block_size;
460 const size_t room = cs->block_size - cs->in_used;
461 const size_t n = avail_in < room ? avail_in : room;
463 ZXC_MEMCPY(cs->in_block + cs->in_used, (
const uint8_t*)in->
src + in->
pos, n);
468 if (cs->in_used == cs->block_size) {
500 if (UNLIKELY(cs->state ==
CS_ERRORED))
return cs->error_code;
514 if (!
cs_drain_pending(cs, out))
return (int64_t)(cs->pending_len - cs->pending_pos);
521 if (cs->in_used > 0) {
537 if (!
cs_drain_pending(cs, out))
return (int64_t)(cs->pending_len - cs->pending_pos);
546 if (!
cs_drain_pending(cs, out))
return (int64_t)(cs->pending_len - cs->pending_pos);
554 if (!
cs_drain_pending(cs, out))
return (int64_t)(cs->pending_len - cs->pending_pos);
561 return cs->state ==
CS_ERRORED ? cs->error_code : 0;
689struct zxc_dstream_s {
692 int inner_initialized;
694 int file_has_checksum;
712 uint64_t sek_remaining;
714 uint64_t block_index;
719 size_t alloc_block_size;
738 ds->error_code = code;
750static int ds_pull(uint8_t* RESTRICT dst,
size_t* RESTRICT used,
const size_t need,
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;
756 ZXC_MEMCPY(dst + *used, (
const uint8_t*)in->
src + in->
pos, n);
760 return *used == need;
770 ds->payload_cap = ds->decoded_cap = 0;
771 ds->inner_initialized = 0;
772 ds->alloc_block_size = 0;
778 ds->scratch_used = 0;
780 ds->payload_used = ds->payload_need = 0;
781 ds->decoded_size = ds->decoded_pos = 0;
782 ds->sek_remaining = 0;
800 if (UNLIKELY(!ds))
return NULL;
801 if (opts) ds->opts = *opts;
802 if (UNLIKELY(ds->opts.dict || ds->opts.dict_size || ds->opts.dict_huf)) {
806 ds->opts.n_threads = 0;
807 ds->opts.progress_cb = NULL;
808 ds->opts.user_data = NULL;
819 if (UNLIKELY(!ds))
return;
837 if (UNLIKELY(!ds))
return 0;
840 return (b == 0 || b > SIZE_MAX) ? ds->block_size : (size_t)b;
850 if (UNLIKELY(!ds))
return 0;
856 const size_t n =
zxc_outbuf_push(out, ds->decoded, &ds->decoded_pos, ds->decoded_size);
858 if (produced) *produced += n;
859 return ds->decoded_pos == ds->decoded_size;
879 const int full =
ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in);
888 uint32_t dict_id = 0;
895 ds->file_has_checksum = has_csum;
896 ds->file_has_seek = has_seek;
900 if (ds->alloc_block_size != ds->block_size) {
906 ds->payload_cap = (size_t)pb;
907 ds->payload = (uint8_t*)
ZXC_MALLOC(ds->payload_cap);
910 ds->decoded = (uint8_t*)
ZXC_MALLOC(ds->decoded_cap);
916 ds->inner_initialized = 1;
917 ds->alloc_block_size = ds->block_size;
919 ds->inner.checksum_enabled = ds->file_has_checksum && ds->opts.checksum_enabled;
923 ds->scratch_used = 0;
945 if (!
ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in))
return 1;
951 if (ds->cur_bh.block_type == (uint8_t)ZXC_BLOCK_EOF) {
956 ds->scratch_used = 0;
965 if (UNLIKELY((uint64_t)ds->cur_bh.comp_size > (uint64_t)ds->block_size))
969 const uint64_t need = (uint64_t)ds->cur_bh.comp_size +
979 if (UNLIKELY(ds->payload_need > ds->payload_cap)) {
981 uint8_t* nb = (uint8_t*)
ZXC_REALLOC(ds->payload, ds->payload_need);
984 ds->payload_cap = ds->payload_need;
1011 if (UNLIKELY(!ds || !out || !in || in->
pos > in->
size || out->
pos > out->
size ||
1015 if (UNLIKELY(ds->state ==
DS_ERRORED))
return ds->error_code;
1017 if (in->
pos == in->
size)
return 0;
1021 size_t produced = 0;
1024 switch (ds->state) {
1027 if (rc == 1)
return (int64_t)produced;
1028 if (rc < 0)
return rc;
1034 if (rc == 1)
return (int64_t)produced;
1035 if (rc < 0)
return rc;
1040 if (!
ds_pull(ds->payload, &ds->payload_used, ds->payload_need, in))
1041 return (int64_t)produced;
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;
1052 ds->decoded_cap, ds->block_index);
1056 if (ds->file_has_checksum && ds->opts.checksum_enabled)
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;
1068 ds->scratch_used = 0;
1073 ds->decoded_size = (size_t)dsz;
1074 ds->decoded_pos = 0;
1081 if (!done)
return (int64_t)produced;
1083 ds->scratch_used = 0;
1090 const int full =
ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in);
1092 ds->block_size, &ds->sek_remaining);
1094 if (!full)
return (int64_t)produced;
1101 const size_t avail = in->
size - in->
pos;
1103 (uint64_t)avail < ds->sek_remaining ? avail : (size_t)ds->sek_remaining;
1105 ds->sek_remaining -= n;
1106 if (ds->sek_remaining > 0)
return (int64_t)produced;
1108 ds->scratch_used = 0;
1116 const int full =
ds_pull(ds->scratch, &ds->scratch_used, ds->scratch_need, in);
1122 if (!full)
return (int64_t)produced;
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))
1134 ds->after_frame = 1;
1136 return (int64_t)produced;
1140 return (int64_t)produced;
1143 return ds->error_code;
struct zxc_cctx_s zxc_cctx
Opaque reusable compression context (see zxc_create_cctx).
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.
@ ZXC_ERROR_CORRUPT_DATA
Corrupted compressed data.
@ ZXC_ERROR_NULL_INPUT
Required input pointer is NULL.
@ ZXC_ERROR_MEMORY
Memory allocation failure.
@ ZXC_ERROR_BAD_HEADER
Corrupted or invalid header (checksum mismatch).
@ ZXC_ERROR_DICT_REQUIRED
File requires a dictionary but none was provided.
@ ZXC_ERROR_BAD_CHECKSUM
A block's decoded bytes fail its checksum, or the archive digest mismatches.
@ ZXC_OK
Success (no error).
@ ZXC_ERROR_BAD_BLOCK_SIZE
Invalid block size.
#define ZXC_MALLOC(size)
Heap allocator.
#define ZXC_CALLOC(nmemb, size)
Zero-initialised heap allocator.
#define ZXC_FREE(ptr)
Heap deallocator.
#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.
#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.
Compression / decompression context.
Options for decompression.
Input buffer descriptor for push streaming.
size_t size
Total bytes available in src.
size_t pos
Bytes already consumed by the library (in/out).
const void * src
Caller-owned input bytes.
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.
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.
void zxc_cctx_free(zxc_cctx_t *ctx)
Releases all resources owned by a compression context.
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.
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.
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,...
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_...
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...
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.
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.
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.
@ CS_DRAIN_FOOTER
Draining the file footer; transitions to CS_DONE.
@ CS_INIT
Initial state; nothing has been emitted yet.
@ CS_ACCUMULATE
Copying input bytes into the internal block accumulator until it is full (or the stream is finalised)...
@ CS_DRAIN_BLOCK
A full data block was just compressed; draining it to out, then back to CS_ACCUMULATE.
@ CS_DRAIN_EOF
Draining the EOF block; transitions to CS_DRAIN_FOOTER.
@ CS_ERRORED
Sticky error state; subsequent calls return the latched error code.
@ CS_DRAIN_HEADER
File header staged in pending; draining to out, then transitions to CS_ACCUMULATE.
@ CS_DONE
Finalisation complete; further _compress / _end calls are rejected.
@ CS_DRAIN_LAST
Draining the final partial block produced inside zxc_cstream_end; transitions to CS_DRAIN_EOF.
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.