Canonical guidance for AI coding agents and maintainers working in this repo.
(CLAUDE.md and GEMINI.md are pointers to this file.)
README.md— what kzstd is and how to use it.CONTRIBUTING.md— environment, build/test commands, DCO sign-off, PR flow.CHANGELOG.md— release history.- The design invariants below — do not violate them.
kzstd is a pure-Kotlin, multiplatform Zstandard (zstd) codec with dictionary support and zero runtime dependencies. It produces and reads standard zstd frames that interoperate with libzstd in both directions. It was extracted from TAKPacket-SDK.
src/commonMain/kotlin/org/meshtastic/kzstd/— the public API:Zstd(the compress/decompress facade),ZstdDictionary(a digested dictionary), andZstdException.src/commonMain/kotlin/org/meshtastic/kzstd/internal/— the RFC 8878 engine (encoder, decoder, FSE/Huffman, bit readers/writers, dictionary parser, match index). Allinternal; not part of the public API.src/{commonTest,jvmTest,nativeTest,wasmWasiTest}/— tests.scripts/train_test_dict.py— regenerates the committed trained test dictionary.
This is intentionally a single-module project: kzstd is one small codec, so the
multi-module build-logic / bom scaffolding used by larger meshtastic KMP SDKs
would be over-engineering here.
- One-shot only (no incremental streaming API yet). Every frame is independently decodable; there is no cross-call state.
- Blocks are independent, and per-frame state is threaded through them. The
encoder cuts input into 128 KiB (
Block_Maximum_Size) chunks and emits a multi-block frame; a chunk is matched only against itself and the dictionary, never against an earlier block's output. The three repeat offsets and the tables the "Repeat" / "Treeless" modes name are FRAME state —PureZstdEncoder'sFrameEntropymust keep mirroringPureZstdDecoder'sDecodeStateexactly, or a mode names one table on each side and the frame decodes to garbage that only a real-libzstd oracle catches. - Total history (dictionary content + input) is capped at 128 MiB. The
frame header always declares a window covering the full history; beyond
128 MiB that window exceeds libzstd's default decompression limit
(
ZSTD_WINDOWLOG_LIMIT_DEFAULT= windowLog 27), soencode()rejects it with aZstdExceptionup front rather than emit a frame most real-world libzstd consumers refuse. Keep this guard — it's what "frames stay libzstd-interoperable in both directions" actually requires now that multi-block encoding has no other size limit. - No shared mutable state, no lock. A
ZstdDictionarydigests its dictionary once in its constructor and is immutable thereafter; the engine objects keep all per-call state in locals. Do not reintroduce global caches. The encoder's predefined FSE tables must stayby lazy(safe cross-thread publication) — this is what lets the codec carry noatomicfu/lock. maxSizeis a required decompression-bomb guard on every decode.- The public API throws only
ZstdException(annotated@Throwsso it bridges to Swift / Kotlin-Native callers instead of aborting the process). explicitApi()+ binary-compatibility-validator: run./gradlew apiDumpafter any public-API change and commitapi/kzstd.api.- Frames stay libzstd-interoperable in both directions — guarded by the
jvmTestzstd-jni oracle and the pinned cross-target dict-entropy fixture.
export JAVA_HOME=/path/to/jdk-21
./gradlew build # compile all 13 targets, run tests, apiCheck
./gradlew jvmTest # JVM tests (incl. the zstd-jni interop oracle)
./gradlew apiDump # refresh the API baseline after public-API changes
python3 scripts/train_test_dict.py # regenerate the trained test dictionaryThe interop oracle (zstd-jni) and the native concurrency test run on macOS; the
iOS/tvOS simulator tests need their SDKs installed (CI runs the full matrix on
macos-latest). Linux/Windows native test binaries are cross-compiled but run only
on their own host.
Maven Central via the vanniktech plugin (org.meshtastic:kzstd); JitPack is a
fallback (com.github.meshtastic:kzstd). Releases are tag-driven — see
RELEASING.md.
- Commits are signed off (DCO):
git commit -s. The repo owner prefers to be the commit author — do not addCo-Authored-Bytrailers. - Source files carry an
SPDX-License-Identifier: GPL-3.0-or-laterheader. - Commit messages: imperative mood, with a body explaining what + why.
- Do not auto-commit; stage changes and describe what you did.