Changelog
Source:NEWS.md
zukomp 0.1.0
First release of the v1 feature set: a codec registry with a uniform byte-in/byte-out API, one vendored codec family, and a stable C ABI.
R API
komp_compress()andkomp_decompress(), overidentity,deflate-raw,zlibandgzip. Raw vectors only — encoding is the caller’s decision, so character input is refused.komp_detect()andcodec = "auto". Detection is a registry property, and it refuses to guess: headerless formats returnNAand must be named, because guessing wrong there returns wrong bytes rather than an error.komp_codecs(), the capability table. It also lists the codec names zukomp reserves for future implementations (zstd,brotli, …) withavailable = FALSE; none has been released yet, and asking for one is azukomp_unsupported_codecerror that says so. Its columns includecan_flush, andlevel_fast/level_best— where the abstract level names land for each codec.-
levelaccepts a codec-native whole number,NULLfor the codec’s default, or one of the abstract names"fast","default"and"best". Numeric levels are codec-native and deliberately not comparable between codecs, so the names are the portable way to say “compress harder”; they work on every codec, including ones with no level axis, where all three mean the codec’s one behaviour.The names are declared by each codec rather than derived from
level_min/level_max, because the range is which integers are accepted, not which ones mean cheap and thorough: DEFLATE’s level 0 is stored blocks, and a codec whose level is an acceleration factor inverts the mapping entirely. komp_codec_available()andkomp_info().komp_info()$versionis a character string, so it pastes into a log line directly.Structured conditions carrying
codec,input_bytes,output_bytesandnative_status, so callers branch on the class rather than on the message.
Safety
- Decompression is capped at 1 GiB by default, overridable with
options(zukomp.max_output = ). An optionalmax_ratiois off by default, since legitimately compressible data routinely exceeds any safe-looking threshold. - Both limits must be whole numbers. They are narrowed to integer types on the way to C, which truncates toward zero, and native
0means no limit — so a fractional value in(0, 1)would otherwise disable the guard it was asked to impose.NULL,0andInfare the spellings for unlimited. - Both limits are enforced by the core stream driver rather than by codecs, so a third-party codec inherits them and cannot bypass them.
- Every checksum is verified, every truncation errors, and gzip’s ISIZE is validated against actual output — never used to size a buffer.
- Trailing data after a gzip member is a trailing-data decision, never a malformed-member one. A following member is confirmed on both magic bytes, including when they arrive in separate chunks, so the policy a tail receives does not depend on its first byte or on chunk size.
- miniz’s assertions are compiled out explicitly.
MZ_ASSERTexpands toassert()at sites reachable from malformed input, and upstream defines it unconditionally, so its behaviour was left entirely toNDEBUG; a local patch makes it overridable so that a-UNDEBUGbuild cannotabort()where an ordinary build raises a condition. - Match distances are validated against the bytes emitted so far, as RFC 1951 requires, so a malformed stream cannot read outside the decompression window. This is a local patch to the vendored miniz, which performs the check only for a non-wrapping output buffer — a configuration a streaming decoder never uses. Worth knowing if you compare zukomp’s behaviour against stock miniz: zukomp rejects three classes of stream that miniz accepts, all of which RFC 1951 section 3.2.5 forbids.
For package authors
A versioned C ABI in
inst/include/zukomp.h, reached throughzukomp-r.hand R’s registered C-callable mechanism.Other packages can register codecs at
ZU_CODEC_VENDOR_BASE. Adding a codec changes no existing declaration and needs no ABI bump. Registration is experimental in this release:zu_register_codec()andzu_codec_vtableare outside the ABI stability promise until a real satellite codec has used them, and may change in a minor release. The core limits apply to a registered codec regardless.zukomp.soexportsR_init_zukompand nothing else (on ELF and Mach-O; Windows exports through R’s.deffile). Everything is reached through R’s registration tables, and an exported miniz could otherwise bind to another package’s vendored copy in the same process.ZU_FINISHis delivered to a codec together with the final bytes, not only on a later call with an empty buffer. A codec can therefore tell “these are the last bytes” from “here are some bytes” – which gzip’s next-member probe needs, so that a stream ending in a lone0x1fis consumed exactly rather than one byte over.Registration enforces codec identity for every registration, declared or not: a declared id must carry its declared name, an undeclared id must be in the vendor range, and names and content-coding tokens must be unique — including against declared codecs whose implementation is absent, whose names are reserved for a satellite to claim with the declared id. The registry is process-global and has no removal API, so one ambiguous registration would otherwise poison codec discovery for the session.
A satellite is usable regardless of load order.
komp_codecs()caches on a registry mutation counter, so an implementation registered after the table was first read is picked up.zu_codec_vtable,zu_codec_info,zu_encoder_optsandzu_decoder_optsmay all gain fields without an ABI bump: each check requires only the prefix the core dereferences, so a consumer compiled against an older header keeps working and simply does not supply the newer fields. Options structs are copied bounded by the caller’s ownstruct_size, so a shorter one is never read past its end.-
A second route, for a consumer that needs the ZIP container layer rather than a codec – the members of an
.xlsx, for one. An installed zukomp shipslib/libzukomp.aandinclude/miniz.h, so that layer can be linked throughLinkingToinstead of vendoring a second ZIP implementation. The archive is a separate compilation of the vendoredminiz.cwith the archive APIs left in;zukomp.sokeeps exactly the trim it has, and contains nomz_zip_*code at all. The archive’s symbols have hidden visibility too: they link into the consumer as usual, but the consumer’s shared object does not re-export them. See “Using zukomp from C” in the README.Resolve it with
system.file("lib", .Platform$r_arch, package = "zukomp"). It is architecture-specific object code, so it installs underR_ARCHbeside the shared object’s directory –lib/on every single-arch platform andlib/x64/on Windows.Compile
miniz.hwithMINIZ_NO_ZLIB_COMPATIBLE_NAMESdefined and nothing else.MINIZ_NO_TIMEin particular changesMZ_TIME_T, and with it the layout ofmz_zip_archive_file_stat, between the consumer’s translation unit and the archive – a silent mismatch rather than a link error.
Notes
- gzip output is deterministic — no timestamp, no filename — for a fixed zukomp version. It is not a content hash: compressed bytes may change when a vendored codec is updated.
- Whole-buffer output is built in a
realloc’d buffer owned by an R external pointer, so growth releases superseded blocks as it goes and a large decode does not hold several at once. - miniz 3.1.2 is vendored and trimmed; provenance is reproducible from
tools/vendor/manifest.tsvand verifiable offline withtools/vendor/verify. Its MIT notice is installed atlicenses/miniz-LICENSE:miniz.hcarries no copyright line of its own, and an installed zukomp ships both that header and compiled miniz code. - Deferred to a later release: R-level streaming objects, file and connection helpers,
komp_compress_text(), dictionaries, and the brotli/zstd/LZ4/Snappy satellite packages.