From d5acbc5ed13396855d5fc460f26be9eb7f734382 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 19 Oct 2026 14:07:27 +0300 Subject: [PATCH 01/11] feat(decoding): decode the v0.5, v0.6 and v0.7 frame formats libzstd 1.5.7 reads these by default (legacy level 5 in every upstream build system and in the distributions), so the decoder needs them too. - decoding/legacy: one module per format plus the shared entropy layer (FSE tables up to 4096 states, the Huffman description with its weight-run header, one and four streams). Only the backward bit reader is shared with the RFC 8878 decoder; the formats differ from it and from each other in headers, literal and sequence coding, dictionaries and how an FSE string ends. - A block decodes to at most 128 KiB; a v0.7 content checksum is verified; the declared content size is not enforced, since the v0.6.2 command line declares its first chunk's size. - `legacy` feature, on by default. - scripts/legacy-fixtures.sh builds the v0.5.1, v0.6.2 and v0.7.5 command lines from digest-checked tags and writes zstd/legacy_frames, each frame checked to decode through libzstd 1.5.7 to its source. Part of #131 --- scripts/legacy-fixtures.sh | 85 +++ zstd/Cargo.toml | 8 +- .../ModemManager.service.v05.dict.zst | Bin 0 -> 108 bytes .../ModemManager.service.v06.dict.zst | Bin 0 -> 108 bytes .../ModemManager.service.v07.dict-noid.zst | Bin 0 -> 115 bytes .../ModemManager.service.v07.dict.zst | Bin 0 -> 116 bytes .../ModemManager.service.zst.v05.dict.zst | Bin 0 -> 101 bytes .../ModemManager.service.zst.v06.dict.zst | Bin 0 -> 102 bytes .../ModemManager.service.zst.v07.dict.zst | Bin 0 -> 103 bytes ...orkManager-dispatcher.service.v05.dict.zst | Bin 0 -> 262 bytes ...orkManager-dispatcher.service.v06.dict.zst | Bin 0 -> 262 bytes ...orkManager-dispatcher.service.v07.dict.zst | Bin 0 -> 256 bytes zstd/legacy_frames/abc.txt.v05.l1.zst | Bin 0 -> 37 bytes zstd/legacy_frames/abc.txt.v05.l19.zst | Bin 0 -> 37 bytes zstd/legacy_frames/abc.txt.v06.l1.zst | Bin 0 -> 38 bytes zstd/legacy_frames/abc.txt.v06.l19.zst | Bin 0 -> 38 bytes zstd/legacy_frames/abc.txt.v07.l1.zst | Bin 0 -> 38 bytes zstd/legacy_frames/abc.txt.v07.l19.zst | Bin 0 -> 38 bytes zstd/legacy_frames/dictionary.v05 | Bin 0 -> 4069 bytes zstd/legacy_frames/dictionary.v06 | Bin 0 -> 3992 bytes zstd/legacy_frames/dictionary.v07 | Bin 0 -> 4027 bytes zstd/legacy_frames/z000000.v05.l1.zst | Bin 0 -> 8138 bytes zstd/legacy_frames/z000000.v05.l19.zst | Bin 0 -> 6381 bytes zstd/legacy_frames/z000000.v06.l1.zst | Bin 0 -> 8007 bytes zstd/legacy_frames/z000000.v06.l19.zst | Bin 0 -> 5742 bytes zstd/legacy_frames/z000000.v07.l1.zst | Bin 0 -> 8004 bytes zstd/legacy_frames/z000000.v07.l19.zst | Bin 0 -> 5734 bytes zstd/legacy_frames/z000000.v07.nocheck.zst | Bin 0 -> 7971 bytes zstd/legacy_frames/z000001.v05.l1.zst | Bin 0 -> 35 bytes zstd/legacy_frames/z000001.v05.l19.zst | Bin 0 -> 35 bytes zstd/legacy_frames/z000001.v06.l1.zst | Bin 0 -> 36 bytes zstd/legacy_frames/z000001.v06.l19.zst | Bin 0 -> 36 bytes zstd/legacy_frames/z000001.v07.l1.zst | Bin 0 -> 36 bytes zstd/legacy_frames/z000001.v07.l19.zst | Bin 0 -> 36 bytes zstd/legacy_frames/z000013.v05.l1.zst | Bin 0 -> 12 bytes zstd/legacy_frames/z000013.v05.l19.zst | Bin 0 -> 12 bytes zstd/legacy_frames/z000013.v06.l1.zst | Bin 0 -> 13 bytes zstd/legacy_frames/z000013.v06.l19.zst | Bin 0 -> 13 bytes zstd/legacy_frames/z000013.v07.l1.zst | Bin 0 -> 13 bytes zstd/legacy_frames/z000013.v07.l19.zst | Bin 0 -> 13 bytes zstd/legacy_frames/z000021.v05.l1.zst | Bin 0 -> 31740 bytes zstd/legacy_frames/z000021.v05.l19.zst | Bin 0 -> 26642 bytes zstd/legacy_frames/z000021.v06.l1.zst | Bin 0 -> 30798 bytes zstd/legacy_frames/z000021.v06.l19.zst | Bin 0 -> 25332 bytes zstd/legacy_frames/z000021.v07.l1.zst | Bin 0 -> 31693 bytes zstd/legacy_frames/z000021.v07.l19.zst | Bin 0 -> 26588 bytes zstd/legacy_frames/z000046.v05.l1.zst | Bin 0 -> 2803 bytes zstd/legacy_frames/z000046.v05.l19.zst | Bin 0 -> 2803 bytes zstd/legacy_frames/z000046.v06.l1.zst | Bin 0 -> 2805 bytes zstd/legacy_frames/z000046.v06.l19.zst | Bin 0 -> 2805 bytes zstd/legacy_frames/z000046.v07.l1.zst | Bin 0 -> 2805 bytes zstd/legacy_frames/z000046.v07.l19.zst | Bin 0 -> 2805 bytes zstd/legacy_frames/z000062.v05.l1.zst | Bin 0 -> 8 bytes zstd/legacy_frames/z000062.v05.l19.zst | Bin 0 -> 8 bytes zstd/legacy_frames/z000062.v06.l1.zst | Bin 0 -> 8 bytes zstd/legacy_frames/z000062.v06.l19.zst | Bin 0 -> 8 bytes zstd/legacy_frames/z000062.v07.l1.zst | 1 + zstd/legacy_frames/z000062.v07.l19.zst | 1 + zstd/src/decoding/errors.rs | 114 +++ zstd/src/decoding/legacy/entropy.rs | 568 ++++++++++++++ zstd/src/decoding/legacy/entropy/tests.rs | 149 ++++ zstd/src/decoding/legacy/mod.rs | 693 ++++++++++++++++++ zstd/src/decoding/legacy/tests.rs | 422 +++++++++++ zstd/src/decoding/legacy/v05.rs | 417 +++++++++++ zstd/src/decoding/legacy/v06.rs | 312 ++++++++ zstd/src/decoding/legacy/v07.rs | 495 +++++++++++++ zstd/src/decoding/mod.rs | 5 + 67 files changed, 3269 insertions(+), 1 deletion(-) create mode 100755 scripts/legacy-fixtures.sh create mode 100644 zstd/legacy_frames/ModemManager.service.v05.dict.zst create mode 100644 zstd/legacy_frames/ModemManager.service.v06.dict.zst create mode 100644 zstd/legacy_frames/ModemManager.service.v07.dict-noid.zst create mode 100644 zstd/legacy_frames/ModemManager.service.v07.dict.zst create mode 100644 zstd/legacy_frames/ModemManager.service.zst.v05.dict.zst create mode 100644 zstd/legacy_frames/ModemManager.service.zst.v06.dict.zst create mode 100644 zstd/legacy_frames/ModemManager.service.zst.v07.dict.zst create mode 100644 zstd/legacy_frames/NetworkManager-dispatcher.service.v05.dict.zst create mode 100644 zstd/legacy_frames/NetworkManager-dispatcher.service.v06.dict.zst create mode 100644 zstd/legacy_frames/NetworkManager-dispatcher.service.v07.dict.zst create mode 100644 zstd/legacy_frames/abc.txt.v05.l1.zst create mode 100644 zstd/legacy_frames/abc.txt.v05.l19.zst create mode 100644 zstd/legacy_frames/abc.txt.v06.l1.zst create mode 100644 zstd/legacy_frames/abc.txt.v06.l19.zst create mode 100644 zstd/legacy_frames/abc.txt.v07.l1.zst create mode 100644 zstd/legacy_frames/abc.txt.v07.l19.zst create mode 100644 zstd/legacy_frames/dictionary.v05 create mode 100644 zstd/legacy_frames/dictionary.v06 create mode 100644 zstd/legacy_frames/dictionary.v07 create mode 100644 zstd/legacy_frames/z000000.v05.l1.zst create mode 100644 zstd/legacy_frames/z000000.v05.l19.zst create mode 100644 zstd/legacy_frames/z000000.v06.l1.zst create mode 100644 zstd/legacy_frames/z000000.v06.l19.zst create mode 100644 zstd/legacy_frames/z000000.v07.l1.zst create mode 100644 zstd/legacy_frames/z000000.v07.l19.zst create mode 100644 zstd/legacy_frames/z000000.v07.nocheck.zst create mode 100644 zstd/legacy_frames/z000001.v05.l1.zst create mode 100644 zstd/legacy_frames/z000001.v05.l19.zst create mode 100644 zstd/legacy_frames/z000001.v06.l1.zst create mode 100644 zstd/legacy_frames/z000001.v06.l19.zst create mode 100644 zstd/legacy_frames/z000001.v07.l1.zst create mode 100644 zstd/legacy_frames/z000001.v07.l19.zst create mode 100644 zstd/legacy_frames/z000013.v05.l1.zst create mode 100644 zstd/legacy_frames/z000013.v05.l19.zst create mode 100644 zstd/legacy_frames/z000013.v06.l1.zst create mode 100644 zstd/legacy_frames/z000013.v06.l19.zst create mode 100644 zstd/legacy_frames/z000013.v07.l1.zst create mode 100644 zstd/legacy_frames/z000013.v07.l19.zst create mode 100644 zstd/legacy_frames/z000021.v05.l1.zst create mode 100644 zstd/legacy_frames/z000021.v05.l19.zst create mode 100644 zstd/legacy_frames/z000021.v06.l1.zst create mode 100644 zstd/legacy_frames/z000021.v06.l19.zst create mode 100644 zstd/legacy_frames/z000021.v07.l1.zst create mode 100644 zstd/legacy_frames/z000021.v07.l19.zst create mode 100644 zstd/legacy_frames/z000046.v05.l1.zst create mode 100644 zstd/legacy_frames/z000046.v05.l19.zst create mode 100644 zstd/legacy_frames/z000046.v06.l1.zst create mode 100644 zstd/legacy_frames/z000046.v06.l19.zst create mode 100644 zstd/legacy_frames/z000046.v07.l1.zst create mode 100644 zstd/legacy_frames/z000046.v07.l19.zst create mode 100644 zstd/legacy_frames/z000062.v05.l1.zst create mode 100644 zstd/legacy_frames/z000062.v05.l19.zst create mode 100644 zstd/legacy_frames/z000062.v06.l1.zst create mode 100644 zstd/legacy_frames/z000062.v06.l19.zst create mode 100644 zstd/legacy_frames/z000062.v07.l1.zst create mode 100644 zstd/legacy_frames/z000062.v07.l19.zst create mode 100644 zstd/src/decoding/legacy/entropy.rs create mode 100644 zstd/src/decoding/legacy/entropy/tests.rs create mode 100644 zstd/src/decoding/legacy/mod.rs create mode 100644 zstd/src/decoding/legacy/tests.rs create mode 100644 zstd/src/decoding/legacy/v05.rs create mode 100644 zstd/src/decoding/legacy/v06.rs create mode 100644 zstd/src/decoding/legacy/v07.rs diff --git a/scripts/legacy-fixtures.sh b/scripts/legacy-fixtures.sh new file mode 100755 index 00000000..e36b98e6 --- /dev/null +++ b/scripts/legacy-fixtures.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Regenerate zstd/legacy_frames: frames written by the v0.5, v0.6 and v0.7 +# release command lines, which the decoder must read back byte-exact. +# +# Usage: scripts/legacy-fixtures.sh [WORK_DIR] +# +# Each release is fetched from its tag, checked against a pinned digest and +# built from source. The inputs are files already in the repository, so every +# frame decodes to a file the tests can compare against. +set -euo pipefail + +root="$(cd "$(dirname "$0")/.." && pwd)" +# shellcheck source=scripts/upstream-zstd.sh +source "$root/scripts/upstream-zstd.sh" + +work="${1:-$(mktemp -d)}" +mkdir -p "$work" +out="$root/zstd/legacy_frames" +corpus="$root/zstd/decodecorpus_files" +samples="$root/zstd/dict_tests/files" + +# version tag, tarball digest +releases=( + "0.5.1 c23be381911764056087a6d0280544589f2fe44daef4c9223fe08ede6def45dc" + "0.6.2 f80c35b2860d66299c8457b891d05277ce4e88b9bdd1f10a431e74804404cb8f" + "0.7.5 6800defac9a93ddb1d9673d62c78526800df71cf9f353456f8e488ba4de51061" +) + +# Shapes the block decoder takes different paths for: empty, one byte, a +# few bytes, short text, several blocks, data that stays raw, and a mixed +# file with long matches and four-stream literals. +inputs=(z000062 z000013 z000001 abc.txt z000000 z000046 z000021) + +# build : the release's command line; prints its path. +build() { + local version="$1" digest="$2" dir="$work/v$1" + mkdir -p "$dir" + fetch_verified "https://github.com/facebook/zstd/archive/refs/tags/v${version}.tar.gz" \ + "$digest" "$dir/zstd-${version}.tar.gz" + rm -rf "$dir/zstd-${version}" + tar -xzf "$dir/zstd-${version}.tar.gz" -C "$dir" + make -s -C "$dir/zstd-${version}/programs" zstd MOREFLAGS=-Wno-error >&2 + echo "$dir/zstd-${version}/programs/zstd" +} + +rm -rf "$out" +mkdir -p "$out" + +for release in "${releases[@]}"; do + read -r version digest <<<"$release" + tag="v0${version:2:1}" + zstd_bin="$(build "$version" "$digest")" + + for input in "${inputs[@]}"; do + for level in 1 19; do + "$zstd_bin" -q -f "-$level" "$corpus/$input" -o "$out/$input.$tag.l$level.zst" + done + done + + # A dictionary trained by the release itself, and frames that need it. + # The training set is listed in sorted order, so the dictionary is the same + # on every run; v0.7 would pick a random ID, so it gets a fixed one. + dict_args=() + if [[ "$tag" == v07 ]]; then + dict_args=(--dictID 7) + fi + find "$samples" -type f | LC_ALL=C sort | head -n 200 >"$work/samples.$tag" + # shellcheck disable=SC2046 + "$zstd_bin" -q -f --train $(cat "$work/samples.$tag") --maxdict 16384 \ + "${dict_args[@]}" -o "$out/dictionary.$tag" + for sample in $(LC_ALL=C ls "$samples" | head -n 3); do + "$zstd_bin" -q -f -3 -D "$out/dictionary.$tag" "$samples/$sample" \ + -o "$out/$sample.$tag.dict.zst" + done + + if [[ "$tag" == v07 ]]; then + # The content checksum and the dictionary ID are optional in v0.7. + "$zstd_bin" -q -f -3 --no-check "$corpus/z000000" -o "$out/z000000.$tag.nocheck.zst" + sample="$(LC_ALL=C ls "$samples" | head -n 1)" + "$zstd_bin" -q -f -3 --no-dictID -D "$out/dictionary.$tag" "$samples/$sample" \ + -o "$out/$sample.$tag.dict-noid.zst" + fi +done + +echo "wrote $(find "$out" -type f | wc -l | tr -d ' ') files to $out" diff --git a/zstd/Cargo.toml b/zstd/Cargo.toml index 9157b354..ce737ee7 100644 --- a/zstd/Cargo.toml +++ b/zstd/Cargo.toml @@ -11,7 +11,7 @@ license = "Apache-2.0" homepage = "https://structured-world.github.io/structured-zstd/dev/bench/" repository = "https://github.com/structured-world/structured-zstd" description = "Pure-Rust Zstandard (zstd) compression and decompression: all levels, streaming, dictionaries, no_std and WebAssembly ready — no FFI, no cmake" -exclude = ["fuzz_decodecorpus/*", "decodecorpus_files/*", "dict_tests/files/**"] +exclude = ["fuzz_decodecorpus/*", "decodecorpus_files/*", "dict_tests/files/**", "legacy_frames/*"] # Package metadata points at a crate-local symlink so the packaged crate and repo root README stay in sync. readme = "README.md" keywords = ["zstd", "zstandard", "decompression", "compression", "no-std"] @@ -129,6 +129,7 @@ default = [ # does not want it turns default features off, as `no_std` builds already # do. "dict-builder", + "legacy", ] # Per-CPU-tier kernel selection, covering the decoder and the encoder. The # default build enables every kernel except `kernel-vbmi2`; with `std`, @@ -168,6 +169,11 @@ kernel-sve = ["kernel-neon"] # simd128 equivalent and stay scalar on wasm. kernel-simd128 = [] dict-builder = ["std", "dep:fastrand"] +# Decode frames of the v0.5, v0.6 and v0.7 formats, the ones every libzstd +# 1.5.7 build reads by default (upstream's legacy level 5). Without it such a +# frame is refused as an unknown format, as a libzstd built at level 0 +# refuses it. Pure decoding code, no dependency. +legacy = [] hash = ["dep:twox-hash"] # Long-distance matching in the encoder. Implies `hash`: the LDM match finder # hashes each `min_match_length` window with XXH64. Without it the parameter diff --git a/zstd/legacy_frames/ModemManager.service.v05.dict.zst b/zstd/legacy_frames/ModemManager.service.v05.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..9ee9c46d7b75726287b7a21693a0366841c9576b GIT binary patch literal 108 zcmV-y0F(bEwJ-ev003c7Y!z(X!ttDjFT%l-u zxONC2vKTzi)A|i+kza2Kd_K#fQAU6>u=WI$#9^pd0@BkHn{$Gj8#^_DZ8$5K;l3(D O(igmFi^XdNzyJW8PcYj6 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/ModemManager.service.v06.dict.zst b/zstd/legacy_frames/ModemManager.service.v06.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..25a3152a98afa53726b25b6d934d505598879df1 GIT binary patch literal 108 zcmV-y0F(bFwJ-gGnE(I)Ur=lnY~8}~uyA5|MF}=`NH^Tq` literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/ModemManager.service.v07.dict.zst b/zstd/legacy_frames/ModemManager.service.v07.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..8dbe1dbfaa27caa32721e6752022ab0e1718e2ee GIT binary patch literal 116 zcmV-)0E_=8wJ-f;2bll>0A^5_8XC|>IjP%SEtM^(<387|9g2{)WloQBoV6w%sBNxX ztQ*HVvrOFNlTs6DWDK9J?ia^Jn;e9D3QuOYRw4q9kPM#A@ux}DXpATA?u4=#k%iwV W*h~89vf%iVFC~ok%w|vx{4u@OJvYk$ literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/ModemManager.service.zst.v05.dict.zst b/zstd/legacy_frames/ModemManager.service.zst.v05.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..a2f5c5033667ff73d8fc3318c377793b34cfb2b1 GIT binary patch literal 101 zcmV-r0Gj_LwJ-evKmb}OwJ-f=NO0aHnE*us000_%0H5{ug~w1V?Q6@9-Fj(iYDf?D zv+d797Pg|0Fnle-@?|TxBeIFSPlPb2M^o#}r086NuKj9yg_DCto+9hhQCKS&&F+T!OCsYI=o}gGHVq>(fzKD<4jp J>W$&ixz9~ZGARH6 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/NetworkManager-dispatcher.service.v05.dict.zst b/zstd/legacy_frames/NetworkManager-dispatcher.service.v05.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..0f1b0bdbdff3cd3e41e61b5ee496f130966232dd GIT binary patch literal 262 zcmV+h0r~zVwJ-ev008?^e0D5bw{SeHZ8(qr%Y2IW^ZjzmW4GIJ%>i`BAx)2vkP z@ymGaT({qC;E$!sbkyL>=egUpUDI&%dE5hk_xm-?ZM&s@_3dIH#=6|%eKodW7mnvV z{v7(<{@0e_x4ZnW+h?>|P-F_1 z!FF8s?pSLAGa&qZku<^|^ytMgw{OOhp`{W9fR^d>Jyg3CXdvIdVHj|JgLnzmmW0=r M(UQaFs0zRU0JU9|{r~^~ literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/NetworkManager-dispatcher.service.v06.dict.zst b/zstd/legacy_frames/NetworkManager-dispatcher.service.v06.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..25eb205e3e6acd9a592efb8653d6cfae92d035f8 GIT binary patch literal 262 zcmV+h0r~zWwJ-gGb^!nY`BHp#EL*p5JgjXvkN?Yjiud#Va?4}4+i}e8@AZq-vV7C5 zRPOQ1cJ>1;@^J<`3GOz>n8ajh(O{ MCNt+ZfK9*v0Nrho<^TWy literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/NetworkManager-dispatcher.service.v07.dict.zst b/zstd/legacy_frames/NetworkManager-dispatcher.service.v07.dict.zst new file mode 100644 index 0000000000000000000000000000000000000000..316b961ce1998e7dde7e8f7f9a34d12ca47f18b5 GIT binary patch literal 256 zcmV+b0ssCdwJ-f;2X+Af0P<3_aL`6MDSPDz|CG1VJMT2|#3a+4eBbsHOQ~0(i1s`! z#m<&`&iKBP339X&l2e4EWZZRq?X^{E=a5UPm)%yrH*){7i>U00h)UB~`Zuq{(KQpLF@AKWAnhBMY`|>xVZ6chx_%=i> zfkxfB=_UaVlw@rebXXBQStyT4d0{1vfz3QL;8x5)3P81lfDnAIS9oX$TOU#@O*RnX zw3IVW81S(s8i`yKJFJ_|ggi7##<%;buZkSgOF`B1mBRzpoK+rA^^y277cup^RCTff G&0k5@pn#zO literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/abc.txt.v05.l1.zst b/zstd/legacy_frames/abc.txt.v05.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..c8159097ba123108f7c6fbc8daa38c8fd3cdd78c GIT binary patch literal 37 scmY%0s{fb4fk7%UDLExIEj=SMD?2AQFTbF$sJNuGth}O<>i`1-00dnP&;S4c literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/abc.txt.v05.l19.zst b/zstd/legacy_frames/abc.txt.v05.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..c8159097ba123108f7c6fbc8daa38c8fd3cdd78c GIT binary patch literal 37 scmY%0s{fb4fk7%UDLExIEj=SMD?2AQFTbF$sJNuGth}O<>i`1-00dnP&;S4c literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/abc.txt.v06.l1.zst b/zstd/legacy_frames/abc.txt.v06.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..f0bc759dea45c7cc3ff9912162afef2fa5aa44c5 GIT binary patch literal 38 tcmY$}s{hwX%7H;DF)2AEH7z|OGb=kMH!r`Su&B7Cw5+_MlIs8i0{{}J4lMux literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/abc.txt.v06.l19.zst b/zstd/legacy_frames/abc.txt.v06.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..f0bc759dea45c7cc3ff9912162afef2fa5aa44c5 GIT binary patch literal 38 tcmY$}s{hwX%7H;DF)2AEH7z|OGb=kMH!r`Su&B7Cw5+_MlIs8i0{{}J4lMux literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/abc.txt.v07.l1.zst b/zstd/legacy_frames/abc.txt.v07.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..7e85747a27092c23a2260683c5390fb48b857f90 GIT binary patch literal 38 tcmY%2s{dC-%7H;DF)2AEH7z|OGb=kMH!r`Su&B7Cw5+_MlIweLIRFrp4-o(W literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/abc.txt.v07.l19.zst b/zstd/legacy_frames/abc.txt.v07.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..7e85747a27092c23a2260683c5390fb48b857f90 GIT binary patch literal 38 tcmY%2s{dC-%7H;DF)2AEH7z|OGb=kMH!r`Su&B7Cw5+_MlIweLIRFrp4-o(W literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/dictionary.v05 b/zstd/legacy_frames/dictionary.v05 new file mode 100644 index 0000000000000000000000000000000000000000..3e915d67d9ad834a6443e6a2b933fbb1a7269bba GIT binary patch literal 4069 zcmaJ^Ux*|}9iE(rxfG}y@F7Yb3NOd)5qtV>C3;+FyxE=E-Qjj;Co{8m3E^04x@&fd z?XK#ps-DRx!U-Y@$%~?x2SH*UVi3HSh%bTxA0iSEd>8cDC*K9Xs_vfMy_?G}?Dn7P z`u+R9@As=$-oO5zXzhi6+-&^)(Qlq*UuZlUH~w_t`7d5d2J{kIF<_cq^q?z!KMzVe4JCymB;8XsSJ`<-{*{nhW~ml_}K ze)Y=z57%G&*P}mv@X5np{&?-(-L+?b^tA`;FMj@W7p~JwAK$$AgSAFu?IC*sHp-ue zt@^5Q!Ch@Oo>@LPbMfbAUb*tx!Ka^eKltzT_FvhHe_8+G+gNzpFS&T>bmc^7K47LSKEoM_GyZcePq!{C@pt{J{tzj=)u6gDqxq?QV78{n4OxPtq{qv+zonz>~I$i;PRltWYu{m+2lhk%r@HH0AVa zAZ)!g&6E_do@9K6FOpbg?n_wW6uIeQ`!;tzn25R+ctOon6=_n2X%@zCV_?VBTPD(E z%sf!qIydntfnDY^Mop2bVHB1eg~s+7YDU<0-3f^ zSQs5jLf-63V_BNsC-<1NJlR}C6m^IT-b`m%uF@2dX(TybP!9x6XkmmrrsBIkJv4*+ z@Xk=|qY-$PPXx-T>u?AeEpi@-B;{m)3S$vDklRe8h2}TN7R%XKq{1#XRUs4XJmfa2 zYfkxwiFGkPVkvJ{^>^+Ep>cSl0?F4&unmiN=5Qm!;}W@L?9Et7t5r%ig-pyyMHFFe z1o-1H+AKi^Xi}IFxiul(XA)A}CO=CejV$5Yjyu!bVc0Nbpw54B%LigBr5_QTV^(-Ta>~(K1BB zElpL-(qLlh9jZ)g4xUkGI^{@T$uB$#kvs=iOP5?9CNK%Rpr1;3!A99T$(DKs-1%aTf2jU zgAqUu5sYq^$qMT7IWjmhJ3io*t5a1onz$4U_=5roGg0Yh)8srBEM5QBH4;^zO)S~u z6Xe^iaTtXd0FsFU7vty~83YF+v`Q-4o)TxH^JKTAT}peKqn-)W!_{0WQb|kYJAo9I zvDYYDCZl=d4BMCSxp>ZG(%Vv!Lr^WDHRvL%3r_l4S)gQBWjq20od)b^Rpe#)s<&sd zQ+A}9E`DcZ#KDb#`(9vt<9ob2I4bypwOd<92X{MzLATxU5BMpsT1kS)%{I%RbjSoc zgA?x94n6QAW3`BF?Xw41`DP2!+|rCSTJ<<#<2UM{gj?qE9HR|$_GmC#W`N|7ISclzCa$H~JG zVu9P;!@f7_u&}fjm||R-4S=2iA>p+Nt_X~g?vnUVDq@#afiX8(o_$fmJQ){;Y!=3e zdZhGm^UO%utgsp6dP$DcVkOf!N1q_we#PlFmy3wW1=#}$`{?3IGB?e5qx%>b^ujH< z2ZnH$ilS;}J=NoLS9bIaOiz`MPw80Ud_F(tBYY0$;zWc)HL-K1d0oDcpH3WZ$mqN! zj5Xz`QS)*|fNuEn9l}p2XCk+#e8zp^um3A*UBR!jufx9HwZe=pc*-guc@##sTU796MC zbVBhzcVoJm)I(RZTv9RGEpTL@rUKThbfF50nFYrJ%&HK9F(6EZrRV{n(l`JmVzR&k zX9yEK*M0#UZD&;sFV!Ig_=MFcBwKi7e6||GgEAL zRi~x98SE6h^FtmM-1c*#s`N)<;&Otp6H&fe*uH#zJN?oOqO&t#U- zh#m>YICG4i9vyd24tuo7*10a-A|i(}mu^7k!nt*uiy133*EVG)EP+Yuv=KBGE{&z7 zE9n-LMJ#o4$q)al%zmJa^I_ss0>3O&jM_5ObE$Bnc5pK2Kj3*3oVTq`&xETZqOy1=Sg^YAc~I9B zkQtZ6r8Ti4Z0-I!j^4Ao0AINwm{6=@2qm7b1~76J&%A<0W#e_L$5{BW{pgRxSi;P zBCVSd{__}ZS0Dp4Dcs1y*_a+Ng>(#AXzLWq1%36X(;p1`UBa0_ZE3VEk~ovoC2$ta zOXNs*J44TlQ%iO=k_z=#!mYG9i^yd1-jKOP5)j9wEtpFe!q^00a3beXRQ;d_bqL8C zpae88<1%OJHs9L89Hf#_iGHx#CMuefPCk|3#HP~{Wbg&b zS-Ox)a1_5z+{L;OYFVhbKUa`~W7UTpa6eRl+(-Eke-}EHf^+M{mlBu)V0?veaqobs zd0B)@F9??_ws5Ynx8u;Aac{EU?^ZtpmXu97jU;XG!$XYY_r}n}qyAt3D422QP!Hd8rAaL<& zs7^JJ8oC6_sECIf0r!Kzgu(aZbnsLN4eNGxPmk~QMx%bW7jB4iUUfJ}=5~+eP&!ls zlfesj>c#>1iFHP%u8BDSta`EwY3>-%#ODINL=mC7OF=HHSkQga35efbcr?UMp-z_R zRgx~4i6X5a2BN8arP5aB1$4R^w3|8Mp%s#O-HU`HW5*O{hWCuoCXou_B^?os95i6! z+w62^9>g`yHqOu45$4_bWoX1RhJ@FUhGCW2WKiL^*6_BVHS3-b0^hWXMm!2h49Xmw zOnSrK=!l$`emmtZLFle9N@SBV>=c)t*pU86WSa46-ugOLrPamx9WN(C6IPr_UP;%` zY_yeBnKjr5c&@$(_-;mO!m<@x+vd{q_ASz+SYow7GZY4tR(?H6#+7^U-)n{+I?dlG znDB5M35GEO`gnIEOtpZ~Dx8hw&DQcvUT&~N?qWMU(p)5vO6aI1mB^3#`@{aQ=jCAx zvB2fQ$uKzeSlIZBY&osmMnF%5lGAXH2YkwOU<@5i!Rrc_!nrp1Bn>qG?|&&NmLfWb{6i z*4b*+Xn46LLBITk2@y7w3z<7~K4(&eHo#alX*WGRFU&@xVcn0zdd2jR-|XQ{0MO9w zDg~jQWq=-5p(h5zld6A#q+9iITQ{~%u%}n0%`)@_n(_V#rtCvxQEiR>l+f8Gc78qE z%>7H#YD)$2?$gWo7sPj!(($nSAgT{1Q6F>G$(a=9Ha*-Q9!59YH{KvuaaO>o1;;7B zolyKO{F<(3&C=DbUa1-FU+`q0wg%R_bgfH@odw4N?5YrfDIiRxqnH7)HaGxrIa}j~ zcZ3aIn6Lsu<#X%Z_=C#+;DjE6@p!f$&TAPRmQ!@PIayJLxcnW9C6!Ls-j@Gx&xg`r zobLG(LO6B2O9c*xFt-Hb{NV<hv#Xi0`{Vt5 zp67k*wfA2A@RN;3WAOa9uHEW2{`1jCU%mg$WBmK~2Y-A2>kk$`{N3eeZhhwMpIkS8 z?En1P{SW^qr+b&?o#+32^EY?sr+?j!-+5vB?N$EF*6EdByhPub|MOqJ|I4qw_ovmR zTaCu{+xb^s{AvI2ms^cXeTU%QQtKoiK!q2n#nF`I9EInzp+r!Q%EsW(F{|(m* z9}X{^$mhTO)XFmx&GoU(w$RxB7|A)U8)JhI_b zbDnT>YE>QvGcLJicyIg5*Ga#yd0`{X^OVJ0HeKi<=0e+orIb@UXO>bf*(Bwo&hTD$ zw}a?UhOPUOh6!JU*SZ9ev{hVWTv}#@k`cK|4`D_LU)~0Euq=5G%;|siz z#42-d!V{;cO&8|d+{Iub>Yl(2YUZj)lQK=SGKL!iKc+UBNRu)1rqb4hiB}2yGG8%j zid4;n#D&Viaj*Lz$pY`ZX;gg5Z50s}`Io!}qjHxAwRHiRwozCZ9ZEvp=t^T*nm#1= znY29FSw$3mhzs6K7g?^-6q#uhIbKl@1WjmRgq%_F4PPFbA$&wBiWCLjO_>24D{@dnwtpM`cXp8f^<`S7NVuh`idh;=O>Lp7wC3O$ zb*@v6@|FC;r4Yq)a5Wy7D7sN8FKJF%*+hU7>rM-h!4)WD@mxrrp!ikdHdHy6i(H1? znS>PdWfyk9eP01`ALT>*ZRk`C&aEO>N?;0raTUUsdk0L;irjy7f^e~7b8B;RJM`Td zb;kSMcKOj`Nt!sJfuJ>h5+CE}^%3;&sN3rS3h0z0vh%eT07B8%*rqhs$^k!O3pN)) zS;&f!#Gp;1vQ`gC%;bb?&8jX3W?tAtE#=x(8B`q{0@ytm_WNUi93mKftt?hhx6hHm zk=gM9w_Km9p3%gWV94JrP%smfaW+lPW5Lqx*RGSO0&QZ+4xggl?iq(shyfs(C~z^3 zZeP!HF0sYoR)RqO;(Sms`%Y+a1zjdN^Y=jZY{ zS4r7=8z(vgr5FJTF<<)8*rvdKOfrKZ0-Dv9e&=O zef5oa%8+mxQZuYH8}&;3RvO;qv|`<3Lg1QK(kKah61^e=C!@}wGdv<^rQb}s3lO@? zwdCoz@H@qYJI1F!#JMDAicJW>gdAeGQjLrRe!b@vC|LC49%2x5V+2gd{N)L~)mFEGWV zbQ=IY0aC(k6I>HmBi%>hU#W;)b_LGdW_kWb3G-x97_w6sC+d;Xv*x*#vRPp>$n}~W zXU$6HagI4bx`T?-Z7x?4lPhuv5)LrMm1J(4@lKDhFzA(gj?~mHndWu-LVmV!xFw_WmN3?ot47Vs4GFsC&rb-y zp`6RyqVpLO(zgM|%1OKK=~-dc8V&1i99Ao)gZz3AuLFRFZdNG>^)v&s zb0poUkDI!&X@WhuDy^5HkI{@bjxl8)B8zfsbf<*YCb4tt*?R6@m{uDqh-aT%#y=*$ ztCWrg?FT`1I0?F#vsO$6*SG26{@^gU*}U<2ausI)#D3PX=i2|M?R#WX?YB~j<{FU?Ud`6o2dMg4c+}eiLa%n literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000000.v05.l1.zst b/zstd/legacy_frames/z000000.v05.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..08c32d46e4c0d337720fb7d8f7b99561a2d9664d GIT binary patch literal 8138 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 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000000.v05.l19.zst b/zstd/legacy_frames/z000000.v05.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..57d2b97136a77a4557df73b4907f5ebfe2e95b4f GIT binary patch literal 6381 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 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000000.v06.l1.zst b/zstd/legacy_frames/z000000.v06.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..7b5c76c081c018b82164cb5aca1782b2985af071 GIT binary patch literal 8007 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 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000000.v07.l1.zst b/zstd/legacy_frames/z000000.v07.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..5132f9f18bc1a0bb8d2e00693627733ee07e4d16 GIT binary patch literal 8004 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 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000001.v07.l19.zst b/zstd/legacy_frames/z000001.v07.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..6bc9786f3de31ed74a8ba8620ae752034a71cdb7 GIT binary patch literal 36 ucmV+<0Nej3wJ-f77(f6RWGKp??;7_ny3F#k(d`o7!a{It6~#7i?S?Qh>Jgp* literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000013.v05.l1.zst b/zstd/legacy_frames/z000013.v05.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..85b5035cda4ecbe26c9a4132e3726b66801da28f GIT binary patch literal 12 TcmY%0s{fb4fq{|n00RR67*hiY literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000013.v05.l19.zst b/zstd/legacy_frames/z000013.v05.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..85b5035cda4ecbe26c9a4132e3726b66801da28f GIT binary patch literal 12 TcmY%0s{fb4fq{|n00RR67*hiY literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000013.v06.l1.zst b/zstd/legacy_frames/z000013.v06.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..ba916f90aa22a891ead9fd96a619500209b845ab GIT binary patch literal 13 UcmY$}s{hxC(Sd=H@c;t@035*sO#lD@ literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000013.v06.l19.zst b/zstd/legacy_frames/z000013.v06.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..ba916f90aa22a891ead9fd96a619500209b845ab GIT binary patch literal 13 UcmY$}s{hxC(Sd=H@c;t@035*sO#lD@ literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000013.v07.l1.zst b/zstd/legacy_frames/z000013.v07.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..ce9a95d450b5349c53a78db755b03e7f07228e56 GIT binary patch literal 13 UcmY%2s{dDo(Sd=H@o|a`039s@^8f$< literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000013.v07.l19.zst b/zstd/legacy_frames/z000013.v07.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..ce9a95d450b5349c53a78db755b03e7f07228e56 GIT binary patch literal 13 UcmY%2s{dDo(Sd=H@o|a`039s@^8f$< literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000021.v05.l1.zst b/zstd/legacy_frames/z000021.v05.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..9938d82a6798e240bf6a0adf515ef7eb4028f88a GIT binary patch literal 31740 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 z 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 lP5PC{{ThB%Wwby literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000021.v05.l19.zst b/zstd/legacy_frames/z000021.v05.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..7efffb88a7afdddc12c1fe08e8b31252d9c74dff GIT binary patch literal 26642 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 Nrroa&^Vb-_003iVoa_Jq literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000021.v06.l1.zst b/zstd/legacy_frames/z000021.v06.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..51674a9092d6e0a5646587ba1dbc0a54f09fa614 GIT binary patch literal 30798 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 MxB|CPU2(tw08H1^vH$=8 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000021.v06.l19.zst b/zstd/legacy_frames/z000021.v06.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..cf969885dac9a36999fcecfbfa5d1044a89887cb GIT binary patch literal 25332 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 literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000021.v07.l1.zst b/zstd/legacy_frames/z000021.v07.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..726dfcda707b872f999040e8b90259411fc1fe5b GIT binary patch literal 31693 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%0ADjF?pI*(I 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 WDv=(2j%ua#DIOZeE$MCn&O^;z6?l>W literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000046.v05.l1.zst b/zstd/legacy_frames/z000046.v05.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..8ab41480a032867deb8f057a1f1ec19425777de9 GIT binary patch literal 2803 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 F007{Xa)ST> literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000046.v05.l19.zst b/zstd/legacy_frames/z000046.v05.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..8ab41480a032867deb8f057a1f1ec19425777de9 GIT binary patch literal 2803 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 F007{Xa)ST> literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000046.v06.l1.zst b/zstd/legacy_frames/z000046.v06.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..5e62620c8ff07033699da8a429b2af68f3e9254a GIT binary patch literal 2805 zcmV2SoII5|#C zDBKMAK{9`v!v{~L5eK;#Ia@Bd7jikfJiLxrW3xm4He>zVWosi11;5wH?c`+DLSBDN zc$Ai_TLqF2u$oPd8b!)xfLSLzQayn@|1XA&X#`ZLd}&rht2G?CL~fr`8!qZ=R5>Aq>k_2E5w#f%+2XYh%8va z_3E`+MD{o77sM%H%-5 zc1fkCXpfW!mP^SLoEZAe9hr)XdJy)6klX2cNXQU>1zvX(5CoZhxn16|od!BQ01-D& zN~F+{k_JP$+BQ#j)r)FdXttw;o&px z57kIE>ggn$HHy7w4Ps~!?BB#o)sF^%y;s*2bqb3XSd=8dWA?*8R z72#O}vIEWW9~bI**OO60rI@RL9rud5!LskGSwB*TiAET4uNSqZ1x z_1X1V?Q*0P=A#p|ui9LkOz`lihH-}@WX$722mSX;$y{IxSfP@5(VvlU-H-xa04jHU zrmu?)DO}8oijla|^K(_T5YaiVXatl<)z|TUl`hHcY`Qek*Z#5jna*0K?N~%%d0Ey5 z=Na>=B@J6z#ze1D!cLEK=^jM!vMp^FIjHVIXg+j3Dpjn;79@E#<>*t^^~N=NfwxEE z+v@@`2>ggMM(BUvL9+jZDqbwDzBwKlt9=}kGyFZ0QH9}hwQ)^;$$#K5_0|>Vb5G?^ zJfbdgtob~~(nFC;sR~>W9RJGHU9Ivi%7b8l=mO+L>B7W+=X4XElyYf5`xGX@JT%T2 zNiQ(a13y=~{KCe#CTe?i?Ht5h;%`%h7m>M@KgDk5sz979kF_}G;{|hG)o&4%O8Stg z&u9RMgIkAUky=T1rJIX$iOrYMln z&VPXXC@{x?8hGWH$5#);3>-r+)bE$R&Y6RjDyD|w9G(SMOU_*TY%S9~FQ zPx3Hn_X~A5(|FMtazQ2WWt!vMGzZ<2o$7k+tr9$ci%xhg{BWiOg$IYzqjW2$wty`r z3^6G50kD@Qs;2zgnn_w~&MV+s^oNxsRx?4iJTk5ZmK-Uo6nk08k0qD@d-Ha_;Wsln zzyt3yjQE&gQ1)T^6$+W)`UfV&RF^@%2Omfk5`zvCXsgSO`*_*|yp>fZ#%WQwCCo-Ep5^J}%5fIsJ? z&lJn?Q%>UN|@i2L|aYPZx0){IeCfJp}*HLbn#$%J6wQ@7!By1+h) z*`*@*iIdm?`4vW+%DG_OB5zyHlikTMh`ZRho3Z*^aj-=ml)LFJp2qrn$&!G{mDDLW zZyogNVDe)ibs=T^12UInoZQ+(FNbXIDgn$jyw`qjn2UM`bSs4wg3IR|P&vU?9nz{~ zV}AoBF|2*wCM3JAui$*bWrV|8OlB}zv14t}Wm6*!6BRkC@7N?HAvinSyGCB{G_+kY zfvg7eU9E>W|=a5tLFH-FMd$AV6Fj>9OgNLWqg4}1&m$YfT zE4gf0<`VO^&>yi>fTw2@M_}K^+DszMUnr7R0(N(Q>TpD6fQ^TfE69x6tW^AtG=LrF zy~JK=gh2B&Qc__PPgd}UV3LEae;XDm>pkQM zR+s_tN>{K{JxnfxaSEkF5+50R^(7U%42{UPh8QO`Es5+MP1DebXrk1V;2IO!uWdY~ z2+PRNO#2Z1w|oszJp0b|Et5?AxI?~8)fv$c^~zOt_y;E399C*_rF{##Mk}9XDD+Gs zZU6KR4gm0oe0{uul)SB=~)C@wc^c? z5}_F;;8pWOcU8s8A@3F1r$7~N0O?5EGHM2col7UIl8bzv|tBRbQgT}`a(seQ8u>f$qn}*5ff)rA(`8x<1}2QdJ#5t)t`}md)v0DCb^rw z2P&6GPqgsT>N^CYWJV)+zZD+lw%JW^XAl4JiKbWqfL5%-; zxw+mlPMuJ~p2<1{=&qzv$>f0c*&w3Sp%P0_=xxNo;hS@%XPYCW zOGywnoASCrf5`;2Gr8RY60`YwG7J5jVBjNi${E0DOlZ(r z(<^Z!_BWM8-cADHD(bB0Zwj>W!Ddg;rI8E0MB*Qy7|Y7vOzv6TD literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000046.v06.l19.zst b/zstd/legacy_frames/z000046.v06.l19.zst new file mode 100644 index 0000000000000000000000000000000000000000..5e62620c8ff07033699da8a429b2af68f3e9254a GIT binary patch literal 2805 zcmV2SoII5|#C zDBKMAK{9`v!v{~L5eK;#Ia@Bd7jikfJiLxrW3xm4He>zVWosi11;5wH?c`+DLSBDN zc$Ai_TLqF2u$oPd8b!)xfLSLzQayn@|1XA&X#`ZLd}&rht2G?CL~fr`8!qZ=R5>Aq>k_2E5w#f%+2XYh%8va z_3E`+MD{o77sM%H%-5 zc1fkCXpfW!mP^SLoEZAe9hr)XdJy)6klX2cNXQU>1zvX(5CoZhxn16|od!BQ01-D& zN~F+{k_JP$+BQ#j)r)FdXttw;o&px z57kIE>ggn$HHy7w4Ps~!?BB#o)sF^%y;s*2bqb3XSd=8dWA?*8R z72#O}vIEWW9~bI**OO60rI@RL9rud5!LskGSwB*TiAET4uNSqZ1x z_1X1V?Q*0P=A#p|ui9LkOz`lihH-}@WX$722mSX;$y{IxSfP@5(VvlU-H-xa04jHU zrmu?)DO}8oijla|^K(_T5YaiVXatl<)z|TUl`hHcY`Qek*Z#5jna*0K?N~%%d0Ey5 z=Na>=B@J6z#ze1D!cLEK=^jM!vMp^FIjHVIXg+j3Dpjn;79@E#<>*t^^~N=NfwxEE z+v@@`2>ggMM(BUvL9+jZDqbwDzBwKlt9=}kGyFZ0QH9}hwQ)^;$$#K5_0|>Vb5G?^ zJfbdgtob~~(nFC;sR~>W9RJGHU9Ivi%7b8l=mO+L>B7W+=X4XElyYf5`xGX@JT%T2 zNiQ(a13y=~{KCe#CTe?i?Ht5h;%`%h7m>M@KgDk5sz979kF_}G;{|hG)o&4%O8Stg z&u9RMgIkAUky=T1rJIX$iOrYMln z&VPXXC@{x?8hGWH$5#);3>-r+)bE$R&Y6RjDyD|w9G(SMOU_*TY%S9~FQ zPx3Hn_X~A5(|FMtazQ2WWt!vMGzZ<2o$7k+tr9$ci%xhg{BWiOg$IYzqjW2$wty`r z3^6G50kD@Qs;2zgnn_w~&MV+s^oNxsRx?4iJTk5ZmK-Uo6nk08k0qD@d-Ha_;Wsln zzyt3yjQE&gQ1)T^6$+W)`UfV&RF^@%2Omfk5`zvCXsgSO`*_*|yp>fZ#%WQwCCo-Ep5^J}%5fIsJ? z&lJn?Q%>UN|@i2L|aYPZx0){IeCfJp}*HLbn#$%J6wQ@7!By1+h) z*`*@*iIdm?`4vW+%DG_OB5zyHlikTMh`ZRho3Z*^aj-=ml)LFJp2qrn$&!G{mDDLW zZyogNVDe)ibs=T^12UInoZQ+(FNbXIDgn$jyw`qjn2UM`bSs4wg3IR|P&vU?9nz{~ zV}AoBF|2*wCM3JAui$*bWrV|8OlB}zv14t}Wm6*!6BRkC@7N?HAvinSyGCB{G_+kY zfvg7eU9E>W|=a5tLFH-FMd$AV6Fj>9OgNLWqg4}1&m$YfT zE4gf0<`VO^&>yi>fTw2@M_}K^+DszMUnr7R0(N(Q>TpD6fQ^TfE69x6tW^AtG=LrF zy~JK=gh2B&Qc__PPgd}UV3LEae;XDm>pkQM zR+s_tN>{K{JxnfxaSEkF5+50R^(7U%42{UPh8QO`Es5+MP1DebXrk1V;2IO!uWdY~ z2+PRNO#2Z1w|oszJp0b|Et5?AxI?~8)fv$c^~zOt_y;E399C*_rF{##Mk}9XDD+Gs zZU6KR4gm0oe0{uul)SB=~)C@wc^c? z5}_F;;8pWOcU8s8A@3F1r$7~N0O?5EGHM2col7UIl8bzv|tBRbQgT}`a(seQ8u>f$qn}*5ff)rA(`8x<1}2QdJ#5t)t`}md)v0DCb^rw z2P&6GPqgsT>N^CYWJV)+zZD+lw%JW^XAl4JiKbWqfL5%-; zxw+mlPMuJ~p2<1{=&qzv$>f0c*&w3Sp%P0_=xxNo;hS@%XPYCW zOGywnoASCrf5`;2Gr8RY60`YwG7J5jVBjNi${E0DOlZ(r z(<^Z!_BWM8-cADHD(bB0Zwj>W!Ddg;rI8E0MB*Qy7|Y7vOzv6TD literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000046.v07.l1.zst b/zstd/legacy_frames/z000046.v07.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..dc2b65ca62cb431233e464afd39809ca13eadc47 GIT binary patch literal 2805 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 ErrorCode { + match self { + LegacyFrameError::UnsupportedParameter => ErrorCode::FrameParameterUnsupported, + LegacyFrameError::WindowTooLarge { .. } => ErrorCode::FrameParameterWindowTooLarge, + LegacyFrameError::OutputTooSmall => ErrorCode::DstSizeTooSmall, + LegacyFrameError::DictionaryCorrupted => ErrorCode::DictionaryCorrupted, + LegacyFrameError::DictionaryMismatch { .. } => ErrorCode::DictionaryWrong, + LegacyFrameError::ChecksumMismatch { .. } => ErrorCode::ChecksumWrong, + LegacyFrameError::Truncated | LegacyFrameError::Corrupted { .. } => { + ErrorCode::CorruptionDetected + } + } + } +} + +#[cfg(feature = "legacy")] +impl fmt::Display for LegacyFrameError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Truncated => write!(f, "the input ends inside a v0.5-v0.7 frame"), + Self::UnsupportedParameter => { + write!( + f, + "the v0.5-v0.7 frame header asks for an unsupported setting" + ) + } + Self::WindowTooLarge { requested, limit } => write!( + f, + "the frame's window of {requested} bytes is past the ceiling of {limit}" + ), + Self::Corrupted { reason } => write!(f, "corrupt v0.5-v0.7 frame: {reason}"), + Self::OutputTooSmall => write!(f, "the output buffer is too small for the frame"), + Self::DictionaryCorrupted => write!(f, "the dictionary could not be read"), + Self::DictionaryMismatch { expected, provided } => write!( + f, + "the frame needs dictionary 0x{expected:X}, the one supplied is 0x{provided:X}" + ), + Self::ChecksumMismatch { + expected, + calculated, + } => write!( + f, + "content checksum mismatch: frame stored 0x{expected:06X}, decoder calculated 0x{calculated:06X}" + ), + } + } +} + +#[cfg(all(feature = "legacy", feature = "std"))] +impl StdError for LegacyFrameError {} + #[derive(Debug)] #[non_exhaustive] pub enum FrameHeaderError { @@ -844,6 +941,17 @@ pub enum FrameDecoderError { /// to make it resumable. #[cfg(feature = "lsm")] ResumeUnidentifiedDictionary, + /// A frame of the v0.5, v0.6 or v0.7 format failed to decode. + #[cfg(feature = "legacy")] + #[cfg_attr(docsrs, doc(cfg(feature = "legacy")))] + LegacyFrame(LegacyFrameError), +} + +#[cfg(feature = "legacy")] +impl From for FrameDecoderError { + fn from(err: LegacyFrameError) -> Self { + FrameDecoderError::LegacyFrame(err) + } } impl FrameDecoderError { @@ -873,6 +981,8 @@ impl FrameDecoderError { FrameDecoderError::TargetTooSmall => ErrorCode::DstSizeTooSmall, FrameDecoderError::NotYetInitialized => ErrorCode::InitMissing, FrameDecoderError::ChecksumMismatch { .. } => ErrorCode::ChecksumWrong, + #[cfg(feature = "legacy")] + FrameDecoderError::LegacyFrame(err) => err.code(), _ => ErrorCode::CorruptionDetected, } } @@ -894,6 +1004,8 @@ impl StdError for FrameDecoderError { FrameDecoderError::FailedToReadChecksum(source) => Some(source), FrameDecoderError::FailedToInitialize(source) => Some(source), FrameDecoderError::FailedToDrainDecodebuffer(source) => Some(source), + #[cfg(feature = "legacy")] + FrameDecoderError::LegacyFrame(source) => Some(source), _ => None, } } @@ -1069,6 +1181,8 @@ impl core::fmt::Display for FrameDecoderError { "a dictionary with no ID cannot be told from another one, so it cannot carry a resume state across frames" ) } + #[cfg(feature = "legacy")] + FrameDecoderError::LegacyFrame(e) => write!(f, "{e}"), } } } diff --git a/zstd/src/decoding/legacy/entropy.rs b/zstd/src/decoding/legacy/entropy.rs new file mode 100644 index 00000000..7db889a2 --- /dev/null +++ b/zstd/src/decoding/legacy/entropy.rs @@ -0,0 +1,568 @@ +//! Entropy coding shared by the v0.5, v0.6 and v0.7 formats: the backward +//! bitstream, FSE tables and their compact description, and Huffman literals. +//! +//! The three formats agree on all of it (upstream `lib/legacy/zstd_v0{5,6,7}.c` +//! carry the same code), and it differs from RFC 8878 in details that matter +//! to a decoder: a Huffman description may be a run of weight 1, the largest +//! FSE table has 4096 states, and a table may be a fixed-width read. + +use super::corrupt; +use crate::bit_io::BitReaderReversed; +use crate::decoding::errors::LegacyFrameError as Error; +use alloc::vec; +use alloc::vec::Vec; + +/// A backward bitstream, read from its last byte toward its first. +pub(super) type Bits<'s> = BitReaderReversed<'s>; + +/// Largest FSE table the formats build: 4096 states (`FSEv07_MAX_TABLELOG`). +const FSE_MAX_TABLE_LOG: u32 = 12; +/// Smallest table log a description encodes (`FSEv07_MIN_TABLELOG`). +const FSE_MIN_TABLE_LOG: i32 = 5; +/// Largest table log a description may name (`FSEv07_TABLELOG_ABSOLUTE_MAX`). +const FSE_DESCRIPTION_MAX_LOG: i32 = 15; +/// Largest Huffman code length a table may use (`HUFv07_TABLELOG_MAX`). +const HUF_MAX_TABLE_LOG: u32 = 12; +/// Weights at or past this value are refused (`HUFv07_TABLELOG_ABSOLUTEMAX`). +const HUF_WEIGHT_LIMIT: u8 = 16; + +/// Open a backward bitstream over `src`, past the padding and the end mark +/// in its last byte. +pub(super) fn open_bits(src: &[u8]) -> Result, Error> { + let &last = src.last().ok_or(corrupt("empty bitstream"))?; + if last == 0 { + return Err(corrupt("bitstream without an end mark")); + } + let mut bits = Bits::new(src); + bits.get_bits(last.leading_zeros() as u8 + 1); + Ok(bits) +} + +/// Whether `bits` has been read past its first byte. +#[inline] +pub(super) fn overflowed(bits: &Bits<'_>) -> bool { + bits.bits_remaining() < 0 +} + +/// A normalised symbol distribution, as an FSE table description encodes it: +/// `-1` marks a symbol below one state. +pub(super) struct Distribution { + counts: [i16; 256], + /// Highest symbol the description lists. + max_symbol: usize, + table_log: u32, +} + +/// Read an FSE table description (`FSEv07_readNCount`), listing at most +/// `max_symbol + 1` symbols. Returns the distribution and the bytes read. +pub(super) fn read_distribution( + src: &[u8], + max_symbol: usize, +) -> Result<(Distribution, usize), Error> { + let end = src.len(); + if end < 4 { + return Err(Error::Truncated); + } + let read32 = |at: usize| u32::from_le_bytes([src[at], src[at + 1], src[at + 2], src[at + 3]]); + let mut counts = [0i16; 256]; + let mut ip = 0usize; + let mut stream = read32(0); + let mut nb_bits = (stream & 0xF) as i32 + FSE_MIN_TABLE_LOG; + if nb_bits > FSE_DESCRIPTION_MAX_LOG { + return Err(corrupt("FSE table log too large")); + } + let table_log = nb_bits as u32; + stream >>= 4; + let mut bit_count: i32 = 4; + let mut remaining: i32 = (1 << nb_bits) + 1; + let mut threshold: i32 = 1 << nb_bits; + nb_bits += 1; + let mut symbol = 0usize; + let mut previous_zero = false; + + while remaining > 1 && symbol <= max_symbol { + if previous_zero { + let mut run_end = symbol; + while stream & 0xFFFF == 0xFFFF { + run_end += 24; + if ip + 5 < end { + ip += 2; + stream = read32(ip).wrapping_shr(bit_count as u32); + } else { + stream >>= 16; + bit_count += 16; + } + } + while stream & 3 == 3 { + run_end += 3; + stream >>= 2; + bit_count += 2; + } + run_end += (stream & 3) as usize; + bit_count += 2; + if run_end > max_symbol { + return Err(corrupt("FSE description lists too many symbols")); + } + // `counts` was zeroed; the run only moves past its symbols. + symbol = run_end; + if ip + 7 <= end || ip + (bit_count >> 3) as usize + 4 <= end { + ip += (bit_count >> 3) as usize; + bit_count &= 7; + stream = read32(ip).wrapping_shr(bit_count as u32); + } else { + stream >>= 2; + } + } + let max = (2 * threshold - 1) - remaining; + let mut count; + if ((stream & (threshold as u32 - 1)) as i32) < max { + count = (stream & (threshold as u32 - 1)) as i32; + bit_count += nb_bits - 1; + } else { + count = (stream & (2 * threshold as u32 - 1)) as i32; + if count >= threshold { + count -= max; + } + bit_count += nb_bits; + } + count -= 1; + remaining -= count.abs(); + // `count` is at most `remaining` before the subtraction, so at least + // one state is always left. + debug_assert!(remaining >= 1); + counts[symbol] = count as i16; + symbol += 1; + previous_zero = count == 0; + while remaining < threshold { + nb_bits -= 1; + threshold >>= 1; + } + if ip + 7 <= end || ip + (bit_count >> 3) as usize + 4 <= end { + ip += (bit_count >> 3) as usize; + bit_count &= 7; + } else { + bit_count -= 8 * (end - 4 - ip) as i32; + ip = end - 4; + } + stream = read32(ip).wrapping_shr((bit_count & 31) as u32); + } + if remaining != 1 { + return Err(corrupt("FSE description does not fill its table")); + } + let consumed = ip + ((bit_count + 7) >> 3) as usize; + if consumed > end { + return Err(Error::Truncated); + } + Ok(( + Distribution { + counts, + max_symbol: symbol - 1, + table_log, + }, + consumed, + )) +} + +impl Distribution { + /// A predefined distribution, as the formats' default tables list them. + pub(super) fn predefined(counts: &[i16], table_log: u32) -> Distribution { + let mut all = [0i16; 256]; + all[..counts.len()].copy_from_slice(counts); + Distribution { + counts: all, + max_symbol: counts.len() - 1, + table_log, + } + } + + pub(super) fn table_log(&self) -> u32 { + self.table_log + } +} + +#[derive(Clone, Copy, Default)] +struct FseCell { + new_state: u16, + symbol: u8, + nb_bits: u8, +} + +/// An FSE decoding table. +#[derive(Clone)] +pub(super) struct FseTable { + log: u32, + cells: Vec, + /// Whether every state reads at least one bit: no symbol holds half the + /// table or more (`fastMode`). + fast: bool, +} + +/// How an FSE-coded byte string marks its end. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(super) enum StreamEnd { + /// The decoder reads until a read passes the stream's first bit, then + /// takes one more symbol from the other state (v0.6, v0.7). + PastStart, + /// The stream ends exactly at its first bit with both states at 0 + /// (v0.5, `FSEv05_decompress_usingDTable_generic`). + Exact, +} + +impl FseTable { + /// The table a distribution spreads (`FSEv07_buildDTable`). + pub(super) fn new(dist: &Distribution) -> Result { + let log = dist.table_log; + if log > FSE_MAX_TABLE_LOG { + return Err(corrupt("FSE table log too large")); + } + let size = 1usize << log; + let counts = &dist.counts[..=dist.max_symbol]; + let mut cells = vec![FseCell::default(); size]; + let mut next = [0u16; 256]; + + // Symbols below one state take the top cells, one each. + let mut high = size; + for (symbol, &count) in counts.iter().enumerate() { + if count == -1 { + high = high + .checked_sub(1) + .ok_or(corrupt("FSE distribution overfills its table"))?; + cells[high].symbol = symbol as u8; + next[symbol] = 1; + } else { + next[symbol] = count.max(0) as u16; + } + } + + let mask = size - 1; + let step = (size >> 1) + (size >> 3) + 3; + let mut position = 0usize; + for (symbol, &count) in counts.iter().enumerate() { + for _ in 0..count.max(0) { + if high == 0 { + return Err(corrupt("FSE distribution overfills its table")); + } + cells[position].symbol = symbol as u8; + position = (position + step) & mask; + while position >= high { + position = (position + step) & mask; + } + } + } + // A distribution that sums to the table size reaches every cell once. + if position != 0 { + return Err(corrupt("FSE distribution does not fill its table")); + } + + for cell in &mut cells { + let state = next[usize::from(cell.symbol)]; + next[usize::from(cell.symbol)] += 1; + let nb_bits = log - (31 - u32::from(state).leading_zeros()); + cell.nb_bits = nb_bits as u8; + cell.new_state = ((u32::from(state) << nb_bits) - size as u32) as u16; + } + let large = 1i16 << (log - 1); + let fast = counts.iter().all(|&count| count < large); + Ok(FseTable { log, cells, fast }) + } + + /// A table reading every symbol as `nb_bits` raw bits (`FSEv07_buildDTable_raw`). + pub(super) fn raw(nb_bits: u32) -> FseTable { + let cells = (0..1usize << nb_bits) + .map(|symbol| FseCell { + new_state: 0, + symbol: symbol as u8, + nb_bits: nb_bits as u8, + }) + .collect(); + FseTable { + log: nb_bits, + cells, + fast: true, + } + } + + /// A table producing `symbol` without reading (`FSEv07_buildDTable_rle`). + pub(super) fn rle(symbol: u8) -> FseTable { + FseTable { + log: 0, + cells: vec![FseCell { + new_state: 0, + symbol, + nb_bits: 0, + }], + fast: false, + } + } +} + +/// A position in an [`FseTable`]. +pub(super) struct FseState { + state: usize, +} + +impl FseState { + pub(super) fn new(table: &FseTable, bits: &mut Bits<'_>) -> FseState { + FseState { + state: bits.get_bits(table.log as u8) as usize, + } + } + + /// The symbol at the current state. + #[inline] + pub(super) fn symbol(&self, table: &FseTable) -> u8 { + table.cells[self.state].symbol + } + + /// Move to the next state, reading the bits the current one calls for. + #[inline] + pub(super) fn update(&mut self, table: &FseTable, bits: &mut Bits<'_>) { + let cell = table.cells[self.state]; + // A cell's `new_state` plus any value of its `nb_bits` stays inside + // the table by construction. + self.state = usize::from(cell.new_state) + bits.get_bits(cell.nb_bits) as usize; + } +} + +/// Decode an FSE-compressed byte string of at most `limit` symbols +/// (`FSEv07_decompress`): a description, then two interleaved states. +fn fse_decompress( + src: &[u8], + out: &mut [u8; 256], + limit: usize, + end: StreamEnd, +) -> Result { + if src.len() < 2 { + return Err(Error::Truncated); + } + let (dist, description_len) = read_distribution(src, 255)?; + if description_len >= src.len() { + return Err(Error::Truncated); + } + let table = FseTable::new(&dist)?; + let mut bits = open_bits(&src[description_len..])?; + let mut first = FseState::new(&table, &mut bits); + let mut second = FseState::new(&table, &mut bits); + if end == StreamEnd::Exact { + return decode_to_exact_end(&table, &mut bits, [first, second], out, limit); + } + let mut n = 0usize; + loop { + if n + 2 > limit { + return Err(corrupt("FSE stream decodes past its output")); + } + out[n] = first.symbol(&table); + n += 1; + first.update(&table, &mut bits); + if overflowed(&bits) { + out[n] = second.symbol(&table); + return Ok(n + 1); + } + if n + 2 > limit { + return Err(corrupt("FSE stream decodes past its output")); + } + out[n] = second.symbol(&table); + n += 1; + second.update(&table, &mut bits); + if overflowed(&bits) { + out[n] = first.symbol(&table); + return Ok(n + 1); + } + } +} + +/// The v0.5 ending: alternate the two states until the stream is read to its +/// first bit and the state about to decode is at 0 (in a table where every +/// state reads a bit, as soon as the stream is read); the string is whole +/// only if the stream ended exactly with both states at 0. +fn decode_to_exact_end( + table: &FseTable, + bits: &mut Bits<'_>, + mut states: [FseState; 2], + out: &mut [u8; 256], + limit: usize, +) -> Result { + let mut n = 0usize; + let mut turn = 0; + loop { + let state = &mut states[turn]; + let exhausted = bits.bits_remaining() == 0; + if overflowed(bits) || n == limit || (exhausted && (table.fast || state.state == 0)) { + break; + } + out[n] = state.symbol(table); + n += 1; + state.update(table, bits); + turn ^= 1; + } + if bits.bits_remaining() == 0 && states[0].state == 0 && states[1].state == 0 { + Ok(n) + } else { + Err(corrupt("FSE stream does not end with its symbols")) + } +} + +/// A Huffman decoding table: one entry per value of its `log` leading bits. +#[derive(Clone)] +pub(super) struct HufTable { + log: u32, + /// `(symbol, code length)` per entry. + cells: Vec<(u8, u8)>, +} + +/// Run lengths a description starting with byte `242 + i` stands for: that +/// many symbols, all of weight 1 (`HUFv07_readStats`). +const WEIGHT_RUNS: [usize; 14] = [1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128]; + +impl HufTable { + /// Read a Huffman table description (`HUFv07_readStats` and + /// `HUFv07_readDTableX2`), its weights FSE-coded with the ending `end`. + /// Returns the table and the bytes read. + pub(super) fn read(src: &[u8], end: StreamEnd) -> Result<(HufTable, usize), Error> { + let &header = src.first().ok_or(Error::Truncated)?; + let mut weights = [0u8; 256]; + let (explicit, consumed) = match header { + 242..=255 => { + weights.fill(1); + (WEIGHT_RUNS[usize::from(header - 242)], 1) + } + 128..=241 => { + let explicit = usize::from(header) - 127; + let packed = explicit.div_ceil(2); + let bytes = src.get(1..1 + packed).ok_or(Error::Truncated)?; + for (n, &byte) in bytes.iter().enumerate() { + weights[2 * n] = byte >> 4; + weights[2 * n + 1] = byte & 0xF; + } + (explicit, packed + 1) + } + _ => { + let len = usize::from(header); + let body = src.get(1..1 + len).ok_or(Error::Truncated)?; + (fse_decompress(body, &mut weights, 255, end)?, len + 1) + } + }; + + let mut ranks = [0u32; HUF_WEIGHT_LIMIT as usize + 1]; + let mut total = 0u32; + for &weight in &weights[..explicit] { + if weight >= HUF_WEIGHT_LIMIT { + return Err(corrupt("Huffman weight too large")); + } + ranks[usize::from(weight)] += 1; + total += (1 << weight) >> 1; + } + if total == 0 { + return Err(corrupt("Huffman description without symbols")); + } + // The last symbol's weight is implied: it brings the total to a + // power of two. + let log = 32 - total.leading_zeros(); + if log > u32::from(HUF_WEIGHT_LIMIT) { + return Err(corrupt("Huffman table too large")); + } + let rest = (1u32 << log) - total; + if !rest.is_power_of_two() { + return Err(corrupt("Huffman weights do not sum to a power of two")); + } + let last = rest.trailing_zeros() + 1; + weights[explicit] = last as u8; + ranks[last as usize] += 1; + // A prefix code has an even number, at least two, of longest codes. + if ranks[1] < 2 || ranks[1] % 2 != 0 { + return Err(corrupt("Huffman weights do not form a prefix code")); + } + if log > HUF_MAX_TABLE_LOG { + return Err(corrupt("Huffman table too large")); + } + + let mut start = [0u32; HUF_WEIGHT_LIMIT as usize + 1]; + let mut next = 0u32; + for weight in 1..=log as usize { + start[weight] = next; + next += ranks[weight] << (weight - 1); + } + let mut cells = vec![(0u8, 0u8); 1 << log]; + for (symbol, &weight) in weights[..=explicit].iter().enumerate() { + if weight == 0 { + continue; + } + let len = 1usize << (weight - 1); + let first = start[usize::from(weight)] as usize; + cells[first..first + len].fill((symbol as u8, (log + 1 - u32::from(weight)) as u8)); + start[usize::from(weight)] += len as u32; + } + Ok((HufTable { log, cells }, consumed)) + } + + #[inline] + fn decode(&self, bits: &mut Bits<'_>) -> u8 { + let log = self.log as u8; + bits.ensure_bits(log); + let (symbol, len) = self.cells[bits.peek_bits(log) as usize]; + bits.consume(len); + symbol + } + + /// Fill `out` from one stream, which must end exactly with it + /// (`HUFv07_decompress1X2_usingDTable`). + pub(super) fn decode_one_stream(&self, src: &[u8], out: &mut [u8]) -> Result<(), Error> { + let mut bits = open_bits(src)?; + for byte in out.iter_mut() { + *byte = self.decode(&mut bits); + } + if bits.bits_remaining() != 0 { + return Err(corrupt("Huffman stream does not end with its literals")); + } + Ok(()) + } + + /// Fill `out` from four streams behind a six-byte jump table, each + /// filling a quarter (`HUFv07_decompress4X2_usingDTable`). + pub(super) fn decode_four_streams(&self, src: &[u8], out: &mut [u8]) -> Result<(), Error> { + if src.len() < 10 { + return Err(corrupt("four-stream Huffman literals too short")); + } + let len1 = usize::from(u16::from_le_bytes([src[0], src[1]])); + let len2 = usize::from(u16::from_le_bytes([src[2], src[3]])); + let len3 = usize::from(u16::from_le_bytes([src[4], src[5]])); + let streams_end = 6 + len1 + len2 + len3; + if streams_end > src.len() { + return Err(corrupt("Huffman jump table points past its streams")); + } + let streams = [ + &src[6..6 + len1], + &src[6 + len1..6 + len1 + len2], + &src[6 + len1 + len2..streams_end], + &src[streams_end..], + ]; + let segment = out.len().div_ceil(4); + // Below six literals the first three quarters reach past the output; + // upstream decodes them into the literal buffer's slack and keeps what + // falls inside. A scratch of that size gives the same output. + let mut small = [0u8; 8]; + let work: &mut [u8] = if 3 * segment > out.len() { + &mut small[..3 * segment] + } else { + &mut *out + }; + let total = work.len(); + for (index, stream) in streams.iter().enumerate() { + let start = (index * segment).min(total); + let end = if index == 3 { + total + } else { + ((index + 1) * segment).min(total) + }; + self.decode_one_stream(stream, &mut work[start..end])?; + } + if 3 * segment > out.len() { + let len = out.len(); + out.copy_from_slice(&small[..len]); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests; diff --git a/zstd/src/decoding/legacy/entropy/tests.rs b/zstd/src/decoding/legacy/entropy/tests.rs new file mode 100644 index 00000000..e3f78146 --- /dev/null +++ b/zstd/src/decoding/legacy/entropy/tests.rs @@ -0,0 +1,149 @@ +use super::*; +use crate::decoding::legacy::v07::{LL_DEFAULT, LL_DEFAULT_LOG}; + +/// A bitstream opens past the padding up to its end mark; an empty one, or +/// one whose last byte has no mark, is refused. +#[test] +fn bitstreams_open_at_their_end_mark() { + assert!(open_bits(&[]).is_err()); + assert!(open_bits(&[0x5A, 0x00]).is_err()); + // The mark is the top set bit of the last byte: seven bits remain. + let bits = open_bits(&[0x01, 0x01]).unwrap(); + assert_eq!(bits.bits_remaining(), 8); + let mut bits = open_bits(&[0b1010_0101]).unwrap(); + assert_eq!(bits.bits_remaining(), 7); + assert_eq!(bits.get_bits(7), 0b010_0101); + assert!(!overflowed(&bits)); + bits.get_bits(1); + assert!(overflowed(&bits)); +} + +/// The predefined literal length distribution spreads into a full table in +/// which every symbol it lists appears. +#[test] +fn predefined_distributions_fill_their_table() { + let dist = Distribution::predefined(&LL_DEFAULT, LL_DEFAULT_LOG); + let table = FseTable::new(&dist).unwrap(); + assert_eq!(table.cells.len(), 1 << LL_DEFAULT_LOG); + for symbol in 0..LL_DEFAULT.len() as u8 { + assert!( + table.cells.iter().any(|cell| cell.symbol == symbol), + "{symbol}" + ); + } + // Every state's successor stays inside the table. + for cell in &table.cells { + assert!(usize::from(cell.new_state) + (1 << cell.nb_bits) <= table.cells.len()); + } +} + +/// A distribution that does not sum to its table is refused, as one with +/// more below-one symbols than cells. +#[test] +fn a_distribution_that_does_not_fill_its_table_is_refused() { + let mut short = LL_DEFAULT; + short[0] -= 1; + assert!(FseTable::new(&Distribution::predefined(&short, LL_DEFAULT_LOG)).is_err()); + let all_low = [-1i16; 40]; + assert!(FseTable::new(&Distribution::predefined(&all_low, 5)).is_err()); +} + +/// A description too short to hold its first word, or naming a table past +/// the largest log, is refused. +#[test] +fn bad_descriptions_are_refused() { + assert_eq!( + read_distribution(&[0x00, 0x00], 35).err(), + Some(Error::Truncated) + ); + // Log 5 + 15 = 20. + assert!(read_distribution(&[0x0F, 0, 0, 0], 35).is_err()); +} + +/// A raw table reads every symbol as its width, from the stream's end +/// backward; a one-symbol table reads nothing. +#[test] +fn raw_and_rle_tables_read_as_described() { + let raw = FseTable::raw(5); + // Under the end mark: 11010 then 11, padded with zeros past the start. + let mut bits = open_bits(&[0b1110_1011]).unwrap(); + let mut state = FseState::new(&raw, &mut bits); + assert_eq!(state.symbol(&raw), 0b11010); + state.update(&raw, &mut bits); + assert_eq!(state.symbol(&raw), 0b11000); + assert!(overflowed(&bits)); + + let rle = FseTable::rle(42); + let mut bits = open_bits(&[0x80]).unwrap(); + let mut state = FseState::new(&rle, &mut bits); + for _ in 0..3 { + assert_eq!(state.symbol(&rle), 42); + state.update(&rle, &mut bits); + } + assert_eq!(bits.bits_remaining(), 7); +} + +/// A description byte of 242 and up is a run of weight-1 symbols: 242 is +/// one, which with the implied last symbol makes a one-bit code. +#[test] +fn a_weight_run_makes_a_flat_code() { + let (table, consumed) = HufTable::read(&[242], StreamEnd::PastStart).unwrap(); + assert_eq!(consumed, 1); + assert_eq!(table.log, 1); + // One stream of bits 1, 0, 1 then the end mark: symbols 1, 0, 1. + let mut out = [0u8; 3]; + table.decode_one_stream(&[0b0000_1101], &mut out).unwrap(); + assert_eq!(out, [1, 0, 1]); + // A stream with a bit left over is refused. + let mut out = [0u8; 2]; + assert!(table.decode_one_stream(&[0b0000_1101], &mut out).is_err()); +} + +/// Weights stored as nibbles (a header of `127 + n` for `n` weights): symbols +/// 0 and 1 of weight 1 and the implied symbol 2 of weight 2 make codes of 2, +/// 2 and 1 bits. +#[test] +fn nibble_weights_build_their_code() { + let end = StreamEnd::PastStart; + let (table, consumed) = HufTable::read(&[127 + 2, 0x11], end).unwrap(); + assert_eq!(consumed, 2); + assert_eq!(table.log, 2); + assert_eq!(table.cells, [(0, 2), (1, 2), (2, 1), (2, 1)]); + // A nibble section cut short is truncated. + assert_eq!( + HufTable::read(&[127 + 4, 0x11], end).err(), + Some(Error::Truncated) + ); + // Weights 3 and 1 sum to 5, which no last weight brings to a power of + // two. + assert!(HufTable::read(&[127 + 2, 0x31], end).is_err()); + // A weight of 16 is refused. + assert!(HufTable::read(&[127 + 2, 0xF1], end).is_err()); +} + +/// Four streams fill the quarters of the output in order, and below six +/// literals the quarters past the output decode into nothing kept. +#[test] +fn four_streams_fill_their_quarters() { + let (table, _) = HufTable::read(&[242], StreamEnd::PastStart).unwrap(); + // Each stream: one byte, symbols 0 and 1 under the end mark. + let stream = 0b0000_0101u8; + let mut src = vec![1, 0, 1, 0, 1, 0]; + src.extend_from_slice(&[stream, stream, stream, stream]); + let mut out = [9u8; 8]; + table.decode_four_streams(&src, &mut out).unwrap(); + assert_eq!(out, [0, 1, 0, 1, 0, 1, 0, 1]); + + // Five literals: quarters of two, the last quarter empty. Its stream + // must still end exactly, so it holds only the end mark. + let mut src = vec![1, 0, 1, 0, 1, 0]; + src.extend_from_slice(&[stream, stream, stream, 0x01]); + let mut out = [9u8; 5]; + table.decode_four_streams(&src, &mut out).unwrap(); + assert_eq!(out, [0, 1, 0, 1, 0]); + + // A jump table pointing past the section is refused. + let mut src = vec![200, 0, 1, 0, 1, 0]; + src.extend_from_slice(&[stream, stream, stream, stream]); + assert!(table.decode_four_streams(&src, &mut out).is_err()); +} diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs new file mode 100644 index 00000000..aed5be0c --- /dev/null +++ b/zstd/src/decoding/legacy/mod.rs @@ -0,0 +1,693 @@ +//! Decoders for the frame formats zstd wrote before 1.0: v0.5, v0.6 and v0.7. +//! +//! libzstd 1.5.7 reads these by default (upstream's legacy level 5 in every +//! build system), so a consumer that holds an archive from 2016 gets its +//! content back from `ZSTD_decompress`; this module gives the same answer. +//! The formats differ from RFC 8878 and from each other in their headers, +//! literal and sequence coding, and dictionaries: each has its own module, +//! and what they share lives here and in [`entropy`]. +//! +//! Upstream's decoders are `lib/legacy/zstd_v0{5,6,7}.c`. + +mod entropy; +mod v05; +mod v06; +mod v07; + +use crate::decoding::errors::LegacyFrameError as Error; +use alloc::vec::Vec; + +/// One of the pre-1.0 frame formats this crate decodes. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum LegacyVersion { + /// The v0.5 format, magic number `0xFD2FB525`. + V05, + /// The v0.6 format, magic number `0xFD2FB526`. + V06, + /// The v0.7 format, magic number `0xFD2FB527`. + V07, +} + +impl LegacyVersion { + /// The format whose magic number starts `src`, if it is one of these. + pub fn of(src: &[u8]) -> Option { + let magic = u32::from_le_bytes(src.get(..4)?.try_into().ok()?); + match magic { + 0xFD2F_B525 => Some(LegacyVersion::V05), + 0xFD2F_B526 => Some(LegacyVersion::V06), + 0xFD2F_B527 => Some(LegacyVersion::V07), + _ => None, + } + } + + /// The minor version number: 5, 6 or 7. + pub fn minor(self) -> u8 { + match self { + LegacyVersion::V05 => 5, + LegacyVersion::V06 => 6, + LegacyVersion::V07 => 7, + } + } +} + +/// Most bytes one block decodes to, the formats' `BLOCKSIZE`. +const BLOCK_SIZE_MAX: usize = 128 * 1024; + +fn corrupt(reason: &'static str) -> Error { + Error::Corrupted { reason } +} + +/// A dictionary as the legacy decoders take it. +#[derive(Clone, Copy)] +pub(crate) enum LegacyDict<'a> { + None, + /// The bytes as the caller supplied them; each format reads its own + /// layout from them. + Raw(&'a [u8]), + /// A dictionary already split by the RFC 8878 parser, whose layout is the + /// v0.7 one: its ID, the bytes from its entropy tables through its repeat + /// offsets, and its content. + Split { + id: u32, + entropy: &'a [u8], + content: &'a [u8], + }, +} + +impl<'a> LegacyDict<'a> { + /// The dictionary's bytes as one contiguous run, for a format that reads + /// a split dictionary as plain content. Only a split dictionary needs the + /// copy; it is rare enough (an RFC 8878 dictionary with a v0.5 or v0.6 + /// frame) to pay for one. + fn whole(&self) -> alloc::borrow::Cow<'a, [u8]> { + use alloc::borrow::Cow; + match *self { + LegacyDict::None => Cow::Borrowed(&[]), + LegacyDict::Raw(bytes) => Cow::Borrowed(bytes), + LegacyDict::Split { + id, + entropy, + content, + } => { + let mut whole = Vec::with_capacity(8 + entropy.len() + content.len()); + whole.extend_from_slice(&v07::DICT_MAGIC.to_le_bytes()); + whole.extend_from_slice(&id.to_le_bytes()); + whole.extend_from_slice(entropy); + whole.extend_from_slice(content); + Cow::Owned(whole) + } + } + } +} + +/// The frame header fields the decoders and the size queries need. +#[derive(Debug, Clone, Copy)] +pub(crate) struct FrameHeader { + pub(crate) version: LegacyVersion, + /// Header length including the magic number. + pub(crate) size: usize, + /// The window the frame needs, in bytes. + pub(crate) window: u64, + /// The declared content size; the formats write 0 for unknown. + pub(crate) content_size: Option, + /// The dictionary the frame names, 0 for none. + pub(crate) dict_id: u32, + /// Whether a v0.7 frame carries a content checksum. + pub(crate) checksum: bool, +} + +/// Read the frame header at the start of `src`. +pub(crate) fn read_header(src: &[u8]) -> Result { + match LegacyVersion::of(src).ok_or(corrupt("not a v0.5-v0.7 frame"))? { + LegacyVersion::V05 => v05::read_header(src), + LegacyVersion::V06 => v06::read_header(src), + LegacyVersion::V07 => v07::read_header(src), + } +} + +/// The content size the frame at the start of `src` declares, `None` when it +/// declares none or its header does not read: libzstd reports both as an +/// unknown size (`ZSTD_getDecompressedSize_legacy`). +pub(crate) fn declared_content_size(src: &[u8]) -> Option { + read_header(src).ok()?.content_size +} + +/// A block as its three-byte header describes it. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Block { + Compressed(usize), + Raw(usize), + /// A byte repeated this many times. + Rle(usize), + End, +} + +impl Block { + fn read(src: &[u8]) -> Result { + let header = src.get(..3).ok_or(Error::Truncated)?; + let size = + usize::from(header[2]) | usize::from(header[1]) << 8 | usize::from(header[0] & 7) << 16; + Ok(match header[0] >> 6 { + 0 => Block::Compressed(size), + 1 => Block::Raw(size), + 2 => Block::Rle(size), + _ => Block::End, + }) + } + + /// Bytes the block's body takes after its header. + fn body_len(self) -> usize { + match self { + Block::Compressed(size) | Block::Raw(size) => size, + Block::Rle(_) => 1, + Block::End => 0, + } + } +} + +/// Whether `block` ends the frame. v0.5 and v0.6 end it at any block whose +/// body is empty, as their decoders and size walks do +/// (`if (cBlockSize == 0) break;`); v0.7 only at the end block. +fn ends_frame(version: LegacyVersion, block: Block) -> bool { + match version { + LegacyVersion::V07 => block == Block::End, + LegacyVersion::V05 | LegacyVersion::V06 => block.body_len() == 0, + } +} + +/// Where the frame starting `src` ends, and the most it can decode to: one +/// block's maximum per block (`ZSTD_findFrameSizeInfoLegacy`). +pub(crate) fn frame_size(src: &[u8]) -> Result<(usize, u64), Error> { + let header = read_header(src)?; + let mut at = header.size; + let mut blocks = 0u64; + loop { + let block = Block::read(&src[at.min(src.len())..])?; + at += 3; + let body = block.body_len(); + if body > src.len() - at.min(src.len()) { + return Err(Error::Truncated); + } + if ends_frame(header.version, block) { + break; + } + at += body; + blocks += 1; + } + Ok((at, blocks * BLOCK_SIZE_MAX as u64)) +} + +/// Where decoded bytes go: an output that knows how much of the frame it has +/// produced and how much of that it still holds for matches to reach. +pub(crate) trait Output { + /// Bytes of the current frame produced so far. + fn produced(&self) -> usize; + /// Bytes of the current frame still held, the most a match can reach + /// back within the frame. + fn held(&self) -> usize; + /// Refuse `len` more bytes the output cannot take. + fn reserve(&mut self, len: usize) -> Result<(), Error>; + fn extend(&mut self, bytes: &[u8]); + fn repeat(&mut self, byte: u8, len: usize); + /// Append `len` bytes copied from `offset` back, `1 <= offset <= held`; + /// an overlapping copy repeats the bytes it has just written. + fn copy_back(&mut self, offset: usize, len: usize); + /// The bytes produced since `from` (a value of `produced()`), for the + /// content checksum. + fn since(&self, from: usize) -> &[u8]; +} + +/// Output into a growing vector, the current frame starting at `start`. +pub(crate) struct VecOutput<'a> { + pub(crate) buf: &'a mut Vec, + pub(crate) start: usize, + /// Bytes of the frame already handed on and dropped from `buf`. + pub(crate) dropped: usize, + /// Most bytes the frame may produce. + pub(crate) limit: usize, +} + +impl Output for VecOutput<'_> { + fn produced(&self) -> usize { + self.dropped + self.held() + } + + fn held(&self) -> usize { + self.buf.len() - self.start + } + + fn reserve(&mut self, len: usize) -> Result<(), Error> { + if len > self.limit - self.produced() { + return Err(Error::OutputTooSmall); + } + self.buf.reserve(len); + Ok(()) + } + + fn extend(&mut self, bytes: &[u8]) { + self.buf.extend_from_slice(bytes); + } + + fn repeat(&mut self, byte: u8, len: usize) { + self.buf.resize(self.buf.len() + len, byte); + } + + fn copy_back(&mut self, offset: usize, len: usize) { + let from = self.buf.len() - offset; + if offset >= len { + self.buf.extend_from_within(from..from + len); + } else { + for i in 0..len { + let byte = self.buf[from + i]; + self.buf.push(byte); + } + } + } + + fn since(&self, from: usize) -> &[u8] { + &self.buf[self.start + (from - self.dropped)..] + } +} + +/// Output into a caller's slice, the current frame starting at its start. +pub(crate) struct SliceOutput<'a> { + pub(crate) buf: &'a mut [u8], + pub(crate) pos: usize, +} + +impl Output for SliceOutput<'_> { + fn produced(&self) -> usize { + self.pos + } + + fn held(&self) -> usize { + self.pos + } + + fn reserve(&mut self, len: usize) -> Result<(), Error> { + if len > self.buf.len() - self.pos { + return Err(Error::OutputTooSmall); + } + Ok(()) + } + + fn extend(&mut self, bytes: &[u8]) { + self.buf[self.pos..self.pos + bytes.len()].copy_from_slice(bytes); + self.pos += bytes.len(); + } + + fn repeat(&mut self, byte: u8, len: usize) { + self.buf[self.pos..self.pos + len].fill(byte); + self.pos += len; + } + + fn copy_back(&mut self, offset: usize, len: usize) { + let from = self.pos - offset; + if offset >= len { + self.buf.copy_within(from..from + len, self.pos); + } else { + for i in 0..len { + self.buf[self.pos + i] = self.buf[from + i]; + } + } + self.pos += len; + } + + fn since(&self, from: usize) -> &[u8] { + &self.buf[from..self.pos] + } +} + +/// Copy a match of `len` bytes from `offset` back: within the frame, from +/// the dictionary's content in front of it, or across both +/// (`ZSTDv07_execSequence`). The output has room for `len` more bytes. +fn copy_match(out: &mut O, offset: usize, len: usize, dict: &[u8]) -> Result<(), Error> { + if offset == 0 { + return Err(corrupt("match with offset 0")); + } + let held = out.held(); + if offset <= held { + out.copy_back(offset, len); + return Ok(()); + } + let produced = out.produced(); + if offset <= produced { + return Err(corrupt("match reaches before the window")); + } + let back = offset - produced; + if back > dict.len() { + return Err(corrupt("match reaches before the dictionary")); + } + let from = dict.len() - back; + let first = len.min(back); + out.extend(&dict[from..from + first]); + let rest = len - first; + if rest > 0 { + // The match runs on from the frame's first byte. + if produced != held { + return Err(corrupt("match reaches before the window")); + } + let distance = out.held(); + out.copy_back(distance, rest); + } + Ok(()) +} + +/// Append one sequence: `lit_len` literals from `literals[*lit_pos..]`, then a +/// match of `match_len` bytes from `offset` back. +fn execute( + out: &mut O, + literals: &[u8], + lit_pos: &mut usize, + lit_len: usize, + match_len: usize, + offset: usize, + dict: &[u8], +) -> Result<(), Error> { + out.reserve( + lit_len + .checked_add(match_len) + .ok_or(Error::OutputTooSmall)?, + )?; + let lits = literals + .get(*lit_pos..) + .and_then(|rest| rest.get(..lit_len)) + .ok_or(corrupt("sequence takes more literals than the block has"))?; + out.extend(lits); + *lit_pos += lit_len; + copy_match(out, offset, match_len, dict) +} + +/// The literal section's header, common to the three formats. +enum Literals { + /// Huffman-coded: `size` literals from `compressed` bytes after a + /// `header`-byte header, in one stream or four. + Huffman { + header: usize, + size: usize, + compressed: usize, + single_stream: bool, + }, + /// Huffman-coded with the previous table: one stream. + Repeat { + size: usize, + compressed: usize, + }, + Raw { + header: usize, + size: usize, + }, + Rle { + header: usize, + size: usize, + }, +} + +impl Literals { + /// Read the literal header at the start of a compressed block + /// (`ZSTDv07_decodeLiteralsBlock`). + fn read(src: &[u8]) -> Result { + // A compressed block holds at least a literal header byte, one + // literal or run byte, and the sequence count. + if src.len() < 3 { + return Err(corrupt("compressed block too short")); + } + let b0 = usize::from(src[0]); + let b1 = usize::from(src[1]); + let b2 = usize::from(src[2]); + let size_format = (b0 >> 4) & 3; + Ok(match b0 >> 6 { + 0 => { + if src.len() < 5 { + return Err(corrupt("compressed block too short")); + } + let b3 = usize::from(src[3]); + let b4 = usize::from(src[4]); + let (header, size, compressed, single_stream) = match size_format { + 0 | 1 => ( + 3, + ((b0 & 15) << 6) + (b1 >> 2), + ((b1 & 3) << 8) + b2, + b0 & 16 != 0, + ), + 2 => ( + 4, + ((b0 & 15) << 10) + (b1 << 2) + (b2 >> 6), + ((b2 & 63) << 8) + b3, + false, + ), + _ => ( + 5, + ((b0 & 15) << 14) + (b1 << 6) + (b2 >> 2), + ((b2 & 3) << 16) + (b3 << 8) + b4, + false, + ), + }; + if size > BLOCK_SIZE_MAX { + return Err(corrupt("literal section too large")); + } + if compressed + header > src.len() { + return Err(corrupt("literals run past the block")); + } + Literals::Huffman { + header, + size, + compressed, + single_stream, + } + } + 1 => { + // Only the small single-stream form exists. + if size_format != 1 { + return Err(corrupt("repeated-table literals in an unknown form")); + } + let size = ((b0 & 15) << 6) + (b1 >> 2); + let compressed = ((b1 & 3) << 8) + b2; + if compressed + 3 > src.len() { + return Err(corrupt("literals run past the block")); + } + Literals::Repeat { size, compressed } + } + kind => { + let (header, size) = match size_format { + 0 | 1 => (1, b0 & 31), + 2 => (2, ((b0 & 15) << 8) + b1), + _ => (3, ((b0 & 15) << 16) + (b1 << 8) + b2), + }; + if kind == 2 { + if size + header > src.len() { + return Err(corrupt("literals run past the block")); + } + Literals::Raw { header, size } + } else { + if header == 3 && src.len() < 4 { + return Err(corrupt("compressed block too short")); + } + if size > BLOCK_SIZE_MAX { + return Err(corrupt("literal section too large")); + } + Literals::Rle { header, size } + } + } + }) + } +} + +/// Where a block's literals are once decoded. +enum LiteralSource { + /// In the block itself, at this range. + Block(core::ops::Range), + /// In the decoder's literal buffer. + Buffer, +} + +/// The per-format state of a frame being decoded. +enum VersionDecoder { + V05(v05::Decoder), + V06(v06::Decoder), + V07(v07::Decoder), +} + +/// An output that refuses to grow past `end` produced bytes: a block decodes +/// to at most [`BLOCK_SIZE_MAX`], however many sequences it holds. +struct Budget<'o, O: Output> { + out: &'o mut O, + end: usize, +} + +impl Output for Budget<'_, O> { + fn produced(&self) -> usize { + self.out.produced() + } + fn held(&self) -> usize { + self.out.held() + } + fn reserve(&mut self, len: usize) -> Result<(), Error> { + if len > self.end - self.out.produced() { + return Err(corrupt("block decodes past the largest block size")); + } + self.out.reserve(len) + } + fn extend(&mut self, bytes: &[u8]) { + self.out.extend(bytes); + } + fn repeat(&mut self, byte: u8, len: usize) { + self.out.repeat(byte, len); + } + fn copy_back(&mut self, offset: usize, len: usize) { + self.out.copy_back(offset, len); + } + fn since(&self, from: usize) -> &[u8] { + self.out.since(from) + } +} + +/// A frame being decoded block by block: the whole-buffer and the streaming +/// decoders both drive one. +pub(crate) struct LegacyFrame<'d> { + header: FrameHeader, + decoder: VersionDecoder, + /// The bytes holding the dictionary's content, at `content`. + dict: alloc::borrow::Cow<'d, [u8]>, + content: core::ops::Range, + #[cfg(feature = "hash")] + hash: Option, +} + +impl<'d> LegacyFrame<'d> { + /// Start decoding the frame `header` describes. + pub(crate) fn new(header: FrameHeader, dict: LegacyDict<'d>) -> Result, Error> { + use alloc::borrow::Cow; + let (decoder, bytes, content) = match header.version { + LegacyVersion::V07 => { + let (decoder, content) = v07::Decoder::new(dict)?; + let bytes: &'d [u8] = match dict { + LegacyDict::None => &[], + LegacyDict::Raw(raw) => raw, + LegacyDict::Split { content, .. } => content, + }; + if header.dict_id != 0 && header.dict_id != decoder.dict_id { + return Err(Error::DictionaryMismatch { + expected: header.dict_id, + provided: decoder.dict_id, + }); + } + (VersionDecoder::V07(decoder), Cow::Borrowed(bytes), content) + } + LegacyVersion::V06 => { + let whole = dict.whole(); + let (decoder, content) = v06::Decoder::new(&whole)?; + (VersionDecoder::V06(decoder), whole, content) + } + LegacyVersion::V05 => { + let whole = dict.whole(); + let (decoder, content) = v05::Decoder::new(&whole)?; + (VersionDecoder::V05(decoder), whole, content) + } + }; + Ok(LegacyFrame { + header, + decoder, + dict: bytes, + content, + #[cfg(feature = "hash")] + hash: header.checksum.then(|| twox_hash::XxHash64::with_seed(0)), + }) + } + + pub(crate) fn header(&self) -> &FrameHeader { + &self.header + } + + /// Decode one block, `body` being the bytes after its header. + fn block(&mut self, block: Block, body: &[u8], out: &mut O) -> Result<(), Error> { + let before = out.produced(); + let mut out = Budget { + end: before + BLOCK_SIZE_MAX, + out, + }; + let dict = &self.dict[self.content.clone()]; + match block { + Block::Compressed(_) => match &mut self.decoder { + VersionDecoder::V05(decoder) => decoder.block(body, &mut out, dict)?, + VersionDecoder::V06(decoder) => decoder.block(body, &mut out, dict)?, + VersionDecoder::V07(decoder) => decoder.block(body, &mut out, dict)?, + }, + Block::Raw(len) => { + out.reserve(len)?; + out.extend(body); + } + Block::Rle(len) => { + out.reserve(len)?; + out.repeat(body[0], len); + } + Block::End => {} + } + #[cfg(feature = "hash")] + if let Some(hash) = &mut self.hash { + use core::hash::Hasher; + hash.write(out.since(before)); + } + Ok(()) + } + + /// Close the frame at its end block, whose header is `end`: the content + /// checksum a v0.7 frame keeps there. + /// + /// Upstream checks it only while streaming; a frame failing it is + /// corrupt whichever way it is read, so it is checked here for both. + /// + /// The declared content size is not checked against the output. It is + /// no integrity field in these formats: the v0.6.2 command line writes + /// the size of the first chunk it read, so a v0.6 archive past 128 KiB + /// declares too little. Upstream's streaming decoder (`zstd -d`) reads + /// such an archive whole; its one-shot `ZSTD_decompress` calls it + /// corrupt, which would refuse intact data. + fn finish(&mut self, end: [u8; 3]) -> Result<(), Error> { + #[cfg(feature = "hash")] + if let Some(hash) = &self.hash { + use core::hash::Hasher; + let expected = + u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16; + let calculated = ((hash.finish() >> 11) & 0x3F_FFFF) as u32; + if expected != calculated { + return Err(Error::ChecksumMismatch { + expected, + calculated, + }); + } + } + #[cfg(not(feature = "hash"))] + let _ = end; + Ok(()) + } +} + +/// Decode the frame starting `src` into `out`. Returns the bytes the frame +/// took. +pub(crate) fn decode_frame( + src: &[u8], + dict: LegacyDict<'_>, + out: &mut O, +) -> Result { + let header = read_header(src)?; + let mut frame = LegacyFrame::new(header, dict)?; + let mut at = header.size; + loop { + let block_header = src.get(at..at + 3).ok_or(Error::Truncated)?; + let block = Block::read(block_header)?; + let end = [block_header[0], block_header[1], block_header[2]]; + at += 3; + let body = src.get(at..at + block.body_len()).ok_or(Error::Truncated)?; + if ends_frame(header.version, block) { + frame.finish(end)?; + return Ok(at); + } + frame.block(block, body, out)?; + at += body.len(); + } +} + +#[cfg(test)] +mod tests; diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs new file mode 100644 index 00000000..8e73d242 --- /dev/null +++ b/zstd/src/decoding/legacy/tests.rs @@ -0,0 +1,422 @@ +//! Frames written by the v0.5, v0.6 and v0.7 release command lines +//! (`scripts/legacy-fixtures.sh`) decode to the files they were made from. + +use super::*; +use alloc::vec; +use alloc::vec::Vec; + +#[cfg(feature = "std")] +mod fixtures { + use super::*; + use alloc::format; + use alloc::string::{String, ToString}; + use std::path::{Path, PathBuf}; + + pub(super) struct Fixture { + pub(super) name: String, + pub(super) frame: Vec, + pub(super) content: Vec, + pub(super) dict: Option>, + pub(super) version: LegacyVersion, + } + + fn root() -> &'static Path { + Path::new(env!("CARGO_MANIFEST_DIR")) + } + + /// Every fixture, with the file it decodes to and the dictionary it needs. + pub(super) fn all() -> Vec { + let dir = root().join("legacy_frames"); + let mut names: Vec = std::fs::read_dir(&dir) + .expect("legacy_frames directory") + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .filter(|name| name.ends_with(".zst")) + .collect(); + names.sort(); + assert!(names.len() >= 50, "fixtures missing from {}", dir.display()); + names + .into_iter() + .map(|name| { + let (stem, rest) = ["v05", "v06", "v07"] + .iter() + .find_map(|tag| { + name.split_once(&format!(".{tag}.")) + .map(|split| (split, *tag)) + }) + .map(|((stem, _), tag)| (stem.to_string(), tag)) + .expect("fixture name carries its version"); + let version = match rest { + "v05" => LegacyVersion::V05, + "v06" => LegacyVersion::V06, + _ => LegacyVersion::V07, + }; + let is_dict = name.contains(".dict"); + let source: PathBuf = if is_dict { + root().join("dict_tests/files").join(&stem) + } else { + root().join("decodecorpus_files").join(&stem) + }; + Fixture { + frame: std::fs::read(dir.join(&name)).unwrap(), + content: std::fs::read(&source).unwrap(), + dict: is_dict + .then(|| std::fs::read(dir.join(format!("dictionary.{rest}"))).unwrap()), + version, + name, + } + }) + .collect() + } +} + +#[cfg(feature = "std")] +use fixtures::all as fixtures; + +fn decode_to_vec(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec, usize), Error> { + let mut buf = Vec::new(); + let mut out = VecOutput { + buf: &mut buf, + start: 0, + dropped: 0, + limit: usize::MAX, + }; + let consumed = decode_frame(frame, dict, &mut out)?; + Ok((buf, consumed)) +} + +/// Each fixture names its own format and decodes byte-exact, taking exactly +/// its own bytes. +#[cfg(feature = "std")] +#[test] +fn fixtures_decode_to_their_content() { + for fixture in fixtures() { + assert_eq!( + LegacyVersion::of(&fixture.frame), + Some(fixture.version), + "{}", + fixture.name + ); + let dict = fixture + .dict + .as_deref() + .map_or(LegacyDict::None, LegacyDict::Raw); + let (decoded, consumed) = decode_to_vec(&fixture.frame, dict) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + assert_eq!(consumed, fixture.frame.len(), "{}", fixture.name); + assert!( + decoded == fixture.content, + "{} decodes to other bytes", + fixture.name + ); + } +} + +/// Decoding into a caller's slice gives the same bytes, and a slice one byte +/// short is refused as too small rather than written past. +#[cfg(feature = "std")] +#[test] +fn fixtures_decode_into_a_slice() { + for fixture in fixtures() { + let dict = fixture + .dict + .as_deref() + .map_or(LegacyDict::None, LegacyDict::Raw); + let mut buf = vec![0u8; fixture.content.len()]; + let mut out = SliceOutput { + buf: &mut buf, + pos: 0, + }; + decode_frame(&fixture.frame, dict, &mut out) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + assert_eq!(out.pos, fixture.content.len(), "{}", fixture.name); + assert!(buf == fixture.content, "{}", fixture.name); + + if let Some(short) = fixture.content.len().checked_sub(1) { + let mut buf = vec![0u8; short]; + let mut out = SliceOutput { + buf: &mut buf, + pos: 0, + }; + assert_eq!( + decode_frame(&fixture.frame, dict, &mut out), + Err(Error::OutputTooSmall), + "{}", + fixture.name + ); + } + } +} + +/// The size walk ends where the decoder does, and bounds the content by +/// whole blocks. +#[cfg(feature = "std")] +#[test] +fn frame_size_matches_the_decoded_frame() { + for fixture in fixtures() { + let (size, bound) = frame_size(&fixture.frame).unwrap(); + assert_eq!(size, fixture.frame.len(), "{}", fixture.name); + assert!(bound >= fixture.content.len() as u64, "{}", fixture.name); + assert_eq!(bound % BLOCK_SIZE_MAX as u64, 0, "{}", fixture.name); + } +} + +/// A v0.7 frame naming a dictionary by ID is refused without it, and with a +/// dictionary of another ID, saying which it needs. (v0.5 and v0.6 frames +/// carry no ID; one whose blocks never reach into the dictionary decodes +/// without it.) +#[cfg(feature = "std")] +#[test] +fn a_v07_frame_naming_a_dictionary_needs_it() { + for fixture in fixtures() + .into_iter() + .filter(|f| f.name.ends_with(".v07.dict.zst")) + { + assert_eq!( + decode_to_vec(&fixture.frame, LegacyDict::None).unwrap_err(), + Error::DictionaryMismatch { + expected: 7, + provided: 0 + }, + "{}", + fixture.name + ); + let mut other = fixture.dict.clone().unwrap(); + other[4] ^= 1; + assert_eq!( + decode_to_vec(&fixture.frame, LegacyDict::Raw(&other)).unwrap_err(), + Error::DictionaryMismatch { + expected: 7, + provided: 6 + }, + "{}", + fixture.name + ); + } +} + +/// A v0.7 dictionary split by the RFC 8878 parser primes the decoder as its +/// raw bytes do. +#[cfg(feature = "std")] +#[test] +fn a_split_v07_dictionary_decodes_like_its_bytes() { + for fixture in fixtures() + .into_iter() + .filter(|f| f.version == LegacyVersion::V07 && f.dict.is_some()) + { + let raw = fixture.dict.as_deref().unwrap(); + let parsed = crate::decoding::Dictionary::decode_dict(raw).unwrap(); + let content_start = raw.len() - parsed.dict_content.len(); + let split = LegacyDict::Split { + id: parsed.id, + entropy: &raw[8..content_start], + content: &raw[content_start..], + }; + let (decoded, _) = decode_to_vec(&fixture.frame, split).unwrap(); + assert!(decoded == fixture.content, "{}", fixture.name); + } +} + +/// Every truncation of every fixture is refused or decodes to a prefix, and +/// none panics. +#[cfg(feature = "std")] +#[test] +fn truncated_frames_are_refused() { + for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 4096) { + let dict = fixture + .dict + .as_deref() + .map_or(LegacyDict::None, LegacyDict::Raw); + for len in 0..fixture.frame.len() { + assert!( + decode_to_vec(&fixture.frame[..len], dict).is_err(), + "{} cut to {len} bytes decoded", + fixture.name + ); + } + } +} + +/// Flipping any one bit of a frame is refused or decodes to some output, but +/// never panics; in a v0.7 frame with a checksum a change to the content is +/// caught. +#[cfg(feature = "std")] +#[test] +fn damaged_frames_never_panic() { + for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 4096) { + let dict = fixture + .dict + .as_deref() + .map_or(LegacyDict::None, LegacyDict::Raw); + let mut frame = fixture.frame.clone(); + for byte in 0..frame.len() { + for bit in 0..8 { + frame[byte] ^= 1 << bit; + let _ = decode_to_vec(&frame, dict); + frame[byte] ^= 1 << bit; + } + } + } +} + +/// A v0.7 frame whose content checksum does not match is refused with the +/// checksum error. +#[cfg(all(feature = "std", feature = "hash"))] +#[test] +fn a_wrong_v07_checksum_is_refused() { + let fixture = fixtures() + .into_iter() + .find(|f| f.name == "abc.txt.v07.l1.zst") + .unwrap(); + let mut frame = fixture.frame.clone(); + // The end block's header holds the checksum in its low 22 bits. + let last = frame.len() - 1; + frame[last] ^= 1; + assert!(matches!( + decode_to_vec(&frame, LegacyDict::None), + Err(Error::ChecksumMismatch { .. }) + )); +} + +/// The headers of the three formats: version, size, window and content size. +#[test] +fn headers_read_their_fields() { + // v0.5: magic, then a window descriptor of log 11 + 1. + let v05 = read_header(&[0x25, 0xB5, 0x2F, 0xFD, 0x01]).unwrap(); + assert_eq!( + (v05.version, v05.size, v05.window, v05.content_size), + (LegacyVersion::V05, 5, 1 << 12, None) + ); + // v0.5 reserves the descriptor's high nibble. + assert_eq!( + read_header(&[0x25, 0xB5, 0x2F, 0xFD, 0x10]).unwrap_err(), + Error::UnsupportedParameter + ); + // v0.6: a one-byte content size of 200, window log 12 + 3. + let v06 = read_header(&[0x26, 0xB5, 0x2F, 0xFD, 0x43, 200]).unwrap(); + assert_eq!( + (v06.version, v06.size, v06.window, v06.content_size), + (LegacyVersion::V06, 6, 1 << 15, Some(200)) + ); + // A two-byte content size counts from 256. + let v06 = read_header(&[0x26, 0xB5, 0x2F, 0xFD, 0x80, 1, 0]).unwrap(); + assert_eq!(v06.content_size, Some(257)); + // v0.6 reserves bit 5. + assert_eq!( + read_header(&[0x26, 0xB5, 0x2F, 0xFD, 0x20]).unwrap_err(), + Error::UnsupportedParameter + ); + // v0.7: checksum, a one-byte dictionary ID, single segment with a + // one-byte content size, which is also the window. + let v07 = read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x25, 9, 100]).unwrap(); + assert_eq!( + ( + v07.size, + v07.window, + v07.content_size, + v07.dict_id, + v07.checksum + ), + (7, 100, Some(100), 9, true) + ); + // v0.7 reserves bit 3, and a header cut short is truncated. + assert_eq!( + read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x08, 0]).unwrap_err(), + Error::UnsupportedParameter + ); + assert_eq!( + read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x25, 9]).unwrap_err(), + Error::Truncated + ); + // A window past the format's largest is refused. + assert_eq!( + read_header(&[0x27, 0xB5, 0x2F, 0xFD, 0x00, 0xF8]).unwrap_err(), + Error::UnsupportedParameter + ); +} + +/// An RFC 8878 frame and a frame of another magic number are not legacy. +#[test] +fn other_magic_numbers_are_not_legacy() { + assert_eq!(LegacyVersion::of(&[0x28, 0xB5, 0x2F, 0xFD]), None); + assert_eq!(LegacyVersion::of(&[0x24, 0xB5, 0x2F, 0xFD]), None); + assert_eq!(LegacyVersion::of(&[0x25, 0xB5, 0x2F]), None); + assert_eq!(declared_content_size(&[0x26, 0xB5, 0x2F, 0xFD, 0x43]), None); +} + +/// v0.5 and v0.6 end a frame at any block whose body is empty, v0.7 only at +/// its end block: a raw block of no bytes is an empty block there. +#[test] +fn an_empty_block_ends_only_older_frames() { + let empty_raw = [0x40, 0, 0]; + let end = [0xC0, 0, 0]; + let mut v06 = vec![0x26, 0xB5, 0x2F, 0xFD, 0x00]; + v06.extend_from_slice(&empty_raw); + assert_eq!(frame_size(&v06).unwrap(), (8, 0)); + let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00]; + v07.extend_from_slice(&empty_raw); + assert_eq!(frame_size(&v07).unwrap_err(), Error::Truncated); + v07.extend_from_slice(&end); + assert_eq!(frame_size(&v07).unwrap(), (12, BLOCK_SIZE_MAX as u64)); + assert_eq!( + decode_to_vec(&v07, LegacyDict::None).unwrap(), + (Vec::new(), 12) + ); +} + +/// An RLE block repeats its byte, in every format. +#[test] +fn rle_blocks_repeat_their_byte() { + for magic in [0x25u8, 0x26, 0x27] { + let mut frame = vec![magic, 0xB5, 0x2F, 0xFD, 0x00]; + if magic == 0x27 { + frame.push(0x00); + } + frame.extend_from_slice(&[0x80, 0, 5, b'z', 0xC0, 0, 0]); + let (decoded, consumed) = decode_to_vec(&frame, LegacyDict::None).unwrap(); + assert_eq!(decoded, b"zzzzz"); + assert_eq!(consumed, frame.len()); + } +} + +/// A block may not decode past the largest block size, whatever its header +/// claims. +#[test] +fn a_block_past_the_largest_size_is_refused() { + let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00]; + // An RLE block of 128 KiB + 1. + let size = BLOCK_SIZE_MAX + 1; + frame.extend_from_slice(&[ + 0x80 | (size >> 16) as u8, + (size >> 8) as u8, + size as u8, + b'z', + ]); + frame.extend_from_slice(&[0xC0, 0, 0]); + assert!(matches!( + decode_to_vec(&frame, LegacyDict::None), + Err(Error::Corrupted { .. }) + )); +} + +/// The declared content size does not bound the output: the v0.6.2 command +/// line declares its first chunk's size, and the blocks are what decode. +#[cfg(feature = "std")] +#[test] +fn the_declared_content_size_is_not_enforced() { + // v0.6 with a one-byte content size of 4 and an RLE block of 5. + let frame = [ + 0x26, 0xB5, 0x2F, 0xFD, 0x40, 4, 0x80, 0, 5, b'z', 0xC0, 0, 0, + ]; + assert_eq!(declared_content_size(&frame), Some(4)); + assert_eq!( + decode_to_vec(&frame, LegacyDict::None).unwrap(), + (b"zzzzz".to_vec(), frame.len()) + ); + // A v0.6 archive from that command line, past 128 KiB, declares 128 KiB. + let fixture = fixtures() + .into_iter() + .find(|f| f.name == "z000000.v06.l1.zst") + .unwrap(); + assert_eq!(declared_content_size(&fixture.frame), Some(128 * 1024)); + assert_eq!(fixture.content.len(), 224_787); +} diff --git a/zstd/src/decoding/legacy/v05.rs b/zstd/src/decoding/legacy/v05.rs new file mode 100644 index 00000000..b6109a96 --- /dev/null +++ b/zstd/src/decoding/legacy/v05.rs @@ -0,0 +1,417 @@ +//! The v0.5 format (upstream `lib/legacy/zstd_v05.c`): lengths coded as +//! symbols with long values in a separate "dumps" area, a single repeat +//! offset, and a minimum match of 4. + +use super::entropy::{FseState, FseTable, HufTable, StreamEnd, open_bits, overflowed}; +use super::v06::huffman; +use super::v07::{SeqTables, sequence_count}; +use super::{ + BLOCK_SIZE_MAX, FrameHeader, LegacyVersion, LiteralSource, Literals, Output, corrupt, execute, +}; +use crate::decoding::errors::LegacyFrameError as Error; +use alloc::vec::Vec; + +/// Magic number of a v0.5 dictionary. +const DICT_MAGIC: u32 = 0xEC30_A435; + +const LL_BITS: u32 = 6; +const ML_BITS: u32 = 7; +const OFF_BITS: u32 = 5; +const MAX_LL: usize = (1 << LL_BITS) - 1; +const MAX_ML: usize = (1 << ML_BITS) - 1; +const MAX_OFF: usize = (1 << OFF_BITS) - 1; +const LL_LOG: u32 = 10; +const ML_LOG: u32 = 10; +const OFF_LOG: u32 = 9; +const MIN_MATCH: usize = 4; + +/// Offset code `n` reads `n - 1` bits on top of this; code 0 is the repeat +/// offset, and codes 27 to 31 are not used. +const OFFSET_PREFIX: [usize; MAX_OFF + 1] = [ + 1, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, + 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 1, 1, 1, 1, 1, +]; + +/// Read a v0.5 frame header (`ZSTDv05_getFrameParams`): five bytes, no +/// content size. +pub(super) fn read_header(src: &[u8]) -> Result { + let &descriptor = src.get(4).ok_or(Error::Truncated)?; + let window_log = u32::from(descriptor & 0xF) + 11; + if descriptor >> 4 != 0 || (usize::BITS == 32 && window_log > 25) { + return Err(Error::UnsupportedParameter); + } + Ok(FrameHeader { + version: LegacyVersion::V05, + size: 5, + window: 1 << window_log, + content_size: None, + dict_id: 0, + checksum: false, + }) +} + +/// The decoding state a v0.5 frame carries from block to block. +pub(super) struct Decoder { + /// The dictionary's Huffman table, which repeat-table literals use. + dict_huf: Option, + tables: SeqTables, + /// Whether a block may keep the previous tables: once a dictionary has + /// been loaded (`flagStaticTables`). + repeat_tables: bool, + literals: Vec, +} + +impl Decoder { + /// A decoder primed with the dictionary `bytes`, and where its content + /// lies in them. + pub(super) fn new(bytes: &[u8]) -> Result<(Decoder, core::ops::Range), Error> { + let mut decoder = Decoder { + dict_huf: None, + tables: SeqTables::default(), + repeat_tables: false, + literals: Vec::new(), + }; + if bytes.len() < 4 + || u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) != DICT_MAGIC + { + return Ok((decoder, 0..bytes.len())); + } + // `ZSTDv05_loadEntropy`. + let src = &bytes[4..]; + let bad = |_| Error::DictionaryCorrupted; + let (huf, mut at) = HufTable::read(src, StreamEnd::Exact).map_err(bad)?; + for (slot, max, max_log) in [ + (&mut decoder.tables.of, MAX_OFF, OFF_LOG), + (&mut decoder.tables.ml, MAX_ML, ML_LOG), + (&mut decoder.tables.ll, MAX_LL, LL_LOG), + ] { + let (dist, len) = super::entropy::read_distribution( + src.get(at..).ok_or(Error::DictionaryCorrupted)?, + max, + ) + .map_err(bad)?; + if dist.table_log() > max_log { + return Err(Error::DictionaryCorrupted); + } + *slot = Some(FseTable::new(&dist).map_err(bad)?); + at += len; + } + decoder.dict_huf = Some(huf); + decoder.repeat_tables = true; + Ok((decoder, 4 + at..bytes.len())) + } + + /// Decode one compressed block into `out` (`ZSTDv05_decompressBlock_internal`). + pub(super) fn block( + &mut self, + block: &[u8], + out: &mut O, + dict: &[u8], + ) -> Result<(), Error> { + if block.len() >= BLOCK_SIZE_MAX { + return Err(corrupt("compressed block too large")); + } + let (source, consumed) = self.literals(block)?; + // The buffer is lent out for the sequence pass, which also needs + // `self` for its tables, and handed back after it. + let buffer = core::mem::take(&mut self.literals); + let literals = match &source { + LiteralSource::Block(range) => &block[range.clone()], + LiteralSource::Buffer => &buffer[..], + }; + let result = self.sequences(&block[consumed..], literals, out, dict); + self.literals = buffer; + result + } + + fn literals(&mut self, block: &[u8]) -> Result<(LiteralSource, usize), Error> { + Ok(match Literals::read(block)? { + Literals::Huffman { + header, + size, + compressed, + single_stream, + } => { + let src = &block[header..header + compressed]; + self.literals.clear(); + if single_stream { + self.literals.resize(size, 0); + huffman(src, &mut self.literals, false, StreamEnd::Exact)?; + } else { + // `HUFv05_decompress`: an uncompressed section is not + // Huffman literals here, a one-byte section is a run. + if size == 0 || compressed >= size { + return Err(corrupt("Huffman literals no smaller than their content")); + } + if compressed == 1 { + self.literals.resize(size, src[0]); + } else { + self.literals.resize(size, 0); + huffman(src, &mut self.literals, true, StreamEnd::Exact)?; + } + } + (LiteralSource::Buffer, header + compressed) + } + Literals::Repeat { size, compressed } => { + if !self.repeat_tables { + return Err(Error::DictionaryCorrupted); + } + let table = self.dict_huf.as_ref().ok_or(Error::DictionaryCorrupted)?; + self.literals.clear(); + self.literals.resize(size, 0); + table + .decode_one_stream(&block[3..3 + compressed], &mut self.literals) + .map_err(|_| corrupt("bad Huffman literals"))?; + (LiteralSource::Buffer, 3 + compressed) + } + Literals::Raw { header, size } => { + (LiteralSource::Block(header..header + size), header + size) + } + Literals::Rle { header, size } => { + self.literals.clear(); + self.literals.resize(size, block[header]); + (LiteralSource::Buffer, header + 1) + } + }) + } + + fn sequences( + &mut self, + src: &[u8], + literals: &[u8], + out: &mut O, + dict: &[u8], + ) -> Result<(), Error> { + let (count, mut at) = sequence_count(src, false)?; + let mut lit_pos = 0; + if count > 0 { + // `ZSTDv05_decodeSeqHeaders`: the field modes, then the dumps. + let &modes = src.get(at).ok_or(Error::Truncated)?; + let dumps_len; + if modes & 2 != 0 { + let bytes = src.get(at + 1..at + 3).ok_or(Error::Truncated)?; + dumps_len = usize::from(bytes[1]) + (usize::from(bytes[0]) << 8); + at += 3; + } else { + let &low = src.get(at + 1).ok_or(Error::Truncated)?; + dumps_len = usize::from(low) + (usize::from(modes & 1) << 8); + at += 2; + } + let dumps_start = at; + at += dumps_len; + if at + 3 > src.len() { + return Err(Error::Truncated); + } + let dumps_end = at; + let repeat = self.repeat_tables; + at += field_table( + &mut self.tables.ll, + modes >> 6, + src, + at, + Field::LITERAL_LENGTH, + repeat, + )?; + at += field_table( + &mut self.tables.of, + (modes >> 4) & 3, + src, + at, + Field::OFFSET, + repeat, + )?; + at += field_table( + &mut self.tables.ml, + (modes >> 2) & 3, + src, + at, + Field::MATCH_LENGTH, + repeat, + )?; + let (ll, of, ml) = self.tables.all()?; + + let mut bits = open_bits(&src[at..]).map_err(|_| corrupt("bad sequence bitstream"))?; + let mut ll_state = FseState::new(ll, &mut bits); + let mut of_state = FseState::new(of, &mut bits); + let mut ml_state = FseState::new(ml, &mut bits); + let mut dumps = Dumps { + src, + at: dumps_start, + end: dumps_end, + }; + // The previous sequence's offset and the one before a literal- + // free sequence, both starting at 1 in every block. + let mut last_offset = 1usize; + let mut prev_offset = 1usize; + for _ in 0..count { + if overflowed(&bits) { + return Err(corrupt("sequence bitstream ends early")); + } + // `ZSTDv05_decodeSequence`. + let mut lit_len = usize::from(ll_state.symbol(ll)); + let repeat_offset = if lit_len != 0 { + last_offset + } else { + prev_offset + }; + if lit_len == MAX_LL { + lit_len = dumps.extend(lit_len, true); + } + let code = usize::from(of_state.symbol(of)); + let extra = if code == 0 { 0 } else { code as u8 - 1 }; + let mut offset = OFFSET_PREFIX[code] + bits.get_bits(extra) as usize; + if code == 0 { + offset = repeat_offset; + } + if code != 0 || lit_len == 0 { + prev_offset = last_offset; + } + of_state.update(of, &mut bits); + ll_state.update(ll, &mut bits); + let mut match_len = usize::from(ml_state.symbol(ml)); + ml_state.update(ml, &mut bits); + if match_len == MAX_ML { + match_len = dumps.extend(match_len, false); + } + match_len += MIN_MATCH; + last_offset = offset; + execute( + out, + literals, + &mut lit_pos, + lit_len, + match_len, + offset, + dict, + )?; + } + } + let rest = &literals[lit_pos..]; + out.reserve(rest.len())?; + out.extend(rest); + Ok(()) + } +} + +/// The largest symbol, largest table log and raw width of one sequence field. +struct Field { + max: usize, + max_log: u32, + raw_bits: u32, + /// Whether a one-symbol table's symbol is masked to the field and needs + /// a byte after it (offsets and match lengths), or taken as is (literal + /// lengths). + checked_rle: bool, +} + +impl Field { + const LITERAL_LENGTH: Field = Field { + max: MAX_LL, + max_log: LL_LOG, + raw_bits: LL_BITS, + checked_rle: false, + }; + const OFFSET: Field = Field { + max: MAX_OFF, + max_log: OFF_LOG, + raw_bits: OFF_BITS, + checked_rle: true, + }; + const MATCH_LENGTH: Field = Field { + max: MAX_ML, + max_log: ML_LOG, + raw_bits: ML_BITS, + checked_rle: true, + }; +} + +/// Set one field's table by its mode, the description starting at +/// `src[at..]` (`ZSTDv05_decodeSeqHeaders`). Returns the bytes it took. +fn field_table( + table: &mut Option, + mode: u8, + src: &[u8], + at: usize, + field: Field, + repeat: bool, +) -> Result { + let (new, len) = match mode { + // Every symbol read as raw bits. + 0 => (FseTable::raw(field.raw_bits), 0), + 1 => { + if field.checked_rle && at + 2 > src.len() { + return Err(Error::Truncated); + } + let &symbol = src.get(at).ok_or(Error::Truncated)?; + // Only offsets are masked; a literal or match length symbol past + // its field is taken as it stands. + let symbol = if field.max == MAX_OFF { + symbol & MAX_OFF as u8 + } else { + symbol + }; + (FseTable::rle(symbol), 1) + } + 2 => { + if !repeat || table.is_none() { + return Err(corrupt("sequence table repeated before one was sent")); + } + return Ok(0); + } + _ => { + let (dist, len) = super::entropy::read_distribution(&src[at..], field.max) + .map_err(|_| corrupt("bad sequence table"))?; + if dist.table_log() > field.max_log { + return Err(corrupt("sequence table too large")); + } + (FseTable::new(&dist)?, len) + } + }; + *table = Some(new); + Ok(len) +} + +/// The dumps area, read forward for lengths past a field's largest symbol. +struct Dumps<'s> { + src: &'s [u8], + at: usize, + end: usize, +} + +impl Dumps<'_> { + /// The length a symbol of `base` (the field's largest) stands for: `base` + /// plus one dumps byte, or, when that byte is 255, a value of 16 or 24 + /// bits halved. Reads as upstream does, including its clamp back to the + /// last dumps byte; the area sits inside the sequence section, ahead of + /// at least three more bytes, so every read stays in `src`. + fn extend(&mut self, base: usize, literal: bool) -> usize { + // A literal length always takes its byte; a match length only while + // the area has one. + let add = if literal || self.at < self.end { + let byte = usize::from(self.src[self.at]); + self.at += 1; + byte + } else { + 0 + }; + let mut value = base; + if add < 255 { + value += add; + } else if self.at + 2 <= self.end { + value = usize::from(u16::from_le_bytes([ + self.src[self.at], + self.src[self.at + 1], + ])); + self.at += 2; + if value & 1 != 0 && self.at < self.end { + value += usize::from(self.src[self.at]) << 16; + self.at += 1; + } + value >>= 1; + } + if self.at >= self.end { + self.at = self.end - 1; + } + value + } +} diff --git a/zstd/src/decoding/legacy/v06.rs b/zstd/src/decoding/legacy/v06.rs new file mode 100644 index 00000000..9bfb8e16 --- /dev/null +++ b/zstd/src/decoding/legacy/v06.rs @@ -0,0 +1,312 @@ +//! The v0.6 format (upstream `lib/legacy/zstd_v06.c`): v0.7's coding without +//! a dictionary ID or checksum, repeat offsets that restart every block, and +//! offsets coded two higher. + +use super::entropy::{FseState, FseTable, HufTable, StreamEnd, open_bits, overflowed}; +use super::v07::{ + LL_BASE, LL_BITS, LL_DEFAULT, LL_DEFAULT_LOG, ML_BASE, ML_BITS, ML_DEFAULT, ML_DEFAULT_LOG, + OF_DEFAULT, OF_DEFAULT_LOG, SeqTables, seq_table, sequence_count, +}; +use super::{ + BLOCK_SIZE_MAX, FrameHeader, LegacyVersion, LiteralSource, Literals, Output, corrupt, execute, +}; +use crate::decoding::errors::LegacyFrameError as Error; +use alloc::vec::Vec; + +/// Magic number of a v0.6 dictionary. +const DICT_MAGIC: u32 = 0xEC30_A436; + +const MAX_LL: usize = 35; +const MAX_ML: usize = 52; +const MAX_OFF: usize = 28; +const LL_LOG: u32 = 9; +const ML_LOG: u32 = 9; +const OFF_LOG: u32 = 8; + +/// Offset bases; codes 27 and 28 are not used and read as 1. +const OF_BASE: [u32; MAX_OFF + 1] = [ + 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, + 0xFFFF, 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF, 0x1FFFFFF, + 0x3FFFFFF, 1, 1, +]; + +/// Read a v0.6 frame header (`ZSTDv06_getFrameParams`). +pub(super) fn read_header(src: &[u8]) -> Result { + let &descriptor = src.get(4).ok_or(Error::Truncated)?; + let fcs_len = [0usize, 1, 2, 8][usize::from(descriptor >> 6)]; + let header = src.get(..5 + fcs_len).ok_or(Error::Truncated)?; + let window_log = u32::from(descriptor & 0xF) + 12; + if descriptor & 0x20 != 0 || (usize::BITS == 32 && window_log > 25) { + return Err(Error::UnsupportedParameter); + } + let fcs = &header[5..]; + let content_size = match descriptor >> 6 { + 0 => 0, + 1 => u64::from(fcs[0]), + 2 => u64::from(u16::from_le_bytes([fcs[0], fcs[1]])) + 256, + _ => u64::from_le_bytes(fcs.try_into().expect("eight bytes")), + }; + Ok(FrameHeader { + version: LegacyVersion::V06, + size: header.len(), + window: 1 << window_log, + content_size: (content_size != 0).then_some(content_size), + dict_id: 0, + checksum: false, + }) +} + +/// The decoding state a v0.6 frame carries from block to block. +pub(super) struct Decoder { + /// The dictionary's Huffman table, which repeat-table literals use. + dict_huf: Option, + tables: SeqTables, + /// Whether the next block may use the dictionary's tables: from loading a + /// dictionary until the first sequence section is read + /// (`flagRepeatTable`). + repeat_tables: bool, + literals: Vec, +} + +impl Decoder { + /// A decoder primed with `dict`, and where the dictionary's content lies + /// in `bytes`, the bytes `dict` reads as one run. + pub(super) fn new(bytes: &[u8]) -> Result<(Decoder, core::ops::Range), Error> { + let mut decoder = Decoder { + dict_huf: None, + tables: SeqTables::default(), + repeat_tables: false, + literals: Vec::new(), + }; + if bytes.len() < 4 + || u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) != DICT_MAGIC + { + return Ok((decoder, 0..bytes.len())); + } + // `ZSTDv06_loadEntropy`. + let src = &bytes[4..]; + let bad = |_| Error::DictionaryCorrupted; + let (huf, mut at) = HufTable::read(src, StreamEnd::PastStart).map_err(bad)?; + for (slot, max, max_log) in [ + (&mut decoder.tables.of, MAX_OFF, OFF_LOG), + (&mut decoder.tables.ml, MAX_ML, ML_LOG), + (&mut decoder.tables.ll, MAX_LL, LL_LOG), + ] { + let (dist, len) = super::entropy::read_distribution( + src.get(at..).ok_or(Error::DictionaryCorrupted)?, + max, + ) + .map_err(bad)?; + if dist.table_log() > max_log { + return Err(Error::DictionaryCorrupted); + } + *slot = Some(FseTable::new(&dist).map_err(bad)?); + at += len; + } + decoder.dict_huf = Some(huf); + decoder.repeat_tables = true; + Ok((decoder, 4 + at..bytes.len())) + } + + /// Decode one compressed block into `out` (`ZSTDv06_decompressBlock_internal`). + pub(super) fn block( + &mut self, + block: &[u8], + out: &mut O, + dict: &[u8], + ) -> Result<(), Error> { + if block.len() >= BLOCK_SIZE_MAX { + return Err(corrupt("compressed block too large")); + } + let (source, consumed) = self.literals(block)?; + // The buffer is lent out for the sequence pass, which also needs + // `self` for its tables, and handed back after it. + let buffer = core::mem::take(&mut self.literals); + let literals = match &source { + LiteralSource::Block(range) => &block[range.clone()], + LiteralSource::Buffer => &buffer[..], + }; + let result = self.sequences(&block[consumed..], literals, out, dict); + self.literals = buffer; + result + } + + fn literals(&mut self, block: &[u8]) -> Result<(LiteralSource, usize), Error> { + Ok(match Literals::read(block)? { + Literals::Huffman { + header, + size, + compressed, + single_stream, + } => { + let src = &block[header..header + compressed]; + self.literals.clear(); + if single_stream { + self.literals.resize(size, 0); + huffman(src, &mut self.literals, false, StreamEnd::PastStart)?; + } else { + // `HUFv06_decompress`: a section as long as its content + // is the content, a one-byte section is a run of it. + if size == 0 || compressed > size { + return Err(corrupt("Huffman literals larger than their content")); + } + if compressed == size { + self.literals.extend_from_slice(src); + } else if compressed == 1 { + self.literals.resize(size, src[0]); + } else { + self.literals.resize(size, 0); + huffman(src, &mut self.literals, true, StreamEnd::PastStart)?; + } + } + (LiteralSource::Buffer, header + compressed) + } + Literals::Repeat { size, compressed } => { + if !self.repeat_tables { + return Err(Error::DictionaryCorrupted); + } + let table = self.dict_huf.as_ref().ok_or(Error::DictionaryCorrupted)?; + self.literals.clear(); + self.literals.resize(size, 0); + table + .decode_one_stream(&block[3..3 + compressed], &mut self.literals) + .map_err(|_| corrupt("bad Huffman literals"))?; + (LiteralSource::Buffer, 3 + compressed) + } + Literals::Raw { header, size } => { + (LiteralSource::Block(header..header + size), header + size) + } + Literals::Rle { header, size } => { + self.literals.clear(); + self.literals.resize(size, block[header]); + (LiteralSource::Buffer, header + 1) + } + }) + } + + fn sequences( + &mut self, + src: &[u8], + literals: &[u8], + out: &mut O, + dict: &[u8], + ) -> Result<(), Error> { + let (count, mut at) = sequence_count(src, true)?; + let mut lit_pos = 0; + if count > 0 { + if at + 4 > src.len() { + return Err(Error::Truncated); + } + let modes = src[at]; + at += 1; + let repeat = self.repeat_tables; + at += seq_table( + &mut self.tables.ll, + modes >> 6, + &src[at..], + (MAX_LL, LL_LOG, &LL_DEFAULT, LL_DEFAULT_LOG), + repeat, + )?; + at += seq_table( + &mut self.tables.of, + (modes >> 4) & 3, + &src[at..], + (MAX_OFF, OFF_LOG, &OF_DEFAULT, OF_DEFAULT_LOG), + repeat, + )?; + at += seq_table( + &mut self.tables.ml, + (modes >> 2) & 3, + &src[at..], + (MAX_ML, ML_LOG, &ML_DEFAULT, ML_DEFAULT_LOG), + repeat, + )?; + } + // Read once, the dictionary's tables are no longer on offer, even to + // a block that sent none. + self.repeat_tables = false; + if count > 0 { + let (ll, of, ml) = self.tables.all()?; + let mut reps = [1usize; 3]; + let mut bits = open_bits(&src[at..]).map_err(|_| corrupt("bad sequence bitstream"))?; + let mut ll_state = FseState::new(ll, &mut bits); + let mut of_state = FseState::new(of, &mut bits); + let mut ml_state = FseState::new(ml, &mut bits); + for _ in 0..count { + if overflowed(&bits) { + return Err(corrupt("sequence bitstream ends early")); + } + let ll_code = usize::from(ll_state.symbol(ll)); + let ml_code = usize::from(ml_state.symbol(ml)); + let of_code = of_state.symbol(of); + let mut offset = if of_code == 0 { + 0 + } else { + OF_BASE[usize::from(of_code)] as usize + bits.get_bits(of_code) as usize + }; + if offset < 3 { + if ll_code == 0 && offset <= 1 { + offset = 1 - offset; + } + if offset != 0 { + let picked = reps[offset]; + if offset != 1 { + reps[2] = reps[1]; + } + reps[1] = reps[0]; + reps[0] = picked; + offset = picked; + } else { + offset = reps[0]; + } + } else { + offset -= 2; + reps[2] = reps[1]; + reps[1] = reps[0]; + reps[0] = offset; + } + let match_len = + ML_BASE[ml_code] as usize + bits.get_bits(ML_BITS[ml_code]) as usize; + let lit_len = LL_BASE[ll_code] as usize + bits.get_bits(LL_BITS[ll_code]) as usize; + ll_state.update(ll, &mut bits); + ml_state.update(ml, &mut bits); + of_state.update(of, &mut bits); + execute( + out, + literals, + &mut lit_pos, + lit_len, + match_len, + offset, + dict, + )?; + } + } + let rest = &literals[lit_pos..]; + out.reserve(rest.len())?; + out.extend(rest); + Ok(()) + } +} + +/// Decode Huffman literals with the table their section starts with, into +/// `out` (`HUFv06_decompress1X2` / `HUFv06_decompress4X2`); `end` is how the +/// format ends the table's FSE-coded weights. +pub(super) fn huffman( + src: &[u8], + out: &mut [u8], + four_streams: bool, + end: StreamEnd, +) -> Result<(), Error> { + let (table, table_len) = HufTable::read(src, end).map_err(|_| corrupt("bad Huffman table"))?; + if table_len >= src.len() { + return Err(corrupt("Huffman table fills the literal section")); + } + let streams = &src[table_len..]; + if four_streams { + table.decode_four_streams(streams, out) + } else { + table.decode_one_stream(streams, out) + } + .map_err(|_| corrupt("bad Huffman literals")) +} diff --git a/zstd/src/decoding/legacy/v07.rs b/zstd/src/decoding/legacy/v07.rs new file mode 100644 index 00000000..d9e88168 --- /dev/null +++ b/zstd/src/decoding/legacy/v07.rs @@ -0,0 +1,495 @@ +//! The v0.7 format (upstream `lib/legacy/zstd_v07.c`): the last before 1.0, +//! with a content checksum, a dictionary ID, and repeat offsets that carry +//! from block to block. + +use super::entropy::{ + Distribution, FseState, FseTable, HufTable, StreamEnd, open_bits, overflowed, +}; +use super::{ + BLOCK_SIZE_MAX, FrameHeader, LegacyDict, LegacyVersion, LiteralSource, Literals, Output, + corrupt, execute, +}; +use crate::decoding::errors::LegacyFrameError as Error; +use alloc::vec::Vec; + +/// Magic number of a v0.7 dictionary; RFC 8878 dictionaries kept it. +pub(super) const DICT_MAGIC: u32 = 0xEC30_A437; + +/// Largest window a frame may ask for (`ZSTDv07_WINDOWLOG_MAX`). +const WINDOW_LOG_MAX: u32 = if usize::BITS == 32 { 25 } else { 27 }; + +const MAX_LL: usize = 35; +const MAX_ML: usize = 52; +const MAX_OFF: usize = 28; +const LL_LOG: u32 = 9; +const ML_LOG: u32 = 9; +const OFF_LOG: u32 = 8; + +pub(super) const LL_BITS: [u8; MAX_LL + 1] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, +]; +pub(super) const LL_BASE: [u32; MAX_LL + 1] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 40, 48, 64, + 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000, 0x10000, +]; +pub(super) const ML_BITS: [u8; MAX_ML + 1] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, +]; +/// Match length bases, the minimum match of 3 included. +pub(super) const ML_BASE: [u32; MAX_ML + 1] = [ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 0x83, 0x103, 0x203, + 0x403, 0x803, 0x1003, 0x2003, 0x4003, 0x8003, 0x10003, +]; +const OF_BASE: [u32; MAX_OFF + 1] = [ + 0, 1, 1, 5, 0xD, 0x1D, 0x3D, 0x7D, 0xFD, 0x1FD, 0x3FD, 0x7FD, 0xFFD, 0x1FFD, 0x3FFD, 0x7FFD, + 0xFFFD, 0x1FFFD, 0x3FFFD, 0x7FFFD, 0xFFFFD, 0x1FFFFD, 0x3FFFFD, 0x7FFFFD, 0xFFFFFD, 0x1FFFFFD, + 0x3FFFFFD, 0x7FFFFFD, 0xFFFFFFD, +]; + +/// The predefined distributions, shared with v0.6. +pub(super) const LL_DEFAULT: [i16; MAX_LL + 1] = [ + 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1, + -1, -1, -1, -1, +]; +pub(super) const ML_DEFAULT: [i16; MAX_ML + 1] = [ + 1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, +]; +pub(super) const OF_DEFAULT: [i16; MAX_OFF + 1] = [ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, +]; +pub(super) const LL_DEFAULT_LOG: u32 = 6; +pub(super) const ML_DEFAULT_LOG: u32 = 6; +pub(super) const OF_DEFAULT_LOG: u32 = 5; + +/// Read a v0.7 frame header (`ZSTDv07_getFrameParams`). +pub(super) fn read_header(src: &[u8]) -> Result { + let &descriptor = src.get(4).ok_or(Error::Truncated)?; + let dict_id_code = descriptor & 3; + let checksum = descriptor & 4 != 0; + let single_segment = descriptor & 0x20 != 0; + let fcs_code = descriptor >> 6; + let fcs_len = [0usize, 2, 4, 8][usize::from(fcs_code)]; + let size = 5 + + usize::from(!single_segment) + + [0usize, 1, 2, 4][usize::from(dict_id_code)] + + fcs_len + + usize::from(single_segment && fcs_len == 0); + let header = src.get(..size).ok_or(Error::Truncated)?; + if descriptor & 0x08 != 0 { + return Err(Error::UnsupportedParameter); + } + let mut pos = 5; + let mut window: u32 = 0; + if !single_segment { + let byte = header[pos]; + pos += 1; + let log = u32::from(byte >> 3) + 10; + if log > WINDOW_LOG_MAX { + return Err(Error::UnsupportedParameter); + } + window = 1 << log; + window += (window >> 3) * u32::from(byte & 7); + } + let le = |at: usize, len: usize| { + header[at..at + len] + .iter() + .rev() + .fold(0u64, |acc, &byte| acc << 8 | u64::from(byte)) + }; + let dict_id = match dict_id_code { + 0 => 0, + 1 => le(pos, 1) as u32, + 2 => le(pos, 2) as u32, + _ => le(pos, 4) as u32, + }; + pos += [0usize, 1, 2, 4][usize::from(dict_id_code)]; + let content_size = match fcs_code { + 0 if single_segment => le(pos, 1), + 0 => 0, + 1 => le(pos, 2) + 256, + 2 => le(pos, 4), + _ => le(pos, 8), + }; + if window == 0 { + // A single-segment frame's window is its content, cut to 32 bits as + // upstream's `windowSize` field holds it. + window = content_size as u32; + } + if window > 1 << WINDOW_LOG_MAX { + return Err(Error::UnsupportedParameter); + } + Ok(FrameHeader { + version: LegacyVersion::V07, + size, + window: u64::from(window), + content_size: (content_size != 0).then_some(content_size), + dict_id, + checksum, + }) +} + +/// The tables and offsets a dictionary primes, read as `ZSTDv07_loadEntropy` +/// reads them; `dict_size` is the size the repeat offsets are checked +/// against, the dictionary less its magic number and ID. +fn load_entropy(src: &[u8], dict_size: usize) -> Result<(Decoder, usize), Error> { + let bad = |_| Error::DictionaryCorrupted; + let (huf, mut at) = HufTable::read(src, StreamEnd::PastStart).map_err(bad)?; + let mut table = |max: usize, max_log: u32| -> Result { + let (dist, len) = super::entropy::read_distribution( + src.get(at..).ok_or(Error::DictionaryCorrupted)?, + max, + ) + .map_err(bad)?; + if dist.table_log() > max_log { + return Err(Error::DictionaryCorrupted); + } + at += len; + FseTable::new(&dist).map_err(bad) + }; + let of = table(MAX_OFF, OFF_LOG)?; + let ml = table(MAX_ML, ML_LOG)?; + let ll = table(MAX_LL, LL_LOG)?; + let reps_bytes = src.get(at..at + 12).ok_or(Error::DictionaryCorrupted)?; + let mut reps = [0usize; 3]; + for (rep, bytes) in reps.iter_mut().zip(reps_bytes.chunks_exact(4)) { + let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; + if value == 0 || value >= dict_size { + return Err(Error::DictionaryCorrupted); + } + *rep = value; + } + Ok(( + Decoder { + huf: Some(huf), + tables: SeqTables { + ll: Some(ll), + of: Some(of), + ml: Some(ml), + }, + repeat_tables: true, + reps, + literals: Vec::new(), + dict_id: 0, + }, + at + 12, + )) +} + +/// The literal length, offset and match length tables last in use, which a +/// block may keep instead of sending new ones. +#[derive(Default)] +pub(super) struct SeqTables { + pub(super) ll: Option, + pub(super) of: Option, + pub(super) ml: Option, +} + +/// The decoding state a v0.7 frame carries from block to block. +pub(super) struct Decoder { + /// The last Huffman table, which repeat-table literals reuse. + huf: Option, + tables: SeqTables, + /// Whether a block may keep the previous tables: once a dictionary or a + /// block with sequences has set them (`fseEntropy`). + repeat_tables: bool, + reps: [usize; 3], + literals: Vec, + /// ID of the dictionary in use, 0 for none. + pub(super) dict_id: u32, +} + +impl Decoder { + /// A decoder primed with `dict`, and where the dictionary's content + /// lies within the bytes the returned range indexes: `dict`'s raw bytes, + /// or a split dictionary's content. + pub(super) fn new(dict: LegacyDict<'_>) -> Result<(Decoder, core::ops::Range), Error> { + let fresh = || Decoder { + huf: None, + tables: SeqTables::default(), + repeat_tables: false, + reps: [1, 4, 8], + literals: Vec::new(), + dict_id: 0, + }; + match dict { + LegacyDict::None => Ok((fresh(), 0..0)), + LegacyDict::Raw(raw) => { + if raw.len() < 8 + || u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) != DICT_MAGIC + { + return Ok((fresh(), 0..raw.len())); + } + let id = u32::from_le_bytes([raw[4], raw[5], raw[6], raw[7]]); + let (mut decoder, len) = load_entropy(&raw[8..], raw.len() - 8)?; + decoder.dict_id = id; + Ok((decoder, 8 + len..raw.len())) + } + LegacyDict::Split { + id, + entropy, + content, + } => { + let (mut decoder, len) = load_entropy(entropy, entropy.len() + content.len())?; + if len != entropy.len() { + return Err(Error::DictionaryCorrupted); + } + decoder.dict_id = id; + Ok((decoder, 0..content.len())) + } + } + } + + /// Decode one compressed block into `out` (`ZSTDv07_decompressBlock_internal`). + pub(super) fn block( + &mut self, + block: &[u8], + out: &mut O, + dict: &[u8], + ) -> Result<(), Error> { + if block.len() >= BLOCK_SIZE_MAX { + return Err(corrupt("compressed block too large")); + } + let (source, consumed) = self.literals(block)?; + // The buffer is lent out for the sequence pass, which also needs + // `self` for its tables, and handed back after it. + let buffer = core::mem::take(&mut self.literals); + let literals = match &source { + LiteralSource::Block(range) => &block[range.clone()], + LiteralSource::Buffer => &buffer[..], + }; + let result = self.sequences(&block[consumed..], literals, out, dict); + self.literals = buffer; + result + } + + fn literals(&mut self, block: &[u8]) -> Result<(LiteralSource, usize), Error> { + Ok(match Literals::read(block)? { + Literals::Huffman { + header, + size, + compressed, + single_stream, + } => { + let src = &block[header..header + compressed]; + if !single_stream && (compressed >= size || compressed <= 1) { + return Err(corrupt("Huffman literals no smaller than their content")); + } + let (table, table_len) = HufTable::read(src, StreamEnd::PastStart) + .map_err(|_| corrupt("bad Huffman table"))?; + if table_len >= src.len() { + return Err(corrupt("Huffman table fills the literal section")); + } + self.literals.clear(); + self.literals.resize(size, 0); + let streams = &src[table_len..]; + if single_stream { + table.decode_one_stream(streams, &mut self.literals) + } else { + table.decode_four_streams(streams, &mut self.literals) + } + .map_err(|_| corrupt("bad Huffman literals"))?; + self.huf = Some(table); + (LiteralSource::Buffer, header + compressed) + } + Literals::Repeat { size, compressed } => { + let table = self.huf.as_ref().ok_or(Error::DictionaryCorrupted)?; + self.literals.clear(); + self.literals.resize(size, 0); + table + .decode_one_stream(&block[3..3 + compressed], &mut self.literals) + .map_err(|_| corrupt("bad Huffman literals"))?; + (LiteralSource::Buffer, 3 + compressed) + } + Literals::Raw { header, size } => { + (LiteralSource::Block(header..header + size), header + size) + } + Literals::Rle { header, size } => { + self.literals.clear(); + self.literals.resize(size, block[header]); + (LiteralSource::Buffer, header + 1) + } + }) + } + + fn sequences( + &mut self, + src: &[u8], + literals: &[u8], + out: &mut O, + dict: &[u8], + ) -> Result<(), Error> { + let (count, mut at) = sequence_count(src, true)?; + let mut lit_pos = 0; + if count > 0 { + if at + 4 > src.len() { + return Err(Error::Truncated); + } + let modes = src[at]; + at += 1; + let repeat = self.repeat_tables; + at += seq_table( + &mut self.tables.ll, + modes >> 6, + &src[at..], + (MAX_LL, LL_LOG, &LL_DEFAULT, LL_DEFAULT_LOG), + repeat, + )?; + at += seq_table( + &mut self.tables.of, + (modes >> 4) & 3, + &src[at..], + (MAX_OFF, OFF_LOG, &OF_DEFAULT, OF_DEFAULT_LOG), + repeat, + )?; + at += seq_table( + &mut self.tables.ml, + (modes >> 2) & 3, + &src[at..], + (MAX_ML, ML_LOG, &ML_DEFAULT, ML_DEFAULT_LOG), + repeat, + )?; + self.repeat_tables = true; + let (ll, of, ml) = self.tables.all()?; + + let mut reps = self.reps; + let mut bits = open_bits(&src[at..]).map_err(|_| corrupt("bad sequence bitstream"))?; + let mut ll_state = FseState::new(ll, &mut bits); + let mut of_state = FseState::new(of, &mut bits); + let mut ml_state = FseState::new(ml, &mut bits); + for _ in 0..count { + if overflowed(&bits) { + return Err(corrupt("sequence bitstream ends early")); + } + let ll_code = usize::from(ll_state.symbol(ll)); + let ml_code = usize::from(ml_state.symbol(ml)); + let of_code = of_state.symbol(of); + let mut offset = if of_code == 0 { + 0 + } else { + OF_BASE[usize::from(of_code)] as usize + bits.get_bits(of_code) as usize + }; + if of_code <= 1 { + if ll_code == 0 && offset <= 1 { + offset = 1 - offset; + } + if offset != 0 { + let picked = reps[offset]; + if offset != 1 { + reps[2] = reps[1]; + } + reps[1] = reps[0]; + reps[0] = picked; + offset = picked; + } else { + offset = reps[0]; + } + } else { + reps[2] = reps[1]; + reps[1] = reps[0]; + reps[0] = offset; + } + let match_len = + ML_BASE[ml_code] as usize + bits.get_bits(ML_BITS[ml_code]) as usize; + let lit_len = LL_BASE[ll_code] as usize + bits.get_bits(LL_BITS[ll_code]) as usize; + ll_state.update(ll, &mut bits); + ml_state.update(ml, &mut bits); + of_state.update(of, &mut bits); + execute( + out, + literals, + &mut lit_pos, + lit_len, + match_len, + offset, + dict, + )?; + } + self.reps = reps; + } + let rest = &literals[lit_pos..]; + out.reserve(rest.len())?; + out.extend(rest); + Ok(()) + } +} + +/// The sequence count at the start of a sequence section and the bytes it +/// takes (`ZSTDv07_decodeSeqHeaders`); `long_form` allows the three-byte +/// count v0.6 and v0.7 have. +pub(super) fn sequence_count(src: &[u8], long_form: bool) -> Result<(usize, usize), Error> { + let &first = src.first().ok_or(Error::Truncated)?; + let first = usize::from(first); + Ok(if first <= 0x7F { + (first, 1) + } else if long_form && first == 0xFF { + let bytes = src.get(1..3).ok_or(Error::Truncated)?; + ( + usize::from(u16::from_le_bytes([bytes[0], bytes[1]])) + 0x7F00, + 3, + ) + } else { + let &second = src.get(1).ok_or(Error::Truncated)?; + (((first - 0x80) << 8) + usize::from(second), 2) + }) +} + +impl SeqTables { + /// The three tables, all set once a block has used them. + pub(super) fn all(&self) -> Result<(&FseTable, &FseTable, &FseTable), Error> { + match (&self.ll, &self.of, &self.ml) { + (Some(ll), Some(of), Some(ml)) => Ok((ll, of, ml)), + _ => Err(corrupt("sequence table repeated before one was sent")), + } + } +} + +/// Set the table one sequence field is coded with, by its mode +/// (`ZSTDv07_buildSeqTable`): `field` gives the largest symbol, the largest +/// table log, and the predefined distribution; the repeat mode keeps `table` +/// as it is, where `repeat` allows it. Returns the bytes the mode's +/// description took. +pub(super) fn seq_table( + table: &mut Option, + mode: u8, + src: &[u8], + field: (usize, u32, &[i16], u32), + repeat: bool, +) -> Result { + let (max, max_log, default, default_log) = field; + let (new, len) = match mode { + // Predefined. + 0 => ( + FseTable::new(&Distribution::predefined(default, default_log))?, + 0, + ), + // One symbol throughout. + 1 => { + let &symbol = src.first().ok_or(Error::Truncated)?; + if usize::from(symbol) > max { + return Err(corrupt("sequence symbol out of range")); + } + (FseTable::rle(symbol), 1) + } + // The previous table, in place. + 2 => { + if !repeat || table.is_none() { + return Err(corrupt("sequence table repeated before one was sent")); + } + return Ok(0); + } + _ => { + let (dist, len) = super::entropy::read_distribution(src, max) + .map_err(|_| corrupt("bad sequence table"))?; + if dist.table_log() > max_log { + return Err(corrupt("sequence table too large")); + } + (FseTable::new(&dist)?, len) + } + }; + *table = Some(new); + Ok(len) +} diff --git a/zstd/src/decoding/mod.rs b/zstd/src/decoding/mod.rs index e19100be..02acd7e1 100644 --- a/zstd/src/decoding/mod.rs +++ b/zstd/src/decoding/mod.rs @@ -41,6 +41,9 @@ pub mod workspace; pub use dictionary::{Dictionary, DictionaryHandle, MAGIC_NUM as DICTIONARY_MAGIC}; pub use frame_decoder::{BlockDecodingStrategy, ContentChecksum, FrameDecoder}; +#[cfg(feature = "legacy")] +#[cfg_attr(docsrs, doc(cfg(feature = "legacy")))] +pub use legacy::LegacyVersion; /// Largest window a frame may declare before a decoder that holds the window /// itself refuses it, unless [`FrameDecoder::set_max_window_size`] raised the @@ -616,6 +619,8 @@ pub(crate) mod exec_sequence_inline; // rationale. pub(crate) mod flat_buf; pub(crate) mod frame; +#[cfg(feature = "legacy")] +pub(crate) mod legacy; pub(crate) mod literals_section_decoder; pub(crate) mod prefetch; mod ringbuffer; From 8d5ff4722a55f39cd779cf0836162deef85fcb20 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 19 Oct 2026 14:29:12 +0300 Subject: [PATCH 02/11] feat(decoding): answer frame queries and decode_all for v0.5-v0.7 - read_frame_content_size, find_frame_compressed_size, find_decompressed_size and frame_decompressed_bound read these frames as libzstd does: the declared size or unknown, the length through the end block, and 128 KiB per block as the bound. The size walk reads the header's size fields only, as upstream's does. - is_frame: the codec's answer for ZSTD_isFrame, legacy magics included when the feature is on. - FrameDecoder::decode_all and its dictionary variants decode these frames straight into the caller's slice; a v0.7 frame naming a dictionary takes it from the handles given or from the registry. - A v0.7 frame takes its dictionary tables from the parsed Dictionary, so a handle serves both formats without a second copy of the dictionary bytes. Part of #131 --- zstd/src/decoding/frame_decoder.rs | 96 ++++++++++++-- zstd/src/decoding/legacy/entropy.rs | 47 ++++++- zstd/src/decoding/legacy/mod.rs | 111 ++++++++-------- zstd/src/decoding/legacy/tests.rs | 189 +++++++++++++++++++++++++--- zstd/src/decoding/legacy/v07.rs | 69 ++++++++-- zstd/src/decoding/mod.rs | 53 +++++++- zstd/src/huff0/huff0_decoder.rs | 7 ++ 7 files changed, 473 insertions(+), 99 deletions(-) diff --git a/zstd/src/decoding/frame_decoder.rs b/zstd/src/decoding/frame_decoder.rs index 4cc002a2..51df9403 100644 --- a/zstd/src/decoding/frame_decoder.rs +++ b/zstd/src/decoding/frame_decoder.rs @@ -3553,11 +3553,17 @@ impl FrameDecoder { ) -> Result { #[cfg(not(feature = "lsm"))] { - self.decode_all_impl(input, output, |this, src| this.reset_from_slice(src)) + self.decode_all_impl(input, output, |this, src| this.reset_from_slice(src), &[]) } #[cfg(feature = "lsm")] { - self.decode_all_impl(input, output, |this, src| this.reset_from_slice(src), None) + self.decode_all_impl( + input, + output, + |this, src| this.reset_from_slice(src), + None, + &[], + ) } } @@ -3610,6 +3616,7 @@ impl FrameDecoder { output, |this, src| this.reset_from_slice(src), Some(&mut visitor), + &[], ) } @@ -3638,9 +3645,12 @@ impl FrameDecoder { ) -> Result { #[cfg(not(feature = "lsm"))] { - self.decode_all_impl(input, output, |this, src| { - this.reset_from_slice_with_dict_handle(src, dict) - }) + self.decode_all_impl( + input, + output, + |this, src| this.reset_from_slice_with_dict_handle(src, dict), + core::slice::from_ref(dict), + ) } #[cfg(feature = "lsm")] { @@ -3649,6 +3659,7 @@ impl FrameDecoder { output, |this, src| this.reset_from_slice_with_dict_handle(src, dict), None, + core::slice::from_ref(dict), ) } } @@ -3688,9 +3699,12 @@ impl FrameDecoder { } #[cfg(not(feature = "lsm"))] { - self.decode_all_impl(input, output, |this, src| { - this.reset_from_slice_with_dict_handles(src, dicts) - }) + self.decode_all_impl( + input, + output, + |this, src| this.reset_from_slice_with_dict_handles(src, dicts), + dicts, + ) } #[cfg(feature = "lsm")] { @@ -3699,6 +3713,7 @@ impl FrameDecoder { output, |this, src| this.reset_from_slice_with_dict_handles(src, dicts), None, + dicts, ) } } @@ -3903,6 +3918,53 @@ impl FrameDecoder { Ok(produced as usize) } + /// The dictionary registered under `dict_id`, owned or shared. + #[cfg(feature = "legacy")] + fn registered_dict(&self, dict_id: u32) -> Option<&DictionaryHandle> { + let owned = self.owned_dicts.get(&dict_id); + #[cfg(target_has_atomic = "ptr")] + let owned = owned.or_else(|| self.shared_dicts.get(&dict_id)); + owned + } + + /// Decode the v0.5-v0.7 frame starting `*input` into the start of + /// `output`, advancing `*input` past it; returns the bytes written. + /// + /// A v0.7 frame naming a dictionary takes the one of `dicts` with that ID + /// (else the last, which then fails the ID check), or with no `dicts` the + /// one registered under it. A frame naming none takes the last of `dicts`. + #[cfg(feature = "legacy")] + fn decode_legacy_frame( + &self, + input: &mut &[u8], + output: &mut [u8], + dicts: &[DictionaryHandle], + ) -> Result { + use crate::decoding::errors::LegacyFrameError; + use crate::decoding::legacy::{self, LegacyDict, SliceOutput}; + let header = legacy::read_header(input)?; + let named = (header.dict_id != 0).then_some(header.dict_id); + let dict = match (named, dicts.is_empty()) { + (_, false) => Some(dictionary_for_frame(dicts, named)), + (Some(dict_id), true) => Some( + self.registered_dict(dict_id) + .ok_or(FrameDecoderError::DictNotProvided { dict_id })?, + ), + (None, true) => None, + }; + let dict = dict.map_or(LegacyDict::None, |dict| LegacyDict::Parsed(dict.as_dict())); + let mut out = SliceOutput { + buf: output, + pos: 0, + }; + let consumed = legacy::decode_frame(input, dict, &mut out).map_err(|err| match err { + LegacyFrameError::OutputTooSmall => FrameDecoderError::TargetTooSmall, + err => err.into(), + })?; + *input = &input[consumed..]; + Ok(out.pos) + } + /// Default-feature decode_all_impl: no visitor parameter so the /// no-lsm build's call surface and codegen are byte-identical to /// the pre-#172 implementation. Compiles only when `lsm` is OFF. @@ -3912,9 +3974,18 @@ impl FrameDecoder { mut input: &[u8], mut output: &mut [u8], mut init_frame: impl FnMut(&mut Self, &mut &[u8]) -> Result<(), FrameDecoderError>, + // The dictionaries a v0.5-v0.7 frame chooses from. + #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle], ) -> Result { let mut total_bytes_written = 0; while !input.is_empty() { + #[cfg(feature = "legacy")] + if !self.magicless && crate::decoding::LegacyVersion::of(input).is_some() { + let written = self.decode_legacy_frame(&mut input, output, dicts)?; + output = &mut output[written..]; + total_bytes_written += written; + continue; + } match init_frame(self, &mut input) { Ok(_) => {} Err(FrameDecoderError::ReadFrameHeaderError( @@ -4059,9 +4130,18 @@ impl FrameDecoder { mut output: &mut [u8], mut init_frame: impl FnMut(&mut Self, &mut &[u8]) -> Result<(), FrameDecoderError>, mut skippable_visitor: Option<&mut dyn FnMut(u8, &[u8])>, + // The dictionaries a v0.5-v0.7 frame chooses from. + #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle], ) -> Result { let mut total_bytes_written = 0; while !input.is_empty() { + #[cfg(feature = "legacy")] + if !self.magicless && crate::decoding::LegacyVersion::of(input).is_some() { + let written = self.decode_legacy_frame(&mut input, output, dicts)?; + output = &mut output[written..]; + total_bytes_written += written; + continue; + } match init_frame(self, &mut input) { Ok(_) => {} Err(FrameDecoderError::ReadFrameHeaderError( diff --git a/zstd/src/decoding/legacy/entropy.rs b/zstd/src/decoding/legacy/entropy.rs index 7db889a2..ea6dc446 100644 --- a/zstd/src/decoding/legacy/entropy.rs +++ b/zstd/src/decoding/legacy/entropy.rs @@ -175,6 +175,27 @@ impl Distribution { } } + /// The distribution an RFC 8878 table description parsed to, listing at + /// most `max_symbol + 1` symbols. + pub(super) fn from_probabilities( + probabilities: &[i32], + table_log: u32, + max_symbol: usize, + ) -> Result { + if probabilities.is_empty() || probabilities.len() > max_symbol + 1 { + return Err(corrupt("FSE description lists too many symbols")); + } + let mut counts = [0i16; 256]; + for (count, &probability) in counts.iter_mut().zip(probabilities) { + *count = i16::try_from(probability).map_err(|_| corrupt("FSE count out of range"))?; + } + Ok(Distribution { + counts, + max_symbol: probabilities.len() - 1, + table_log, + }) + } + pub(super) fn table_log(&self) -> u32 { self.table_log } @@ -442,7 +463,31 @@ impl HufTable { (fse_decompress(body, &mut weights, 255, end)?, len + 1) } }; + Ok((HufTable::from_weights(&mut weights, explicit)?, consumed)) + } + + /// The table whose weights are an RFC 8878 description's code lengths, + /// the last one included, under a longest code of `max_bits`. + pub(super) fn from_code_lengths(lengths: &[u8], max_bits: u8) -> Result { + let explicit = lengths + .len() + .checked_sub(1) + .filter(|&explicit| explicit < 256) + .ok_or(corrupt("Huffman description without symbols"))?; + let mut weights = [0u8; 256]; + for (weight, &len) in weights.iter_mut().zip(&lengths[..explicit]) { + if len > max_bits { + return Err(corrupt("Huffman code longer than its table")); + } + *weight = if len == 0 { 0 } else { max_bits + 1 - len }; + } + // The last weight is inferred again, from the others. + HufTable::from_weights(&mut weights, explicit) + } + /// Build the table from the first `explicit` weights, inferring the + /// weight of the symbol after them. + fn from_weights(weights: &mut [u8; 256], explicit: usize) -> Result { let mut ranks = [0u32; HUF_WEIGHT_LIMIT as usize + 1]; let mut total = 0u32; for &weight in &weights[..explicit] { @@ -492,7 +537,7 @@ impl HufTable { cells[first..first + len].fill((symbol as u8, (log + 1 - u32::from(weight)) as u8)); start[usize::from(weight)] += len as u32; } - Ok((HufTable { log, cells }, consumed)) + Ok(HufTable { log, cells }) } #[inline] diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index aed5be0c..f2163983 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -14,6 +14,7 @@ mod v05; mod v06; mod v07; +use crate::decoding::Dictionary; use crate::decoding::errors::LegacyFrameError as Error; use alloc::vec::Vec; @@ -65,38 +66,26 @@ pub(crate) enum LegacyDict<'a> { /// The bytes as the caller supplied them; each format reads its own /// layout from them. Raw(&'a [u8]), - /// A dictionary already split by the RFC 8878 parser, whose layout is the - /// v0.7 one: its ID, the bytes from its entropy tables through its repeat - /// offsets, and its content. - Split { - id: u32, - entropy: &'a [u8], - content: &'a [u8], - }, + /// A dictionary the RFC 8878 parser has read. Its layout is the v0.7 one, + /// so a v0.7 frame takes its tables, repeat offsets and ID; v0.5 and v0.6 + /// read any dictionary of the v0.7 layout as plain content, and are given + /// its content. + Parsed(&'a Dictionary), } impl<'a> LegacyDict<'a> { - /// The dictionary's bytes as one contiguous run, for a format that reads - /// a split dictionary as plain content. Only a split dictionary needs the - /// copy; it is rare enough (an RFC 8878 dictionary with a v0.5 or v0.6 - /// frame) to pay for one. - fn whole(&self) -> alloc::borrow::Cow<'a, [u8]> { - use alloc::borrow::Cow; + /// The bytes a v0.5 or v0.6 frame reads as its dictionary. + /// + /// Those formats take a dictionary of the v0.7 layout whole, header and + /// tables too, as content. A parsed one keeps only its content, which is + /// the end of those bytes: a match reaching past it into the header + /// fails as reaching before the dictionary, rather than decoding to + /// other bytes. + fn content_bytes(&self) -> &'a [u8] { match *self { - LegacyDict::None => Cow::Borrowed(&[]), - LegacyDict::Raw(bytes) => Cow::Borrowed(bytes), - LegacyDict::Split { - id, - entropy, - content, - } => { - let mut whole = Vec::with_capacity(8 + entropy.len() + content.len()); - whole.extend_from_slice(&v07::DICT_MAGIC.to_le_bytes()); - whole.extend_from_slice(&id.to_le_bytes()); - whole.extend_from_slice(entropy); - whole.extend_from_slice(content); - Cow::Owned(whole) - } + LegacyDict::None => &[], + LegacyDict::Raw(bytes) => bytes, + LegacyDict::Parsed(dict) => &dict.dict_content, } } } @@ -176,26 +165,45 @@ fn ends_frame(version: LegacyVersion, block: Block) -> bool { } } -/// Where the frame starting `src` ends, and the most it can decode to: one -/// block's maximum per block (`ZSTD_findFrameSizeInfoLegacy`). -pub(crate) fn frame_size(src: &[u8]) -> Result<(usize, u64), Error> { - let header = read_header(src)?; - let mut at = header.size; +/// The length of the frame header at the start of `src` from its size fields +/// alone, its other fields unchecked, as upstream's size walks read it +/// (`ZSTDv0{6,7}_frameHeaderSize`; v0.5 headers are always 5 bytes). +fn header_len(version: LegacyVersion, src: &[u8]) -> Result { + let descriptor = *src.get(4).ok_or(Error::Truncated)?; + Ok(match version { + LegacyVersion::V05 => 5, + LegacyVersion::V06 => 5 + [0, 1, 2, 8][usize::from(descriptor >> 6)], + LegacyVersion::V07 => { + let single_segment = descriptor & 0x20 != 0; + let fcs = [0, 2, 4, 8][usize::from(descriptor >> 6)]; + let window = usize::from(!single_segment); + // A single-segment frame always carries its size, one byte at least. + let short_size = usize::from(single_segment && fcs == 0); + 5 + window + [0, 1, 2, 4][usize::from(descriptor & 3)] + fcs + short_size + } + }) +} + +/// Where the `version` frame starting `src` ends, and the most it can decode +/// to: one block's maximum per block (`ZSTD_findFrameSizeInfoLegacy`). `None` +/// when `src` ends inside the frame, the only way the walk fails. +pub(crate) fn frame_size(version: LegacyVersion, src: &[u8]) -> Option<(usize, u64)> { + let mut at = header_len(version, src).ok()?; let mut blocks = 0u64; loop { - let block = Block::read(&src[at.min(src.len())..])?; + let block = Block::read(src.get(at..)?).ok()?; at += 3; let body = block.body_len(); - if body > src.len() - at.min(src.len()) { - return Err(Error::Truncated); + if body > src.len().checked_sub(at)? { + return None; } - if ends_frame(header.version, block) { + if ends_frame(version, block) { break; } at += body; blocks += 1; } - Ok((at, blocks * BLOCK_SIZE_MAX as u64)) + Some((at, blocks * BLOCK_SIZE_MAX as u64)) } /// Where decoded bytes go: an output that knows how much of the frame it has @@ -549,7 +557,7 @@ pub(crate) struct LegacyFrame<'d> { header: FrameHeader, decoder: VersionDecoder, /// The bytes holding the dictionary's content, at `content`. - dict: alloc::borrow::Cow<'d, [u8]>, + dict: &'d [u8], content: core::ops::Range, #[cfg(feature = "hash")] hash: Option, @@ -558,32 +566,25 @@ pub(crate) struct LegacyFrame<'d> { impl<'d> LegacyFrame<'d> { /// Start decoding the frame `header` describes. pub(crate) fn new(header: FrameHeader, dict: LegacyDict<'d>) -> Result, Error> { - use alloc::borrow::Cow; - let (decoder, bytes, content) = match header.version { + let bytes = dict.content_bytes(); + let (decoder, content) = match header.version { LegacyVersion::V07 => { let (decoder, content) = v07::Decoder::new(dict)?; - let bytes: &'d [u8] = match dict { - LegacyDict::None => &[], - LegacyDict::Raw(raw) => raw, - LegacyDict::Split { content, .. } => content, - }; if header.dict_id != 0 && header.dict_id != decoder.dict_id { return Err(Error::DictionaryMismatch { expected: header.dict_id, provided: decoder.dict_id, }); } - (VersionDecoder::V07(decoder), Cow::Borrowed(bytes), content) + (VersionDecoder::V07(decoder), content) } LegacyVersion::V06 => { - let whole = dict.whole(); - let (decoder, content) = v06::Decoder::new(&whole)?; - (VersionDecoder::V06(decoder), whole, content) + let (decoder, content) = v06::Decoder::new(bytes)?; + (VersionDecoder::V06(decoder), content) } LegacyVersion::V05 => { - let whole = dict.whole(); - let (decoder, content) = v05::Decoder::new(&whole)?; - (VersionDecoder::V05(decoder), whole, content) + let (decoder, content) = v05::Decoder::new(bytes)?; + (VersionDecoder::V05(decoder), content) } }; Ok(LegacyFrame { @@ -596,10 +597,6 @@ impl<'d> LegacyFrame<'d> { }) } - pub(crate) fn header(&self) -> &FrameHeader { - &self.header - } - /// Decode one block, `body` being the bytes after its header. fn block(&mut self, block: Block, body: &[u8], out: &mut O) -> Result<(), Error> { let before = out.produced(); diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs index 8e73d242..0cf5c00b 100644 --- a/zstd/src/decoding/legacy/tests.rs +++ b/zstd/src/decoding/legacy/tests.rs @@ -153,7 +153,7 @@ fn fixtures_decode_into_a_slice() { #[test] fn frame_size_matches_the_decoded_frame() { for fixture in fixtures() { - let (size, bound) = frame_size(&fixture.frame).unwrap(); + let (size, bound) = frame_size(fixture.version, &fixture.frame).unwrap(); assert_eq!(size, fixture.frame.len(), "{}", fixture.name); assert!(bound >= fixture.content.len() as u64, "{}", fixture.name); assert_eq!(bound % BLOCK_SIZE_MAX as u64, 0, "{}", fixture.name); @@ -194,26 +194,150 @@ fn a_v07_frame_naming_a_dictionary_needs_it() { } } -/// A v0.7 dictionary split by the RFC 8878 parser primes the decoder as its -/// raw bytes do. +/// A v0.7 dictionary read by the RFC 8878 parser primes the decoder as its +/// raw bytes do, and a raw-content one gives its content and its ID. #[cfg(feature = "std")] #[test] -fn a_split_v07_dictionary_decodes_like_its_bytes() { - for fixture in fixtures() - .into_iter() - .filter(|f| f.version == LegacyVersion::V07 && f.dict.is_some()) - { +fn a_parsed_dictionary_decodes_like_its_bytes() { + for fixture in fixtures().into_iter().filter(|f| f.dict.is_some()) { let raw = fixture.dict.as_deref().unwrap(); - let parsed = crate::decoding::Dictionary::decode_dict(raw).unwrap(); - let content_start = raw.len() - parsed.dict_content.len(); - let split = LegacyDict::Split { - id: parsed.id, - entropy: &raw[8..content_start], - content: &raw[content_start..], - }; - let (decoded, _) = decode_to_vec(&fixture.frame, split).unwrap(); + let parsed = crate::decoding::Dictionary::from_serialized_or_raw_content(raw).unwrap(); + let (decoded, _) = decode_to_vec(&fixture.frame, LegacyDict::Parsed(&parsed)) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); assert!(decoded == fixture.content, "{}", fixture.name); } + // A raw-content dictionary answers to the ID it was given. + let fixture = fixtures() + .into_iter() + .find(|f| f.name.ends_with(".v07.dict.zst")) + .unwrap(); + let raw = crate::decoding::Dictionary::from_raw_content(6, vec![0; 64]).unwrap(); + assert_eq!( + decode_to_vec(&fixture.frame, LegacyDict::Parsed(&raw)).unwrap_err(), + Error::DictionaryMismatch { + expected: 7, + provided: 6 + } + ); +} + +/// `FrameDecoder::decode_all` decodes each fixture, its dictionary given as a +/// handle or registered under the ID a v0.7 frame names, and a frame between +/// RFC 8878 frames; a slice one byte short is too small. +#[cfg(feature = "std")] +#[test] +fn decode_all_decodes_the_fixtures() { + use crate::decoding::{DictionaryHandle, FrameDecoder, errors::FrameDecoderError}; + for fixture in fixtures() { + let mut out = vec![0u8; fixture.content.len()]; + let mut decoder = FrameDecoder::new(); + let written = match &fixture.dict { + None => decoder.decode_all(&fixture.frame, &mut out), + Some(raw) => { + let dict = DictionaryHandle::from_serialized_or_raw_content(raw).unwrap(); + decoder.decode_all_with_dict_handle(&fixture.frame, &mut out, &dict) + } + } + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + assert_eq!(written, fixture.content.len(), "{}", fixture.name); + assert!(out == fixture.content, "{}", fixture.name); + if let Some(short) = fixture.content.len().checked_sub(1) + && fixture.dict.is_none() + { + assert!(matches!( + FrameDecoder::new().decode_all(&fixture.frame, &mut out[..short]), + Err(FrameDecoderError::TargetTooSmall) + )); + } + } + + let fixture = fixtures() + .into_iter() + .find(|f| f.name.ends_with(".v07.dict.zst")) + .unwrap(); + let mut out = vec![0u8; fixture.content.len()]; + assert!(matches!( + FrameDecoder::new().decode_all(&fixture.frame, &mut out), + Err(FrameDecoderError::DictNotProvided { dict_id: 7 }) + )); + let mut decoder = FrameDecoder::new(); + decoder + .add_dict_from_bytes(fixture.dict.as_deref().unwrap()) + .unwrap(); + decoder.decode_all(&fixture.frame, &mut out).unwrap(); + assert!(out == fixture.content); + + let fixture = fixtures() + .into_iter() + .find(|f| f.name == "abc.txt.v06.l1.zst") + .unwrap(); + let rfc = crate::encoding::compress_to_vec( + &b"between"[..], + crate::encoding::CompressionLevel::Fastest, + ); + let mut input = rfc.clone(); + input.extend_from_slice(&fixture.frame); + input.extend_from_slice(&rfc); + let mut expected = b"between".to_vec(); + expected.extend_from_slice(&fixture.content); + expected.extend_from_slice(b"between"); + let mut out = vec![0u8; expected.len()]; + assert_eq!( + FrameDecoder::new().decode_all(&input, &mut out).unwrap(), + expected.len() + ); + assert!(out == expected); +} + +/// The frame queries answer for these frames as libzstd does: a frame, its +/// length through the end block, its declared size or unknown, and a bound +/// of 128 KiB per block; a cut frame is truncated. +#[cfg(feature = "std")] +#[test] +fn frame_queries_answer_for_the_fixtures() { + use crate::decoding::{ + FrameContentSize, FrameSizeError, find_decompressed_size, find_frame_compressed_size, + frame_decompressed_bound, is_frame, read_frame_content_size, + }; + for fixture in fixtures() { + let frame = &fixture.frame; + assert!(is_frame(frame), "{}", fixture.name); + assert_eq!( + find_frame_compressed_size(frame).unwrap(), + frame.len(), + "{}", + fixture.name + ); + let bound = frame_decompressed_bound(frame).unwrap(); + assert!(bound >= fixture.content.len() as u64, "{}", fixture.name); + let declared = declared_content_size(frame); + let expected = declared.map_or(FrameContentSize::Unknown, FrameContentSize::Known); + assert_eq!( + read_frame_content_size(frame).unwrap(), + expected, + "{}", + fixture.name + ); + assert_eq!( + find_decompressed_size(frame).unwrap(), + expected, + "{}", + fixture.name + ); + assert!(matches!( + find_frame_compressed_size(&frame[..frame.len() - 1]), + Err(FrameSizeError::Truncated) + )); + } + // v0.5 declares no size. + let v05 = fixtures() + .into_iter() + .find(|f| f.name == "abc.txt.v05.l1.zst") + .unwrap(); + assert_eq!( + read_frame_content_size(&v05.frame).unwrap(), + FrameContentSize::Unknown + ); } /// Every truncation of every fixture is refused or decodes to a prefix, and @@ -351,18 +475,45 @@ fn an_empty_block_ends_only_older_frames() { let end = [0xC0, 0, 0]; let mut v06 = vec![0x26, 0xB5, 0x2F, 0xFD, 0x00]; v06.extend_from_slice(&empty_raw); - assert_eq!(frame_size(&v06).unwrap(), (8, 0)); + assert_eq!(frame_size(LegacyVersion::V06, &v06), Some((8, 0))); let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00]; v07.extend_from_slice(&empty_raw); - assert_eq!(frame_size(&v07).unwrap_err(), Error::Truncated); + assert_eq!(frame_size(LegacyVersion::V07, &v07), None); v07.extend_from_slice(&end); - assert_eq!(frame_size(&v07).unwrap(), (12, BLOCK_SIZE_MAX as u64)); + assert_eq!( + frame_size(LegacyVersion::V07, &v07), + Some((12, BLOCK_SIZE_MAX as u64)) + ); assert_eq!( decode_to_vec(&v07, LegacyDict::None).unwrap(), (Vec::new(), 12) ); } +/// The size walk measures a header from its size fields alone, as upstream's +/// does: a reserved bit the decoder refuses does not stop the walk. +#[test] +fn frame_size_reads_only_the_size_fields() { + let end = [0xC0, 0, 0]; + // v0.6 with the reserved bit and a one-byte content size. + let mut v06 = vec![0x26, 0xB5, 0x2F, 0xFD, 0x60, 9]; + v06.extend_from_slice(&end); + assert_eq!(frame_size(LegacyVersion::V06, &v06), Some((9, 0))); + // v0.7 with the reserved bit, a two-byte dictionary ID and a window byte. + let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x0A, 0, 1, 2]; + v07.extend_from_slice(&end); + assert_eq!(frame_size(LegacyVersion::V07, &v07), Some((11, 0))); + // v0.7 single segment without a size field still carries one byte of it. + let mut v07 = vec![0x27, 0xB5, 0x2F, 0xFD, 0x20, 7]; + v07.extend_from_slice(&end); + assert_eq!(frame_size(LegacyVersion::V07, &v07), Some((9, 0))); + // v0.5 headers are five bytes whatever the descriptor says. + let mut v05 = vec![0x25, 0xB5, 0x2F, 0xFD, 0xF0]; + v05.extend_from_slice(&end); + assert_eq!(frame_size(LegacyVersion::V05, &v05), Some((8, 0))); + assert_eq!(frame_size(LegacyVersion::V05, &v05[..4]), None); +} + /// An RLE block repeats its byte, in every format. #[test] fn rle_blocks_repeat_their_byte() { diff --git a/zstd/src/decoding/legacy/v07.rs b/zstd/src/decoding/legacy/v07.rs index d9e88168..c6a843f8 100644 --- a/zstd/src/decoding/legacy/v07.rs +++ b/zstd/src/decoding/legacy/v07.rs @@ -179,6 +179,55 @@ fn load_entropy(src: &[u8], dict_size: usize) -> Result<(Decoder, usize), Error> )) } +/// The tables and offsets a dictionary of the v0.7 layout primes, taken from +/// what the RFC 8878 parser read of it: the two layouts are the same bytes, +/// so the parsed distributions and code lengths are the ones +/// `ZSTDv07_loadEntropy` would read, held to the v0.7 symbol limits. +/// +/// The repeat offsets are not held to the dictionary's size here, as the +/// parser kept no count of its table bytes; one reaching past the content +/// fails at the match that uses it. +fn parsed_entropy(dict: &crate::decoding::Dictionary) -> Result { + let bad = |_| Error::DictionaryCorrupted; + let huf_table = &dict.huf.table; + let huf = HufTable::from_code_lengths(huf_table.code_lengths(), huf_table.max_num_bits) + .map_err(bad)?; + let table = |fse: &crate::decoding::scratch::AlignedFSETable, + max: usize, + max_log: u32| + -> Result { + let log = u32::from(fse.accuracy_log); + if log > max_log { + return Err(Error::DictionaryCorrupted); + } + let dist = + Distribution::from_probabilities(&fse.symbol_probabilities, log, max).map_err(bad)?; + FseTable::new(&dist).map_err(bad) + }; + let of = table(&dict.fse.offsets, MAX_OFF, OFF_LOG)?; + let ml = table(&dict.fse.match_lengths, MAX_ML, ML_LOG)?; + let ll = table(&dict.fse.literal_lengths, MAX_LL, LL_LOG)?; + let mut reps = [0usize; 3]; + for (rep, &offset) in reps.iter_mut().zip(&dict.offset_hist) { + if offset == 0 { + return Err(Error::DictionaryCorrupted); + } + *rep = offset as usize; + } + Ok(Decoder { + huf: Some(huf), + tables: SeqTables { + ll: Some(ll), + of: Some(of), + ml: Some(ml), + }, + repeat_tables: true, + reps, + literals: Vec::new(), + dict_id: 0, + }) +} + /// The literal length, offset and match length tables last in use, which a /// block may keep instead of sending new ones. #[derive(Default)] @@ -228,17 +277,17 @@ impl Decoder { decoder.dict_id = id; Ok((decoder, 8 + len..raw.len())) } - LegacyDict::Split { - id, - entropy, - content, - } => { - let (mut decoder, len) = load_entropy(entropy, entropy.len() + content.len())?; - if len != entropy.len() { - return Err(Error::DictionaryCorrupted); + LegacyDict::Parsed(dict) => { + let content = 0..dict.dict_content.len(); + // A raw-content dictionary has no tables to load. + if dict.huf.table.max_num_bits == 0 { + let mut decoder = fresh(); + decoder.dict_id = dict.id; + return Ok((decoder, content)); } - decoder.dict_id = id; - Ok((decoder, 0..content.len())) + let mut decoder = parsed_entropy(dict)?; + decoder.dict_id = dict.id; + Ok((decoder, content)) } } } diff --git a/zstd/src/decoding/mod.rs b/zstd/src/decoding/mod.rs index 02acd7e1..93dd85bd 100644 --- a/zstd/src/decoding/mod.rs +++ b/zstd/src/decoding/mod.rs @@ -81,6 +81,11 @@ pub enum FrameContentSize { /// This backs the C `ZSTD_getFrameContentSize` entry point, where the two /// variants map to a concrete size and `ZSTD_CONTENTSIZE_UNKNOWN`. /// +/// With the `legacy` feature a v0.5-v0.7 frame reports the size its header +/// declares, and [`FrameContentSize::Unknown`] when it declares none or its +/// header does not read, as libzstd does: the field is advisory in those +/// formats. +/// /// # Errors /// Returns [`ReadFrameHeaderError`](errors::ReadFrameHeaderError) when `src` /// is too short to hold a header, carries a bad magic number, or begins with @@ -95,6 +100,11 @@ pub enum FrameContentSize { pub fn read_frame_content_size( src: &[u8], ) -> Result { + #[cfg(feature = "legacy")] + if LegacyVersion::of(src).is_some() { + return Ok(legacy::declared_content_size(src) + .map_or(FrameContentSize::Unknown, FrameContentSize::Known)); + } let (header, _consumed) = frame::read_frame_header_with_format(src, false)?; Ok(if header.fcs_declared() { FrameContentSize::Known(header.frame_content_size()) @@ -125,9 +135,10 @@ pub enum FrameSizeError { /// header, every block, and the trailing content checksum when present — /// computed by walking the block headers without decoding any block body. /// -/// For a skippable frame, returns its full `8 + Frame_Size` length. This backs -/// the C `ZSTD_findFrameCompressedSize` entry point; the returned value is the -/// offset at which a following concatenated frame would begin. +/// For a skippable frame, returns its full `8 + Frame_Size` length, and with +/// the `legacy` feature a v0.5-v0.7 frame's length through its end block. This +/// backs the C `ZSTD_findFrameCompressedSize` entry point; the returned value +/// is the offset at which a following concatenated frame would begin. /// /// # Errors /// [`FrameSizeError`] when the header is unreadable, the buffer is truncated @@ -140,6 +151,12 @@ pub enum FrameSizeError { /// assert_eq!(find_frame_compressed_size(&frame).unwrap(), frame.len()); /// ``` pub fn find_frame_compressed_size(src: &[u8]) -> Result { + #[cfg(feature = "legacy")] + if let Some(version) = LegacyVersion::of(src) { + return legacy::frame_size(version, src) + .map(|(size, _)| size) + .ok_or(FrameSizeError::Truncated); + } let (header, header_len) = match frame::read_frame_header_with_format(src, false) { Ok(parsed) => parsed, // Skippable frame: magic (4) + Frame_Size field (4) + payload. @@ -295,11 +312,20 @@ fn walk_blocks( /// Returns the exact size when the header declares `Frame_Content_Size`; /// otherwise a valid (loose) bound of `block_count * block_size_max`, where /// `block_size_max = min(window_size, 128 KiB)` — every block decompresses to -/// at most that many bytes. Skippable frames contribute `0`. +/// at most that many bytes. Skippable frames contribute `0`. With the `legacy` +/// feature a v0.5-v0.7 frame is bounded by 128 KiB per block whatever its +/// header declares, as libzstd bounds it, since those formats do not hold a +/// frame to its declared size. /// /// # Errors /// [`FrameSizeError`] on an unreadable header, truncation, or a reserved block. pub fn frame_decompressed_bound(src: &[u8]) -> Result { + #[cfg(feature = "legacy")] + if let Some(version) = LegacyVersion::of(src) { + return legacy::frame_size(version, src) + .map(|(_, bound)| bound) + .ok_or(FrameSizeError::Truncated); + } let (header, header_len) = match frame::read_frame_header_with_format(src, false) { Ok(parsed) => parsed, // Skippable frame contributes 0, but its full payload must be present: @@ -368,6 +394,25 @@ pub fn frame_magic(src: &[u8]) -> Option { } } +/// Whether `src` starts with a frame this build decodes or skips: a Zstandard +/// frame, a skippable frame, or with the `legacy` feature a v0.5-v0.7 frame. +/// Backs the C `ZSTD_isFrame`, which answers the same for upstream's default +/// build. +/// +/// ```rust +/// use structured_zstd::decoding::is_frame; +/// assert!(is_frame(&[0x28, 0xB5, 0x2F, 0xFD])); +/// assert!(is_frame(&[0x50, 0x2A, 0x4D, 0x18])); +/// assert!(!is_frame(b"zip")); +/// ``` +pub fn is_frame(src: &[u8]) -> bool { + #[cfg(feature = "legacy")] + if LegacyVersion::of(src).is_some() { + return true; + } + frame_magic(src).is_some() +} + /// Bytes the compressed input must trail the end of the output by for every /// frame in `src` to decode in place: the compressed data sits at the end of /// the output buffer and the decoder writes from its start. Backs the C diff --git a/zstd/src/huff0/huff0_decoder.rs b/zstd/src/huff0/huff0_decoder.rs index a0da9a67..b0af3c22 100644 --- a/zstd/src/huff0/huff0_decoder.rs +++ b/zstd/src/huff0/huff0_decoder.rs @@ -291,6 +291,13 @@ impl HuffmanTable { self.fse_table.reset(); } + /// Code length of every symbol the parsed description lists, 0 for a + /// symbol it leaves out, the last symbol's inferred length included. + #[cfg(feature = "legacy")] + pub(crate) fn code_lengths(&self) -> &[u8] { + &self.bits + } + /// Build the equivalent encoder-side Huffman table from parsed weights. pub(crate) fn to_encoder_table(&self) -> Option { if self.bits.is_empty() || self.max_num_bits == 0 { From 601a8df8fc38f6c876cbbc8923a2c5406126450a Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 19 Oct 2026 14:44:49 +0300 Subject: [PATCH 03/11] feat(decoding): stream v0.5-v0.7 frames through FrameDecoder - A Read-driven reset that meets a v0.5-v0.7 magic number reads the rest of that header from the same source and starts the frame on a LegacyStream; an RFC 8878 frame pays nothing, since the check sits on the bad-magic error the parser already returns. - The step interface follows: input_needed, decode_from_to, decode_blocks, collect, read, is_finished and the counters drive the stream, so StreamingDecoder and any caller of that interface decode these frames with no change of its own. The stream keeps the window plus a block of history and drops what lies out of reach once handed out. - read_to_end and concatenated frames decode them into the output vector; a forced dictionary applies as it does to RFC frames. - Dictionaries reach these frames only as parsed handles; the raw byte form and the v0.7 byte loader it needed are gone. - The bit-flip test now checks that a v0.7 frame decodes to other content only when that content matches the 22-bit checksum it carries; one fixture flip is such a collision. - simd_copy tests build their kernel list without a mut binding that a build without kernel features never pushes to. Part of #131 --- zstd/src/decoding/frame_decoder.rs | 382 ++++++++++++++++++++----- zstd/src/decoding/legacy/mod.rs | 288 +++++++++++++++++-- zstd/src/decoding/legacy/tests.rs | 251 ++++++++++++++-- zstd/src/decoding/legacy/v07.rs | 66 +---- zstd/src/decoding/simd_copy/tests.rs | 63 ++-- zstd/src/decoding/streaming_decoder.rs | 5 + 6 files changed, 849 insertions(+), 206 deletions(-) diff --git a/zstd/src/decoding/frame_decoder.rs b/zstd/src/decoding/frame_decoder.rs index 51df9403..ee4ce52d 100644 --- a/zstd/src/decoding/frame_decoder.rs +++ b/zstd/src/decoding/frame_decoder.rs @@ -219,6 +219,11 @@ pub struct FrameDecoder { /// [`Self::reset_with_active_dict`]. Only ever `Some` while `state` is /// `None`: once a state exists the dictionary lives there. armed_dict: Option, + /// The v0.5-v0.7 frame a `Read`-driven reset started, decoded step by + /// step in place of `state`. Boxed so a decoder that never meets one + /// carries a pointer, not the stream. + #[cfg(feature = "legacy")] + legacy: Option>, } /// How the decoder treats a frame's optional XXH64 content checksum @@ -884,6 +889,14 @@ impl DecoderScratchKind { } } +/// `dict` as the v0.5-v0.7 decoders take it. +#[cfg(feature = "legacy")] +fn legacy_dict(dict: Option<&DictionaryHandle>) -> decoding::legacy::LegacyDict<'_> { + dict.map_or(decoding::legacy::LegacyDict::None, |dict| { + decoding::legacy::LegacyDict::Parsed(dict.as_dict()) + }) +} + /// The dictionary of `dicts` a frame naming `dict_id` decodes with: the one /// carrying that ID, else the last, as upstream keeps the most recently /// referenced DDict when its set holds none for the frame @@ -1251,7 +1264,112 @@ impl FrameDecoder { computed_block_checksums: alloc::vec::Vec::new(), kernel: crate::cpu_kernel::detect_cpu_kernel(), armed_dict: None, + #[cfg(feature = "legacy")] + legacy: None, + } + } + + /// The v0.5-v0.7 frame in progress, if the current frame is one. + #[cfg(feature = "legacy")] + #[inline] + fn legacy_frame(&self) -> Option<&decoding::legacy::LegacyStream> { + self.legacy.as_deref().filter(|stream| stream.active()) + } + + /// [`Self::legacy_frame`], mutable. + #[cfg(feature = "legacy")] + #[inline] + fn legacy_frame_mut(&mut self) -> Option<&mut decoding::legacy::LegacyStream> { + self.legacy.as_deref_mut().filter(|stream| stream.active()) + } + + /// Leave any v0.5-v0.7 frame in progress: an RFC 8878 frame has started. + #[inline] + fn end_legacy_frame(&mut self) { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy.as_deref_mut() { + stream.end(); + } + } + + /// Read the frame header from `source` into the frame state, creating it + /// on first use. A v0.5-v0.7 frame is started instead when its magic + /// number is what the header begins with, its dictionary chosen from + /// `dicts` (the registry's when empty); `Ok(false)` reports that. + fn read_frame_header_into_state( + &mut self, + source: &mut R, + #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle], + ) -> Result { + let magicless = self.magicless; + let max_window = self.max_window_size; + let block_limit = self.max_block_size; + let parsed = match &mut self.state { + Some(s) => s.reset_with_format(&mut *source, magicless, max_window, block_limit), + None => { + FrameDecoderState::new_with_format(&mut *source, magicless, max_window, block_limit) + .map(|state| { + self.state = Some(state); + // A frame that resolves its dictionary by ID drops one armed + // for a forced-dictionary stream that never started. + self.armed_dict = None; + }) + } + }; + match parsed { + Ok(()) => { + self.end_legacy_frame(); + Ok(true) + } + #[cfg(feature = "legacy")] + Err(FrameDecoderError::ReadFrameHeaderError( + crate::decoding::errors::ReadFrameHeaderError::BadMagicNumber(magic), + )) if crate::decoding::LegacyVersion::of(&magic.to_le_bytes()).is_some() => { + self.start_legacy_frame(magic, source, dicts)?; + Ok(false) + } + Err(err) => Err(err), + } + } + + /// Start the v0.5-v0.7 frame whose magic number `magic` has been read + /// from `source`, reading the rest of its header from it. + #[cfg(feature = "legacy")] + fn start_legacy_frame( + &mut self, + magic: u32, + source: &mut R, + dicts: &[DictionaryHandle], + ) -> Result<(), FrameDecoderError> { + use crate::decoding::errors::LegacyFrameError; + use crate::decoding::legacy::{self, LegacyStream}; + let version = crate::decoding::LegacyVersion::of(&magic.to_le_bytes()) + .expect("the caller checked the magic number"); + // The longest header: magic, descriptor, window, dictionary ID and an + // eight-byte content size. + let mut bytes = [0u8; 18]; + bytes[..4].copy_from_slice(&magic.to_le_bytes()); + source + .read_exact(&mut bytes[4..5]) + .map_err(|_| LegacyFrameError::Truncated)?; + let len = legacy::header_len(version, &bytes[..5])?; + source + .read_exact(&mut bytes[5..len]) + .map_err(|_| LegacyFrameError::Truncated)?; + let header = legacy::read_header(&bytes[..len])?; + if header.window > self.max_window_size { + return Err(FrameDecoderError::WindowSizeTooBig { + requested: header.window, + limit: self.max_window_size, + }); } + // The stream outlives this call, so it holds the handle: one count, + // once per frame. + let dict = self.legacy_frame_dict(header.dict_id, dicts)?.cloned(); + self.legacy + .get_or_insert_with(|| alloc::boxed::Box::new(LegacyStream::new())) + .start(header, dict)?; + Ok(()) } /// Decode with a named CPU tier instead of the one this machine selected. @@ -1281,9 +1399,16 @@ impl FrameDecoder { /// `ZSTD_sizeof_DCtx`. Shared dictionaries (ref-counted handles) are not /// counted, matching upstream excluding `refDDict` memory. pub fn workspace_size(&self) -> usize { - self.state + let frame = self + .state .as_ref() - .map_or(0, |s| s.decoder_scratch.workspace_bytes()) + .map_or(0, |s| s.decoder_scratch.workspace_bytes()); + #[cfg(feature = "legacy")] + let frame = frame + + self.legacy.as_ref().map_or(0, |stream| { + core::mem::size_of::() + stream.heap_bytes() + }); + frame } /// Select how the frame's optional content checksum is handled @@ -1567,6 +1692,11 @@ impl FrameDecoder { /// content checksum is still to be read, as /// [`Self::read_content_checksum`] takes it. pub fn awaits_checksum(&self) -> bool { + // A v0.7 checksum rides in the end block, read as a block. + #[cfg(feature = "legacy")] + if self.legacy_frame().is_some() { + return false; + } self.state.as_ref().is_some_and(|state| { state.frame_finished && state.frame_header.descriptor.content_checksum_flag() @@ -1596,6 +1726,10 @@ impl FrameDecoder { /// assert!(decoder.input_needed(source) <= source.len()); /// ``` pub fn input_needed(&self, pending: &[u8]) -> usize { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.input_needed(pending); + } let Some(state) = self.state.as_ref() else { return 0; }; @@ -1638,6 +1772,11 @@ impl FrameDecoder { header: &[u8; 3], reserve_window: bool, ) -> Result, FrameDecoderError> { + // A v0.5-v0.7 block is decoded whole, as its step. + #[cfg(feature = "legacy")] + if self.legacy_frame().is_some() { + return Ok(None); + } // Before the window is reserved below. self.check_window_still_admitted()?; let kernel = self.kernel; @@ -2067,36 +2206,20 @@ impl FrameDecoder { /// /// equivalent to init() #[inline] - pub fn reset(&mut self, source: impl Read) -> Result<(), FrameDecoderError> { + pub fn reset(&mut self, mut source: impl Read) -> Result<(), FrameDecoderError> { use FrameDecoderError as err; // Fresh frame → start with an empty per-block checksum vec so // the values for the next frame don't carry over from the // previous one. #[cfg(all(feature = "lsm", feature = "hash"))] self.computed_block_checksums.clear(); - let magicless = self.magicless; - let max_window = self.max_window_size; - let block_limit = self.max_block_size; - let dict_id = match &mut self.state { - Some(s) => { - s.reset_with_format(source, magicless, max_window, block_limit)?; - s.frame_header.dictionary_id() - } - None => { - self.state = Some(FrameDecoderState::new_with_format( - source, - magicless, - max_window, - block_limit, - )?); - // A frame that resolves its dictionary by ID drops one armed - // for a forced-dictionary stream that never started. - self.armed_dict = None; - self.state - .as_ref() - .and_then(|state| state.frame_header.dictionary_id()) - } - }; + if !self.read_frame_header_into_state(&mut source, &[])? { + return Ok(()); + } + let dict_id = self + .state + .as_ref() + .and_then(|state| state.frame_header.dictionary_id()); // Validate any pinned expectations BEFORE block decode work // runs. Catches dict_id substitution / window-descriptor // tampering on inputs already authenticated by an outer @@ -2150,6 +2273,7 @@ impl FrameDecoder { let max_window = crate::common::MAX_DECODER_WINDOW_SIZE; let block_limit = self.max_block_size; let (frame_header, header_size) = frame::read_frame_header_from_slice(input, magicless)?; + self.end_legacy_frame(); let dict_id = match &mut self.state { Some(s) => { s.reset_with_parsed_header(frame_header, header_size, max_window, block_limit)?; @@ -2257,11 +2381,12 @@ impl FrameDecoder { if dicts.is_empty() { return self.reset(source); } - let dict = self.reset_frame_for_dicts(source, dicts)?; - self.state - .as_mut() - .expect("state populated by reset_frame_for_dicts") - .set_active_dict(dict); + if let Some(dict) = self.reset_frame_for_dicts(source, dicts)? { + self.state + .as_mut() + .expect("state populated by reset_frame_for_dicts") + .set_active_dict(dict); + } Ok(()) } @@ -2315,12 +2440,13 @@ impl FrameDecoder { /// Everything [`reset_with_dict_handle`](Self::reset_with_dict_handle) /// does except storing the handle: parse the header, pick the dictionary /// of `dicts` the frame decodes with ([`dictionary_for_frame`]), check the - /// ID, and point the entropy tables at it. `dicts` is not empty. + /// ID, and point the entropy tables at it. `dicts` is not empty. `None` + /// when the frame is a v0.5-v0.7 one, which took its dictionary itself. fn reset_frame_for_dicts<'d>( &mut self, - source: impl Read, + mut source: impl Read, dicts: &'d [DictionaryHandle], - ) -> Result<&'d DictionaryHandle, FrameDecoderError> { + ) -> Result, FrameDecoderError> { use FrameDecoderError as err; // Fresh frame → drop the previous frame's per-block checksum // digests so the next decode starts with an empty vec. @@ -2331,24 +2457,8 @@ impl FrameDecoder { for dict in dicts { Self::validate_dictionary_content(dict.as_dict())?; } - let magicless = self.magicless; - let max_window = self.max_window_size; - let block_limit = self.max_block_size; - // Scope the &mut borrow of `self.state` to the header parse - // alone, so the subsequent `validate_expectations(&self, ...)` - // call below can take a fresh shared borrow of self without - // tripping the borrow checker. - match &mut self.state { - Some(s) => s.reset_with_format(source, magicless, max_window, block_limit)?, - None => { - self.state = Some(FrameDecoderState::new_with_format( - source, - magicless, - max_window, - block_limit, - )?); - self.armed_dict = None; - } + if !self.read_frame_header_into_state(&mut source, dicts)? { + return Ok(None); } // Single source of truth: route through the same // `validate_expectations` used by `reset()`. Routing through @@ -2379,7 +2489,7 @@ impl FrameDecoder { } state.decoder_scratch.init_from_dict(dict); state.using_dict = Some(dict.id()); - Ok(dict) + Ok(Some(dict)) } /// Slice-direct equivalent of [`reset_with_dict_handle`](Self::reset_with_dict_handle) @@ -2415,6 +2525,7 @@ impl FrameDecoder { let max_window = crate::common::MAX_DECODER_WINDOW_SIZE; let block_limit = self.max_block_size; let (frame_header, header_size) = frame::read_frame_header_from_slice(input, magicless)?; + self.end_legacy_frame(); match &mut self.state { Some(s) => { s.reset_with_parsed_header(frame_header, header_size, max_window, block_limit)?; @@ -2586,6 +2697,12 @@ impl FrameDecoder { /// assert_eq!(decoder.collect().unwrap(), b"declared and delivered"); /// ``` pub fn verify_content_size(&self) -> Result<(), FrameDecoderError> { + // A v0.5-v0.7 frame's declared size is not held against its output: + // `LegacyFrame::finish` says why. + #[cfg(feature = "legacy")] + if self.legacy_frame().is_some() { + return Ok(()); + } let Some(state) = self.state.as_ref() else { return Ok(()); }; @@ -2615,6 +2732,11 @@ impl FrameDecoder { if self.content_checksum != ContentChecksum::Verify { return Ok(()); } + // A v0.7 checksum is checked as its end block is read. + #[cfg(feature = "legacy")] + if self.legacy_frame().is_some() { + return Ok(()); + } let Some(state) = self.state.as_ref() else { return Ok(()); }; @@ -2636,6 +2758,10 @@ impl FrameDecoder { /// Bytes the current frame has decoded to so far. pub(crate) fn decoded_size(&self) -> u64 { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.produced(); + } self.state .as_ref() .map_or(0, |state| state.decoder_scratch.total_output()) @@ -2643,6 +2769,10 @@ impl FrameDecoder { /// Counter for how many bytes have been consumed while decoding the frame pub fn bytes_read_from_source(&self) -> u64 { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.consumed(); + } let state = match &self.state { None => return 0, Some(s) => s, @@ -2686,6 +2816,10 @@ impl FrameDecoder { /// If this returns true you can call the drain* functions to get all content /// (the read() function will drain automatically if this returns true) pub fn is_finished(&self) -> bool { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.is_finished(); + } let state = match &self.state { None => return true, Some(s) => s, @@ -2716,6 +2850,26 @@ impl FrameDecoder { strat: BlockDecodingStrategy, ) -> Result { use FrameDecoderError as err; + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + let produced_before = stream.produced(); + let mut blocks = 0usize; + while !stream.is_finished() { + stream.read_step(&mut source)?; + blocks += 1; + let enough = match strat { + BlockDecodingStrategy::All => false, + BlockDecodingStrategy::UptoBlocks(n) => blocks >= n, + BlockDecodingStrategy::UptoBytes(n) => { + stream.produced() - produced_before >= n as u64 + } + }; + if enough { + break; + } + } + return Ok(stream.is_finished()); + } self.check_window_still_admitted()?; // Apply the content-checksum mode to the streaming drain hash before // any block decodes into the ring. Hash only when a digest is both @@ -3252,6 +3406,12 @@ impl FrameDecoder { /// decoding the window stays inside as history, already handed out; once /// it is finished ([`Self::is_finished`]) the buffer is emptied. pub fn collect(&mut self) -> Option> { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + let out = stream.undelivered_bytes().to_vec(); + stream.mark_delivered(out.len()); + return Some(out); + } let finished = self.is_finished(); let state = self.state.as_mut()?; if finished { @@ -3263,6 +3423,14 @@ impl FrameDecoder { /// [`Self::collect`] into `w`. pub fn collect_to_writer(&mut self, w: impl Write) -> Result { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + let mut w = w; + let len = stream.undelivered(); + w.write_all(stream.undelivered_bytes())?; + stream.mark_delivered(len); + return Ok(len); + } let finished = self.is_finished(); let state = match &mut self.state { None => return Ok(0), @@ -3278,6 +3446,10 @@ impl FrameDecoder { /// How many decoded bytes are waiting to be collected: every byte decoded /// and not collected yet, whether or not the frame is finished. pub fn can_collect(&self) -> usize { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.undelivered(); + } self.state .as_ref() .map_or(0, |state| state.decoder_scratch.buffer_undelivered()) @@ -3335,6 +3507,10 @@ impl FrameDecoder { reserve_window: bool, ) -> Result<(usize, usize), FrameDecoderError> { use FrameDecoderError as err; + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + return Ok(stream.decode(source, target)?); + } self.check_window_still_admitted()?; let bytes_read_at_start = match &self.state { Some(s) => s.bytes_read_counter, @@ -3725,6 +3901,11 @@ impl FrameDecoder { /// [`Self::decode_current_frame_to_vec`] can decode the rest of the frame /// straight from the remaining source bytes. pub(crate) fn is_at_frame_start(&self) -> bool { + // A v0.5-v0.7 frame decodes through its stream, a block at a time. + #[cfg(feature = "legacy")] + if self.legacy_frame().is_some() { + return false; + } self.state .as_ref() .is_some_and(|s| s.block_counter == 0 && !s.frame_finished) @@ -3784,6 +3965,11 @@ impl FrameDecoder { ) -> Result { let start_len = output.len(); while !input.is_empty() { + #[cfg(feature = "legacy")] + if !self.magicless && crate::decoding::LegacyVersion::of(input).is_some() { + self.decode_legacy_frame_to_vec(input, output, keep_dictionary)?; + continue; + } let init_result = if keep_dictionary { self.reset_with_active_dict(&mut *input) } else { @@ -3941,30 +4127,86 @@ impl FrameDecoder { dicts: &[DictionaryHandle], ) -> Result { use crate::decoding::errors::LegacyFrameError; - use crate::decoding::legacy::{self, LegacyDict, SliceOutput}; + use crate::decoding::legacy::{self, SliceOutput}; let header = legacy::read_header(input)?; - let named = (header.dict_id != 0).then_some(header.dict_id); - let dict = match (named, dicts.is_empty()) { - (_, false) => Some(dictionary_for_frame(dicts, named)), - (Some(dict_id), true) => Some( - self.registered_dict(dict_id) - .ok_or(FrameDecoderError::DictNotProvided { dict_id })?, - ), - (None, true) => None, - }; - let dict = dict.map_or(LegacyDict::None, |dict| LegacyDict::Parsed(dict.as_dict())); + let dict = self.legacy_frame_dict(header.dict_id, dicts)?; let mut out = SliceOutput { buf: output, pos: 0, }; - let consumed = legacy::decode_frame(input, dict, &mut out).map_err(|err| match err { - LegacyFrameError::OutputTooSmall => FrameDecoderError::TargetTooSmall, - err => err.into(), - })?; + let consumed = + legacy::decode_frame(input, legacy_dict(dict), &mut out).map_err(|err| match err { + LegacyFrameError::OutputTooSmall => FrameDecoderError::TargetTooSmall, + err => err.into(), + })?; *input = &input[consumed..]; Ok(out.pos) } + /// Decode the v0.5-v0.7 frame starting `*input`, appending to `output` + /// and advancing `*input` past it. With `keep_dictionary` it takes the + /// dictionary the decoder holds, else the registry's. + #[cfg(feature = "legacy")] + fn decode_legacy_frame_to_vec( + &self, + input: &mut &[u8], + output: &mut Vec, + keep_dictionary: bool, + ) -> Result<(), FrameDecoderError> { + use crate::decoding::legacy::{self, VecOutput}; + let held = if keep_dictionary { + self.state + .as_ref() + .and_then(|state| state.active_dict.as_ref()) + .or(self.armed_dict.as_ref()) + } else { + None + }; + let dicts = held.map_or(&[][..], core::slice::from_ref); + let header = legacy::read_header(input)?; + let dict = self.legacy_frame_dict(header.dict_id, dicts)?; + let start = output.len(); + let mut out = VecOutput { + buf: output, + start, + dropped: 0, + limit: usize::MAX, + }; + let decoded = legacy::decode_frame(input, legacy_dict(dict), &mut out); + match decoded { + Ok(consumed) => { + *input = &input[consumed..]; + Ok(()) + } + Err(err) => { + // No byte of a frame that failed is handed out. + output.truncate(start); + Err(err.into()) + } + } + } + + /// The dictionary a v0.5-v0.7 frame naming `dict_id` (0 for none) + /// decodes with: the one of `dicts` with that ID, else the last, which + /// then fails the frame's ID check; with no `dicts`, the one registered + /// under the ID. + #[cfg(feature = "legacy")] + fn legacy_frame_dict<'s>( + &'s self, + dict_id: u32, + dicts: &'s [DictionaryHandle], + ) -> Result, FrameDecoderError> { + let named = (dict_id != 0).then_some(dict_id); + Ok(match (named, dicts.is_empty()) { + (_, false) => Some(dictionary_for_frame(dicts, named)), + (Some(dict_id), true) => Some( + self.registered_dict(dict_id) + .ok_or(FrameDecoderError::DictNotProvided { dict_id })?, + ), + (None, true) => None, + }) + } + /// Default-feature decode_all_impl: no visitor parameter so the /// no-lsm build's call surface and codegen are byte-identical to /// the pre-#172 implementation. Compiles only when `lsm` is OFF. @@ -4752,6 +4994,10 @@ impl FrameDecoder { /// this will retain window_size bytes, else it will drain it completely impl Read for FrameDecoder { fn read(&mut self, target: &mut [u8]) -> Result { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + return Ok(stream.hand_out(target)); + } let state = match &mut self.state { None => return Ok(0), Some(s) => s, diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index f2163983..e4bb3b8a 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -63,9 +63,6 @@ fn corrupt(reason: &'static str) -> Error { #[derive(Clone, Copy)] pub(crate) enum LegacyDict<'a> { None, - /// The bytes as the caller supplied them; each format reads its own - /// layout from them. - Raw(&'a [u8]), /// A dictionary the RFC 8878 parser has read. Its layout is the v0.7 one, /// so a v0.7 frame takes its tables, repeat offsets and ID; v0.5 and v0.6 /// read any dictionary of the v0.7 layout as plain content, and are given @@ -81,10 +78,9 @@ impl<'a> LegacyDict<'a> { /// the end of those bytes: a match reaching past it into the header /// fails as reaching before the dictionary, rather than decoding to /// other bytes. - fn content_bytes(&self) -> &'a [u8] { + pub(crate) fn content_bytes(&self) -> &'a [u8] { match *self { LegacyDict::None => &[], - LegacyDict::Raw(bytes) => bytes, LegacyDict::Parsed(dict) => &dict.dict_content, } } @@ -102,7 +98,9 @@ pub(crate) struct FrameHeader { pub(crate) content_size: Option, /// The dictionary the frame names, 0 for none. pub(crate) dict_id: u32, - /// Whether a v0.7 frame carries a content checksum. + /// Whether a v0.7 frame carries a content checksum. Read only where the + /// checksum can be computed, under `hash`; the header parses it always. + #[cfg_attr(not(feature = "hash"), allow(dead_code))] pub(crate) checksum: bool, } @@ -168,7 +166,7 @@ fn ends_frame(version: LegacyVersion, block: Block) -> bool { /// The length of the frame header at the start of `src` from its size fields /// alone, its other fields unchecked, as upstream's size walks read it /// (`ZSTDv0{6,7}_frameHeaderSize`; v0.5 headers are always 5 bytes). -fn header_len(version: LegacyVersion, src: &[u8]) -> Result { +pub(crate) fn header_len(version: LegacyVersion, src: &[u8]) -> Result { let descriptor = *src.get(4).ok_or(Error::Truncated)?; Ok(match version { LegacyVersion::V05 => 5, @@ -223,6 +221,7 @@ pub(crate) trait Output { fn copy_back(&mut self, offset: usize, len: usize); /// The bytes produced since `from` (a value of `produced()`), for the /// content checksum. + #[cfg(feature = "hash")] fn since(&self, from: usize) -> &[u8]; } @@ -273,6 +272,7 @@ impl Output for VecOutput<'_> { } } + #[cfg(feature = "hash")] fn since(&self, from: usize) -> &[u8] { &self.buf[self.start + (from - self.dropped)..] } @@ -322,6 +322,7 @@ impl Output for SliceOutput<'_> { self.pos += len; } + #[cfg(feature = "hash")] fn since(&self, from: usize) -> &[u8] { &self.buf[from..self.pos] } @@ -546,26 +547,28 @@ impl Output for Budget<'_, O> { fn copy_back(&mut self, offset: usize, len: usize) { self.out.copy_back(offset, len); } + #[cfg(feature = "hash")] fn since(&self, from: usize) -> &[u8] { self.out.since(from) } } /// A frame being decoded block by block: the whole-buffer and the streaming -/// decoders both drive one. -pub(crate) struct LegacyFrame<'d> { - header: FrameHeader, +/// decoders both drive one. The dictionary it was started with is passed to +/// each block again, as [`LegacyDict::content_bytes`], so the frame holds no +/// borrow and a stream can keep it beside the dictionary's handle. +pub(crate) struct LegacyFrame { + version: LegacyVersion, decoder: VersionDecoder, - /// The bytes holding the dictionary's content, at `content`. - dict: &'d [u8], + /// Where the dictionary's content lies within its bytes. content: core::ops::Range, #[cfg(feature = "hash")] hash: Option, } -impl<'d> LegacyFrame<'d> { +impl LegacyFrame { /// Start decoding the frame `header` describes. - pub(crate) fn new(header: FrameHeader, dict: LegacyDict<'d>) -> Result, Error> { + pub(crate) fn new(header: FrameHeader, dict: LegacyDict<'_>) -> Result { let bytes = dict.content_bytes(); let (decoder, content) = match header.version { LegacyVersion::V07 => { @@ -588,23 +591,29 @@ impl<'d> LegacyFrame<'d> { } }; Ok(LegacyFrame { - header, + version: header.version, decoder, - dict: bytes, content, #[cfg(feature = "hash")] hash: header.checksum.then(|| twox_hash::XxHash64::with_seed(0)), }) } - /// Decode one block, `body` being the bytes after its header. - fn block(&mut self, block: Block, body: &[u8], out: &mut O) -> Result<(), Error> { + /// Decode one block, `body` being the bytes after its header, with the + /// bytes of the dictionary the frame was started with. + fn block( + &mut self, + block: Block, + body: &[u8], + out: &mut O, + dict_bytes: &[u8], + ) -> Result<(), Error> { let before = out.produced(); let mut out = Budget { end: before + BLOCK_SIZE_MAX, out, }; - let dict = &self.dict[self.content.clone()]; + let dict = &dict_bytes[self.content.clone()]; match block { Block::Compressed(_) => match &mut self.decoder { VersionDecoder::V05(decoder) => decoder.block(body, &mut out, dict)?, @@ -670,6 +679,7 @@ pub(crate) fn decode_frame( ) -> Result { let header = read_header(src)?; let mut frame = LegacyFrame::new(header, dict)?; + let dict_bytes = dict.content_bytes(); let mut at = header.size; loop { let block_header = src.get(at..at + 3).ok_or(Error::Truncated)?; @@ -681,10 +691,248 @@ pub(crate) fn decode_frame( frame.finish(end)?; return Ok(at); } - frame.block(block, body, out)?; + frame.block(block, body, out, dict_bytes)?; at += body.len(); } } +/// A v0.5-v0.7 frame decoded from input that arrives in pieces, a block at a +/// time, its output handed out as each block completes. It holds the window +/// the formats reach back into plus one block, as upstream's buffered +/// decoders (`ZBUFFv0{5,6,7}`) size theirs, and drops older output once it +/// has been handed out. +pub(crate) struct LegacyStream { + /// The frame in progress; `None` when no frame is. + frame: Option, + /// The dictionary the frame was started with. + dict: Option, + /// Output still within reach of a match, and output not handed out yet. + buf: Vec, + /// Bytes of the frame dropped from the front of `buf`. + dropped: usize, + /// How much of `buf` has been handed out. + delivered: usize, + /// History kept for matches: the window and one block. + keep: usize, + /// Whether the frame's end block has been read. + finished: bool, + /// Bytes of the frame read so far, its header included. + consumed: u64, + /// A block read from a `Read` source, held while it decodes. + block: Vec, +} + +impl LegacyStream { + pub(crate) const fn new() -> LegacyStream { + LegacyStream { + frame: None, + dict: None, + buf: Vec::new(), + dropped: 0, + delivered: 0, + keep: 0, + finished: false, + consumed: 0, + block: Vec::new(), + } + } + + /// Start the frame `header` describes, with `dict`. A frame already in + /// progress is dropped. + pub(crate) fn start( + &mut self, + header: FrameHeader, + dict: Option, + ) -> Result<(), Error> { + let legacy_dict = dict + .as_ref() + .map_or(LegacyDict::None, |dict| LegacyDict::Parsed(dict.as_dict())); + self.frame = Some(LegacyFrame::new(header, legacy_dict)?); + self.dict = dict; + self.buf.clear(); + self.dropped = 0; + self.delivered = 0; + // Every header reader holds the window to its format's largest, at + // most 128 MiB, which every target addresses. + debug_assert!(header.window <= 1 << 27); + self.keep = header.window as usize + BLOCK_SIZE_MAX; + self.finished = false; + self.consumed = header.size as u64; + Ok(()) + } + + /// Leave the frame in progress, if any, keeping the buffer's capacity. + pub(crate) fn end(&mut self) { + self.frame = None; + self.dict = None; + self.buf.clear(); + self.delivered = 0; + self.finished = false; + } + + /// Whether a frame is in progress, or finished with output still to hand + /// out. + pub(crate) fn active(&self) -> bool { + self.frame.is_some() + } + + /// Whether the frame's end block has been read. + pub(crate) fn is_finished(&self) -> bool { + self.finished + } + + /// Decoded bytes not handed out yet. + pub(crate) fn undelivered(&self) -> usize { + self.buf.len() - self.delivered + } + + /// Bytes the frame has decoded to so far. + pub(crate) fn produced(&self) -> u64 { + (self.dropped + self.buf.len()) as u64 + } + + /// Bytes of the frame read so far, its header included. + pub(crate) fn consumed(&self) -> u64 { + self.consumed + } + + /// Heap bytes the stream holds. + pub(crate) fn heap_bytes(&self) -> usize { + self.buf.capacity() + self.block.capacity() + } + + /// How many bytes from the start of `pending` the next step needs: a + /// block header, then the whole block; 0 once the frame is finished. + pub(crate) fn input_needed(&self, pending: &[u8]) -> usize { + if self.finished { + return 0; + } + match Block::read(pending) { + Ok(block) => 3 + block.body_len(), + Err(_) => 3, + } + } + + /// Decode the whole steps `src` holds into `target`, a block at a time, + /// decoding no further while `target` cannot take what is decoded. + /// Returns the bytes read and written. + pub(crate) fn decode( + &mut self, + src: &[u8], + target: &mut [u8], + ) -> Result<(usize, usize), Error> { + let mut read = 0; + let mut written = 0; + loop { + written += self.hand_out(&mut target[written..]); + if self.undelivered() > 0 || self.finished { + break; + } + let rest = &src[read..]; + let need = self.input_needed(rest); + if need > rest.len() { + break; + } + self.step(&rest[..need])?; + read += need; + } + Ok((read, written)) + } + + /// Hand out what `target` takes of the undelivered output. + pub(crate) fn hand_out(&mut self, target: &mut [u8]) -> usize { + let n = self.undelivered().min(target.len()); + target[..n].copy_from_slice(&self.buf[self.delivered..self.delivered + n]); + self.delivered += n; + n + } + + /// The undelivered output, which [`Self::mark_delivered`] then hands out. + pub(crate) fn undelivered_bytes(&self) -> &[u8] { + &self.buf[self.delivered..] + } + + /// Count the first `n` undelivered bytes as handed out. + pub(crate) fn mark_delivered(&mut self, n: usize) { + debug_assert!(n <= self.undelivered()); + self.delivered += n; + } + + /// Read the next block from `source` and decode it. + pub(crate) fn read_step( + &mut self, + source: &mut R, + ) -> Result<(), crate::decoding::errors::FrameDecoderError> { + use crate::blocks::block::BlockType; + use crate::decoding::errors::{ + BlockHeaderReadError, DecodeBlockContentError, FrameDecoderError, + }; + // Lent out while the block decodes, which needs `self`. + let mut block = core::mem::take(&mut self.block); + block.clear(); + block.resize(3, 0); + let result = source + .read_exact(&mut block) + .map_err(|source| { + FrameDecoderError::FailedToReadBlockHeader(BlockHeaderReadError::ReadError(source)) + }) + .and_then(|()| { + let need = self.input_needed(&block); + block.resize(need, 0); + source.read_exact(&mut block[3..]).map_err(|source| { + FrameDecoderError::FailedToReadBlockBody(DecodeBlockContentError::ReadError { + step: BlockType::Compressed, + source, + }) + }) + }) + .and_then(|()| Ok(self.step(&block)?)); + self.block = block; + result + } + + /// Decode one block, `step` holding its header and body. + fn step(&mut self, step: &[u8]) -> Result<(), Error> { + let LegacyStream { + frame, + dict, + buf, + dropped, + delivered, + keep, + finished, + consumed, + block: _, + } = self; + let frame = frame.as_mut().ok_or(corrupt("no frame in progress"))?; + let block = Block::read(step)?; + *consumed += step.len() as u64; + if ends_frame(frame.version, block) { + frame.finish([step[0], step[1], step[2]])?; + *finished = true; + return Ok(()); + } + // Drop history out of reach once it has been handed out, a block's + // worth at a time so the move is paid once per block at most. + if buf.len() > *keep + BLOCK_SIZE_MAX { + let drop = (buf.len() - *keep).min(*delivered); + buf.drain(..drop); + *delivered -= drop; + *dropped += drop; + } + let dict_bytes = dict + .as_ref() + .map_or(LegacyDict::None, |dict| LegacyDict::Parsed(dict.as_dict())) + .content_bytes(); + let mut out = VecOutput { + buf, + start: 0, + dropped: *dropped, + limit: usize::MAX, + }; + frame.block(block, &step[3..], &mut out, dict_bytes) + } +} + #[cfg(test)] mod tests; diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs index 0cf5c00b..15acd3e2 100644 --- a/zstd/src/decoding/legacy/tests.rs +++ b/zstd/src/decoding/legacy/tests.rs @@ -17,9 +17,20 @@ mod fixtures { pub(super) frame: Vec, pub(super) content: Vec, pub(super) dict: Option>, + /// `dict` as the dictionary parser reads it. + pub(super) parsed: Option, pub(super) version: LegacyVersion, } + impl Fixture { + /// The fixture's dictionary as the decoders take it. + pub(super) fn legacy_dict(&self) -> LegacyDict<'_> { + self.parsed + .as_ref() + .map_or(LegacyDict::None, LegacyDict::Parsed) + } + } + fn root() -> &'static Path { Path::new(env!("CARGO_MANIFEST_DIR")) } @@ -56,11 +67,15 @@ mod fixtures { } else { root().join("decodecorpus_files").join(&stem) }; + let dict = + is_dict.then(|| std::fs::read(dir.join(format!("dictionary.{rest}"))).unwrap()); Fixture { frame: std::fs::read(dir.join(&name)).unwrap(), content: std::fs::read(&source).unwrap(), - dict: is_dict - .then(|| std::fs::read(dir.join(format!("dictionary.{rest}"))).unwrap()), + parsed: dict.as_deref().map(|raw| { + crate::decoding::Dictionary::from_serialized_or_raw_content(raw).unwrap() + }), + dict, version, name, } @@ -96,11 +111,7 @@ fn fixtures_decode_to_their_content() { "{}", fixture.name ); - let dict = fixture - .dict - .as_deref() - .map_or(LegacyDict::None, LegacyDict::Raw); - let (decoded, consumed) = decode_to_vec(&fixture.frame, dict) + let (decoded, consumed) = decode_to_vec(&fixture.frame, fixture.legacy_dict()) .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); assert_eq!(consumed, fixture.frame.len(), "{}", fixture.name); assert!( @@ -117,10 +128,7 @@ fn fixtures_decode_to_their_content() { #[test] fn fixtures_decode_into_a_slice() { for fixture in fixtures() { - let dict = fixture - .dict - .as_deref() - .map_or(LegacyDict::None, LegacyDict::Raw); + let dict = fixture.legacy_dict(); let mut buf = vec![0u8; fixture.content.len()]; let mut out = SliceOutput { buf: &mut buf, @@ -182,8 +190,9 @@ fn a_v07_frame_naming_a_dictionary_needs_it() { ); let mut other = fixture.dict.clone().unwrap(); other[4] ^= 1; + let other = crate::decoding::Dictionary::decode_dict(&other).unwrap(); assert_eq!( - decode_to_vec(&fixture.frame, LegacyDict::Raw(&other)).unwrap_err(), + decode_to_vec(&fixture.frame, LegacyDict::Parsed(&other)).unwrap_err(), Error::DictionaryMismatch { expected: 7, provided: 6 @@ -289,6 +298,154 @@ fn decode_all_decodes_the_fixtures() { assert!(out == expected); } +/// A source that hands out one byte per read. +#[cfg(feature = "std")] +struct Trickle<'a>(&'a [u8]); + +#[cfg(feature = "std")] +impl std::io::Read for Trickle<'_> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let Some((&first, rest)) = self.0.split_first() else { + return Ok(0); + }; + if buf.is_empty() { + return Ok(0); + } + buf[0] = first; + self.0 = rest; + Ok(1) + } +} + +/// `StreamingDecoder` decodes each fixture whole, and from a source that +/// gives one byte at a time into a small buffer, which drives the stream a +/// step at a time and keeps only a window of history; a stream mixing these +/// frames with RFC 8878 ones decodes in order. +#[cfg(feature = "std")] +#[test] +fn streaming_decoder_decodes_the_fixtures() { + use crate::decoding::{DictionaryHandle, StreamingDecoder}; + use std::io::Read; + for fixture in fixtures() { + let dict = fixture + .dict + .as_deref() + .map(|raw| DictionaryHandle::from_serialized_or_raw_content(raw).unwrap()); + let open = |source| match &dict { + None => StreamingDecoder::new(source), + Some(dict) => StreamingDecoder::new_with_dictionary_handle(source, dict).unwrap(), + }; + let mut whole = Vec::new(); + open(&fixture.frame[..]) + .read_to_end(&mut whole) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + assert!(whole == fixture.content, "{}", fixture.name); + + if fixture.frame.len() < 64 * 1024 { + let mut decoder = match &dict { + None => StreamingDecoder::new(Trickle(&fixture.frame)), + Some(dict) => { + StreamingDecoder::new_with_dictionary_handle(Trickle(&fixture.frame), dict) + .unwrap() + } + }; + let mut pieces = Vec::new(); + let mut buf = [0u8; 1000]; + loop { + let n = decoder + .read(&mut buf) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + if n == 0 { + break; + } + pieces.extend_from_slice(&buf[..n]); + } + assert!(pieces == fixture.content, "{} in pieces", fixture.name); + } + } + + let fixture = fixtures() + .into_iter() + .find(|f| f.name == "z000001.v07.l19.zst") + .unwrap(); + let rfc = crate::encoding::compress_to_vec( + &b"between"[..], + crate::encoding::CompressionLevel::Fastest, + ); + let mut input = rfc.clone(); + input.extend_from_slice(&fixture.frame); + input.extend_from_slice(&rfc); + input.extend_from_slice(&fixture.frame); + let mut expected = b"between".to_vec(); + expected.extend_from_slice(&fixture.content); + expected.extend_from_slice(b"between"); + expected.extend_from_slice(&fixture.content); + let mut out = Vec::new(); + StreamingDecoder::new(&input[..]) + .read_to_end(&mut out) + .unwrap(); + assert!(out == expected); + let mut out = Vec::new(); + let mut decoder = StreamingDecoder::new(&input[..]); + let mut buf = [0u8; 4096]; + loop { + let n = decoder.read(&mut buf).unwrap(); + if n == 0 { + break; + } + out.extend_from_slice(&buf[..n]); + } + assert!(out == expected); +} + +/// The block-level interface drives these frames too: `reset` starts one, +/// `decode_blocks` decodes it a block at a time and `collect` hands it out; +/// `decode_from_to` decodes it from input given in small pieces. +#[cfg(feature = "std")] +#[test] +fn frame_decoder_steps_through_the_fixtures() { + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + for fixture in fixtures().into_iter().filter(|f| f.dict.is_none()) { + let mut source = &fixture.frame[..]; + let mut decoder = FrameDecoder::new(); + decoder.reset(&mut source).unwrap(); + let mut out = Vec::new(); + while !decoder + .decode_blocks(&mut source, BlockDecodingStrategy::UptoBlocks(1)) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)) + { + out.extend(decoder.collect().unwrap()); + } + out.extend(decoder.collect().unwrap()); + assert!(source.is_empty(), "{}", fixture.name); + assert!(out == fixture.content, "{}", fixture.name); + assert_eq!( + decoder.bytes_read_from_source(), + fixture.frame.len() as u64, + "{}", + fixture.name + ); + + // `decode_from_to` with what the decoder asks for, and a target + // smaller than a block. + let mut decoder = FrameDecoder::new(); + let mut source = &fixture.frame[..]; + decoder.reset(&mut source).unwrap(); + let mut out = Vec::new(); + let mut target = [0u8; 777]; + while !(decoder.is_finished() && decoder.can_collect() == 0) { + let need = decoder.input_needed(source).min(source.len()); + let (read, written) = decoder + .decode_from_to(&source[..need], &mut target) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + source = &source[read..]; + out.extend_from_slice(&target[..written]); + assert!(read > 0 || written > 0, "{} stalled", fixture.name); + } + assert!(out == fixture.content, "{}", fixture.name); + } +} + /// The frame queries answer for these frames as libzstd does: a frame, its /// length through the end block, its declared size or unknown, and a bound /// of 128 KiB per block; a cut frame is truncated. @@ -346,10 +503,7 @@ fn frame_queries_answer_for_the_fixtures() { #[test] fn truncated_frames_are_refused() { for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 4096) { - let dict = fixture - .dict - .as_deref() - .map_or(LegacyDict::None, LegacyDict::Raw); + let dict = fixture.legacy_dict(); for len in 0..fixture.frame.len() { assert!( decode_to_vec(&fixture.frame[..len], dict).is_err(), @@ -361,21 +515,35 @@ fn truncated_frames_are_refused() { } /// Flipping any one bit of a frame is refused or decodes to some output, but -/// never panics; in a v0.7 frame with a checksum a change to the content is -/// caught. +/// never panics; a v0.7 frame with a checksum decodes to other content only +/// when that content's checksum is the one the frame carries, which with 22 +/// bits of it does happen. #[cfg(feature = "std")] #[test] fn damaged_frames_never_panic() { for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 4096) { - let dict = fixture - .dict - .as_deref() - .map_or(LegacyDict::None, LegacyDict::Raw); + let dict = fixture.legacy_dict(); let mut frame = fixture.frame.clone(); for byte in 0..frame.len() { for bit in 0..8 { frame[byte] ^= 1 << bit; - let _ = decode_to_vec(&frame, dict); + let decoded = decode_to_vec(&frame, dict); + #[cfg(feature = "hash")] + if let Ok((content, end)) = &decoded + && *content != fixture.content + && read_header(&frame).is_ok_and(|header| header.checksum) + { + let carried = u64::from(frame[end - 1]) + | u64::from(frame[end - 2]) << 8 + | u64::from(frame[end - 3] & 0x3F) << 16; + let digest = twox_hash::XxHash64::oneshot(0, content) >> 11 & 0x3F_FFFF; + assert_eq!( + digest, carried, + "{}: bit {bit} of byte {byte} changed the content unchecked", + fixture.name + ); + } + drop(decoded); frame[byte] ^= 1 << bit; } } @@ -514,6 +682,43 @@ fn frame_size_reads_only_the_size_fields() { assert_eq!(frame_size(LegacyVersion::V05, &v05[..4]), None); } +/// A stream keeps its window and a block of history, not the whole frame: +/// a frame of a 1 KiB window and 1 MiB of blocks decodes in order while the +/// buffer stays within a few blocks. +#[test] +fn a_stream_holds_its_window_not_the_frame() { + // v0.7, window byte 0: a 1 KiB window. + let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00]; + let mut expected = Vec::new(); + for n in 0..8u8 { + let size = 128 * 1024 - 1; + // A raw block of `size` bytes of `n`. + frame.extend_from_slice(&[0x40 | (size >> 16) as u8, (size >> 8) as u8, size as u8]); + frame.extend(core::iter::repeat_n(n, size)); + expected.extend(core::iter::repeat_n(n, size)); + } + frame.extend_from_slice(&[0xC0, 0, 0]); + let header = read_header(&frame).unwrap(); + let mut stream = LegacyStream::new(); + stream.start(header, None).unwrap(); + let mut src = &frame[header.size..]; + let mut out = Vec::new(); + let mut target = vec![0u8; 50_000]; + let mut largest = 0; + while !(stream.is_finished() && stream.undelivered() == 0) { + let (read, written) = stream.decode(src, &mut target).unwrap(); + src = &src[read..]; + out.extend_from_slice(&target[..written]); + largest = largest.max(stream.heap_bytes()); + assert!(read > 0 || written > 0); + } + assert!(src.is_empty()); + assert!(out == expected); + assert_eq!(stream.produced(), expected.len() as u64); + assert_eq!(stream.consumed(), frame.len() as u64); + assert!(largest <= 4 * BLOCK_SIZE_MAX, "held {largest} bytes"); +} + /// An RLE block repeats its byte, in every format. #[test] fn rle_blocks_repeat_their_byte() { diff --git a/zstd/src/decoding/legacy/v07.rs b/zstd/src/decoding/legacy/v07.rs index c6a843f8..19a1652a 100644 --- a/zstd/src/decoding/legacy/v07.rs +++ b/zstd/src/decoding/legacy/v07.rs @@ -12,9 +12,6 @@ use super::{ use crate::decoding::errors::LegacyFrameError as Error; use alloc::vec::Vec; -/// Magic number of a v0.7 dictionary; RFC 8878 dictionaries kept it. -pub(super) const DICT_MAGIC: u32 = 0xEC30_A437; - /// Largest window a frame may ask for (`ZSTDv07_WINDOWLOG_MAX`). const WINDOW_LOG_MAX: u32 = if usize::BITS == 32 { 25 } else { 27 }; @@ -132,53 +129,6 @@ pub(super) fn read_header(src: &[u8]) -> Result { }) } -/// The tables and offsets a dictionary primes, read as `ZSTDv07_loadEntropy` -/// reads them; `dict_size` is the size the repeat offsets are checked -/// against, the dictionary less its magic number and ID. -fn load_entropy(src: &[u8], dict_size: usize) -> Result<(Decoder, usize), Error> { - let bad = |_| Error::DictionaryCorrupted; - let (huf, mut at) = HufTable::read(src, StreamEnd::PastStart).map_err(bad)?; - let mut table = |max: usize, max_log: u32| -> Result { - let (dist, len) = super::entropy::read_distribution( - src.get(at..).ok_or(Error::DictionaryCorrupted)?, - max, - ) - .map_err(bad)?; - if dist.table_log() > max_log { - return Err(Error::DictionaryCorrupted); - } - at += len; - FseTable::new(&dist).map_err(bad) - }; - let of = table(MAX_OFF, OFF_LOG)?; - let ml = table(MAX_ML, ML_LOG)?; - let ll = table(MAX_LL, LL_LOG)?; - let reps_bytes = src.get(at..at + 12).ok_or(Error::DictionaryCorrupted)?; - let mut reps = [0usize; 3]; - for (rep, bytes) in reps.iter_mut().zip(reps_bytes.chunks_exact(4)) { - let value = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; - if value == 0 || value >= dict_size { - return Err(Error::DictionaryCorrupted); - } - *rep = value; - } - Ok(( - Decoder { - huf: Some(huf), - tables: SeqTables { - ll: Some(ll), - of: Some(of), - ml: Some(ml), - }, - repeat_tables: true, - reps, - literals: Vec::new(), - dict_id: 0, - }, - at + 12, - )) -} - /// The tables and offsets a dictionary of the v0.7 layout primes, taken from /// what the RFC 8878 parser read of it: the two layouts are the same bytes, /// so the parsed distributions and code lengths are the ones @@ -252,9 +202,8 @@ pub(super) struct Decoder { } impl Decoder { - /// A decoder primed with `dict`, and where the dictionary's content - /// lies within the bytes the returned range indexes: `dict`'s raw bytes, - /// or a split dictionary's content. + /// A decoder primed with `dict`, and where the dictionary's content lies + /// within [`LegacyDict::content_bytes`]. pub(super) fn new(dict: LegacyDict<'_>) -> Result<(Decoder, core::ops::Range), Error> { let fresh = || Decoder { huf: None, @@ -266,17 +215,6 @@ impl Decoder { }; match dict { LegacyDict::None => Ok((fresh(), 0..0)), - LegacyDict::Raw(raw) => { - if raw.len() < 8 - || u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]) != DICT_MAGIC - { - return Ok((fresh(), 0..raw.len())); - } - let id = u32::from_le_bytes([raw[4], raw[5], raw[6], raw[7]]); - let (mut decoder, len) = load_entropy(&raw[8..], raw.len() - 8)?; - decoder.dict_id = id; - Ok((decoder, 8 + len..raw.len())) - } LegacyDict::Parsed(dict) => { let content = 0..dict.dict_content.len(); // A raw-content dictionary has no tables to load. diff --git a/zstd/src/decoding/simd_copy/tests.rs b/zstd/src/decoding/simd_copy/tests.rs index 9691c206..a80477db 100644 --- a/zstd/src/decoding/simd_copy/tests.rs +++ b/zstd/src/decoding/simd_copy/tests.rs @@ -36,37 +36,38 @@ fn small_exact_copy_writes_exactly_the_run() { /// a kernel only the tier dispatch reaches would otherwise be checked only on /// the hosts that select it. fn runnable_medium_kernels() -> vec::Vec<(&'static str, CopyKernel)> { - let mut kernels: vec::Vec<(&'static str, CopyKernel)> = - vec![("copy_exact_u64", copy_exact_u64)]; - #[cfg(all( - feature = "std", - any(target_arch = "x86", target_arch = "x86_64"), - feature = "kernel-sse" - ))] - if std::arch::is_x86_feature_detected!("sse2") { - kernels.push(("copy_exact_sse2", copy_exact_sse2)); - } - #[cfg(all( - feature = "std", - any(target_arch = "x86", target_arch = "x86_64"), - feature = "kernel-avx2" - ))] - if std::arch::is_x86_feature_detected!("avx2") { - kernels.push(("copy_exact_avx2", copy_exact_avx2)); - } - #[cfg(all( - target_arch = "aarch64", - target_feature = "neon", - feature = "kernel-neon" - ))] - kernels.push(("copy_exact_neon", copy_exact_neon)); - #[cfg(all( - target_arch = "wasm32", - target_feature = "simd128", - feature = "kernel-simd128" - ))] - kernels.push(("copy_exact_simd128", copy_exact_simd128)); - kernels + // Which entries exist depends on the build's kernel features, so the list + // is one array with each entry under its own `cfg`. + let kernels: &[Option<(&'static str, CopyKernel)>] = &[ + Some(("copy_exact_u64", copy_exact_u64)), + #[cfg(all( + feature = "std", + any(target_arch = "x86", target_arch = "x86_64"), + feature = "kernel-sse" + ))] + std::arch::is_x86_feature_detected!("sse2") + .then_some(("copy_exact_sse2", copy_exact_sse2 as CopyKernel)), + #[cfg(all( + feature = "std", + any(target_arch = "x86", target_arch = "x86_64"), + feature = "kernel-avx2" + ))] + std::arch::is_x86_feature_detected!("avx2") + .then_some(("copy_exact_avx2", copy_exact_avx2 as CopyKernel)), + #[cfg(all( + target_arch = "aarch64", + target_feature = "neon", + feature = "kernel-neon" + ))] + Some(("copy_exact_neon", copy_exact_neon)), + #[cfg(all( + target_arch = "wasm32", + target_feature = "simd128", + feature = "kernel-simd128" + ))] + Some(("copy_exact_simd128", copy_exact_simd128)), + ]; + kernels.iter().copied().flatten().collect() } #[test] diff --git a/zstd/src/decoding/streaming_decoder.rs b/zstd/src/decoding/streaming_decoder.rs index 61b5587f..52c40370 100644 --- a/zstd/src/decoding/streaming_decoder.rs +++ b/zstd/src/decoding/streaming_decoder.rs @@ -316,6 +316,11 @@ fn header_len(bytes: &[u8], magicless: bool) -> usize { return 8; } if magic != crate::common::MAGIC_NUM { + // A v0.5-v0.7 header: its descriptor, then the fields it names. + #[cfg(feature = "legacy")] + if let Some(version) = crate::decoding::LegacyVersion::of(bytes) { + return crate::decoding::legacy::header_len(version, bytes).unwrap_or(5); + } return 4; } 4 From 206921b34146101f58d549baa23b04789c7c6689 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 19 Oct 2026 16:27:21 +0300 Subject: [PATCH 04/11] feat(decoding): give the v0.5-v0.7 header length to callers - LegacyVersion::header_size: a frame header's length from its size fields, for a caller that reads a header in pieces, as the C ABI stream does. - LegacyFrameError::WindowTooLarge is gone: a window past the ceiling is refused as FrameDecoderError::WindowSizeTooBig, as for RFC 8878 frames, and nothing built the variant. Part of #131 --- zstd/src/decoding/errors.rs | 12 ------------ zstd/src/decoding/legacy/mod.rs | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/zstd/src/decoding/errors.rs b/zstd/src/decoding/errors.rs index 98a89360..75e1ae33 100644 --- a/zstd/src/decoding/errors.rs +++ b/zstd/src/decoding/errors.rs @@ -49,13 +49,6 @@ pub enum LegacyFrameError { /// The frame header sets a reserved bit or asks for a window the format /// does not allow. UnsupportedParameter, - /// The frame's window is past the ceiling in force. - WindowTooLarge { - /// The window the frame declares. - requested: u64, - /// The ceiling it exceeded. - limit: u64, - }, /// The frame is malformed; `reason` names the structure that is. Corrupted { /// What failed to decode. @@ -87,7 +80,6 @@ impl LegacyFrameError { pub fn code(&self) -> ErrorCode { match self { LegacyFrameError::UnsupportedParameter => ErrorCode::FrameParameterUnsupported, - LegacyFrameError::WindowTooLarge { .. } => ErrorCode::FrameParameterWindowTooLarge, LegacyFrameError::OutputTooSmall => ErrorCode::DstSizeTooSmall, LegacyFrameError::DictionaryCorrupted => ErrorCode::DictionaryCorrupted, LegacyFrameError::DictionaryMismatch { .. } => ErrorCode::DictionaryWrong, @@ -110,10 +102,6 @@ impl fmt::Display for LegacyFrameError { "the v0.5-v0.7 frame header asks for an unsupported setting" ) } - Self::WindowTooLarge { requested, limit } => write!( - f, - "the frame's window of {requested} bytes is past the ceiling of {limit}" - ), Self::Corrupted { reason } => write!(f, "corrupt v0.5-v0.7 frame: {reason}"), Self::OutputTooSmall => write!(f, "the output buffer is too small for the frame"), Self::DictionaryCorrupted => write!(f, "the dictionary could not be read"), diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index e4bb3b8a..c7d159a4 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -50,6 +50,26 @@ impl LegacyVersion { LegacyVersion::V07 => 7, } } + + /// The length of a frame header of this format starting `src`, magic + /// number included, as its size fields give it; `None` until `src` holds + /// the descriptor byte after the magic number. For a caller reading a + /// header in pieces, as [`frame_header_input_needed`] serves an RFC 8878 + /// one. + /// + /// [`frame_header_input_needed`]: crate::decoding::frame_header_input_needed + /// + /// ``` + /// use structured_zstd::decoding::LegacyVersion; + /// // v0.7, a one-byte dictionary ID and a window byte. + /// let header = [0x27, 0xB5, 0x2F, 0xFD, 0x01]; + /// let version = LegacyVersion::of(&header).unwrap(); + /// assert_eq!(version.header_size(&header[..4]), None); + /// assert_eq!(version.header_size(&header), Some(7)); + /// ``` + pub fn header_size(self, src: &[u8]) -> Option { + header_len(self, src).ok() + } } /// Most bytes one block decodes to, the formats' `BLOCKSIZE`. From 24fc9cf5ed9b3a428a841551804057d53fc2a14e Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 19 Oct 2026 21:15:50 +0300 Subject: [PATCH 05/11] feat: read v0.5-v0.7 frames through the C ABI and the command line - ZSTD_isFrame answers through the codec's is_frame, these frames included, as libzstd 1.5.7 answers by default. - ZSTD_decompressStream measures such a header by its own size fields while it arrives in pieces, then starts the frame from the same reader; the frame queries and one-shot decoders already go through the codec. - The command line asks the codec whether input is a frame, so -d, -t and -c decode these frames; -l keeps refusing them, as the reference does. - Tests on the fixtures: the frame queries, ZSTD_decompress and _usingDict, the stream in 7-byte pieces with a DDict, a stream mixing them with RFC 8878 frames, and the command line. Part of #131 --- c-api/src/frame.rs | 7 +- c-api/src/streaming.rs | 46 ++++- c-api/src/tests.rs | 1 + c-api/src/tests/legacy.rs | 267 ++++++++++++++++++++++++++ zstd/src/bin/structured-zstd/main.rs | 14 +- zstd/src/bin/structured-zstd/tests.rs | 40 ++++ 6 files changed, 351 insertions(+), 24 deletions(-) create mode 100644 c-api/src/tests/legacy.rs diff --git a/c-api/src/frame.rs b/c-api/src/frame.rs index 8223ea40..4f45bfdd 100644 --- a/c-api/src/frame.rs +++ b/c-api/src/frame.rs @@ -7,7 +7,7 @@ use core::ffi::{c_uint, c_ulonglong}; use codec::decoding::errors::ReadFrameHeaderError; use codec::decoding::{ FrameContentSize, FrameMagic, FrameSizeError, decompression_margin, find_decompressed_size, - find_frame_compressed_size, frame_decompressed_bound, frame_header_size, frame_magic, + find_frame_compressed_size, frame_decompressed_bound, frame_header_size, frame_magic, is_frame, min_decoding_buffer_size, read_frame_header_info, }; use codec::skippable::{ @@ -220,14 +220,15 @@ pub unsafe extern "C" fn ZSTD_findDecompressedSize(src: *const u8, src_size: usi } /// `unsigned ZSTD_isFrame(const void* buffer, size_t size)` — 1 when `buffer` -/// starts with a frame or skippable-frame magic number, else 0. +/// starts with a frame, skippable-frame or v0.5-v0.7 frame magic number, +/// else 0. /// /// # Safety /// `buffer` must be valid for `size` bytes (or `NULL` with `size == 0`). #[unsafe(no_mangle)] pub unsafe extern "C" fn ZSTD_isFrame(buffer: *const u8, size: usize) -> c_uint { let buffer = unsafe { in_slice(buffer, size) }; - c_uint::from(frame_magic(buffer).is_some()) + c_uint::from(is_frame(buffer)) } /// `unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size)` — 1 when diff --git a/c-api/src/streaming.rs b/c-api/src/streaming.rs index ab5b5d4d..81352337 100644 --- a/c-api/src/streaming.rs +++ b/c-api/src/streaming.rs @@ -1084,6 +1084,21 @@ fn enter_stream_frame( let take = (FRAME_HEADER_MAX - held_len).min(available.len()); view[held_len..held_len + take].copy_from_slice(&available[..take]); let view = &view[..held_len + take]; + // A v0.5-v0.7 header is measured by its own size fields; once whole, + // the decoder starts it from the same reader (upstream's + // `ZSTD_decompressStream` hands such a frame to its legacy streams). + if !dctx.magicless + && let Some(version) = codec::decoding::LegacyVersion::of(view) + { + if version + .header_size(view) + .is_some_and(|len| len <= view.len()) + { + start_stream_frame(dctx, inp, src, &held[..held_len])?; + return Ok(FrameEntry::Started); + } + return Ok(hold_header(dctx, inp, view, take)); + } match codec::decoding::read_frame_header_info(view, dctx.magicless) { Ok(_) => { start_stream_frame(dctx, inp, src, &held[..held_len])?; @@ -1105,16 +1120,7 @@ fn enter_stream_frame( | ReadFrameHeaderError::DictionaryIdReadError(_) | ReadFrameHeaderError::FrameContentSizeReadError(_), ) => { - debug_assert!(view.len() < FRAME_HEADER_MAX, "no header is longer"); - inp.pos += take; - let mut bytes = [0; FRAME_HEADER_MAX]; - bytes[..view.len()].copy_from_slice(view); - dctx.stream_entry = StreamEntry::Header { - bytes, - len: view.len(), - }; - dctx.stream_frame_done = false; - return Ok(FrameEntry::WantsInput); + return Ok(hold_header(dctx, inp, view, take)); } // A malformed header (bad magic, invalid descriptor, ...) is a // decode error: the need-more-input hint with nothing consumed @@ -1128,6 +1134,26 @@ fn enter_stream_frame( } } +/// Hold `view`, a frame header the input has not all brought yet: the `take` +/// bytes of it from the input are consumed, and the frame has begun. +fn hold_header( + dctx: &mut ZSTD_DCtx, + inp: &mut ZSTD_inBuffer, + view: &[u8], + take: usize, +) -> FrameEntry { + debug_assert!(view.len() < FRAME_HEADER_MAX, "no header is longer"); + inp.pos += take; + let mut bytes = [0; FRAME_HEADER_MAX]; + bytes[..view.len()].copy_from_slice(view); + dctx.stream_entry = StreamEntry::Header { + bytes, + len: view.len(), + }; + dctx.stream_frame_done = false; + FrameEntry::WantsInput +} + /// Start the decoder on the frame whose header is `held` followed by the /// input, taking from the input what the header still needed. fn start_stream_frame( diff --git a/c-api/src/tests.rs b/c-api/src/tests.rs index 5c08b155..2db18747 100644 --- a/c-api/src/tests.rs +++ b/c-api/src/tests.rs @@ -6,6 +6,7 @@ mod bufferless; mod cctx_params; mod experimental; +mod legacy; mod provided; mod sequences; mod streams; diff --git a/c-api/src/tests/legacy.rs b/c-api/src/tests/legacy.rs new file mode 100644 index 00000000..9ba75f55 --- /dev/null +++ b/c-api/src/tests/legacy.rs @@ -0,0 +1,267 @@ +//! v0.5-v0.7 frames through the C ABI, as libzstd 1.5.7 reads them by +//! default: the frame queries, the one-shot decoders and the stream, on the +//! frames the v0.5.1, v0.6.2 and v0.7.5 command lines wrote +//! (`zstd/legacy_frames`). + +use std::path::{Path, PathBuf}; + +use crate::attach::ZSTD_decompress_usingDict; +use crate::cdict::{ZSTD_createDDict, ZSTD_freeDDict}; +use crate::context::{ZSTD_createDCtx, ZSTD_freeDCtx}; +use crate::error::{ZSTD_ErrorCode, ZSTD_getErrorCode, ZSTD_isError}; +use crate::frame::{ZSTD_decompressBound, ZSTD_isFrame}; +use crate::simple::{ZSTD_decompress, ZSTD_findFrameCompressedSize, ZSTD_getFrameContentSize}; +use crate::streaming::{ + ZSTD_createDStream, ZSTD_decompressStream, ZSTD_freeDStream, ZSTD_inBuffer, + ZSTD_initDStream_usingDDict, ZSTD_outBuffer, +}; + +struct Fixture { + name: String, + frame: Vec, + content: Vec, + dict: Option>, +} + +fn codec_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../zstd") +} + +/// Every fixture, with the file it decodes to and the dictionary it needs. +fn fixtures() -> Vec { + let dir = codec_dir().join("legacy_frames"); + let mut names: Vec = std::fs::read_dir(&dir) + .expect("legacy_frames directory") + .map(|entry| entry.unwrap().file_name().into_string().unwrap()) + .filter(|name| name.ends_with(".zst")) + .collect(); + names.sort(); + assert!(names.len() >= 50, "fixtures missing from {}", dir.display()); + names + .into_iter() + .map(|name| { + let (stem, tag) = ["v05", "v06", "v07"] + .iter() + .find_map(|tag| { + name.split_once(&format!(".{tag}.")) + .map(|(stem, _)| (stem.to_string(), *tag)) + }) + .expect("fixture name carries its version"); + let is_dict = name.contains(".dict"); + let source = if is_dict { + codec_dir().join("dict_tests/files").join(&stem) + } else { + codec_dir().join("decodecorpus_files").join(&stem) + }; + Fixture { + frame: std::fs::read(dir.join(&name)).unwrap(), + content: std::fs::read(source).unwrap(), + dict: is_dict + .then(|| std::fs::read(dir.join(format!("dictionary.{tag}"))).unwrap()), + name, + } + }) + .collect() +} + +/// The frame queries: a frame, its length through the end block, a bound of +/// whole blocks, and the declared size or unknown. +#[test] +fn frame_queries_read_these_frames() { + for fixture in fixtures() { + let frame = &fixture.frame; + assert_eq!( + unsafe { ZSTD_isFrame(frame.as_ptr(), frame.len()) }, + 1, + "{}", + fixture.name + ); + assert_eq!( + unsafe { ZSTD_findFrameCompressedSize(frame.as_ptr(), frame.len()) }, + frame.len(), + "{}", + fixture.name + ); + let bound = unsafe { ZSTD_decompressBound(frame.as_ptr(), frame.len()) }; + assert!(bound >= fixture.content.len() as u64, "{}", fixture.name); + assert_eq!(bound % (128 * 1024), 0, "{}", fixture.name); + // Cut short, the frame's length cannot be told. + let cut = unsafe { ZSTD_findFrameCompressedSize(frame.as_ptr(), frame.len() - 1) }; + assert_eq!( + ZSTD_getErrorCode(cut), + ZSTD_ErrorCode::ZSTD_error_srcSize_wrong, + "{}", + fixture.name + ); + } + let size = |name: &str| { + let fixture = fixtures().into_iter().find(|f| f.name == name).unwrap(); + unsafe { ZSTD_getFrameContentSize(fixture.frame.as_ptr(), fixture.frame.len()) } + }; + // v0.5 declares no size. + assert_eq!(size("abc.txt.v05.l1.zst"), u64::MAX); + // The v0.6.2 command line declares its first 128 KiB chunk. + assert_eq!(size("z000000.v06.l1.zst"), 128 * 1024); +} + +/// `ZSTD_decompress` decodes a frame that needs no dictionary; +/// `ZSTD_decompress_usingDict` one that does, with the dictionary file as +/// that version's command line trained it; one byte short is too small. +#[test] +fn one_shot_decoders_decode_these_frames() { + let dctx = ZSTD_createDCtx(); + for fixture in fixtures() { + let mut out = vec![0u8; fixture.content.len()]; + let written = match &fixture.dict { + None => unsafe { + ZSTD_decompress( + out.as_mut_ptr(), + out.len(), + fixture.frame.as_ptr(), + fixture.frame.len(), + ) + }, + Some(dict) => unsafe { + ZSTD_decompress_usingDict( + dctx, + out.as_mut_ptr(), + out.len(), + fixture.frame.as_ptr(), + fixture.frame.len(), + dict.as_ptr(), + dict.len(), + ) + }, + }; + assert_eq!( + ZSTD_isError(written), + 0, + "{}: {:?}", + fixture.name, + ZSTD_getErrorCode(written) + ); + assert_eq!(written, fixture.content.len(), "{}", fixture.name); + assert!(out == fixture.content, "{}", fixture.name); + if fixture.dict.is_none() && !out.is_empty() { + let short = unsafe { + ZSTD_decompress( + out.as_mut_ptr(), + out.len() - 1, + fixture.frame.as_ptr(), + fixture.frame.len(), + ) + }; + assert_eq!( + ZSTD_getErrorCode(short), + ZSTD_ErrorCode::ZSTD_error_dstSize_tooSmall, + "{}", + fixture.name + ); + } + } + unsafe { ZSTD_freeDCtx(dctx) }; +} + +/// A v0.7 frame naming a dictionary is refused without it, as the wrong +/// dictionary. +#[test] +fn a_v07_frame_without_its_dictionary_is_refused() { + let fixture = fixtures() + .into_iter() + .find(|f| f.name.ends_with(".v07.dict.zst")) + .unwrap(); + let mut out = vec![0u8; fixture.content.len()]; + let rc = unsafe { + ZSTD_decompress( + out.as_mut_ptr(), + out.len(), + fixture.frame.as_ptr(), + fixture.frame.len(), + ) + }; + assert_eq!( + ZSTD_getErrorCode(rc), + ZSTD_ErrorCode::ZSTD_error_dictionary_wrong + ); +} + +/// Decode `input` through a stream referencing `dict`, giving it `step` +/// bytes of input and `room` bytes of output per call. Returns the output +/// and the last call's result. +fn stream(input: &[u8], dict: Option<&[u8]>, step: usize, room: usize) -> (Vec, usize) { + let zds = ZSTD_createDStream(); + let ddict = dict.map(|dict| unsafe { ZSTD_createDDict(dict.as_ptr().cast(), dict.len()) }); + let rc = unsafe { ZSTD_initDStream_usingDDict(zds, ddict.unwrap_or(core::ptr::null_mut())) }; + assert_eq!(ZSTD_isError(rc), 0); + let mut out = Vec::new(); + let mut buf = vec![0u8; room]; + let mut at = 0; + let mut last = 0; + let mut idle = 0; + while at < input.len() || last != 0 { + let end = (at + step).min(input.len()); + let mut inb = ZSTD_inBuffer { + src: input[at..end].as_ptr().cast(), + size: end - at, + pos: 0, + }; + let mut outb = ZSTD_outBuffer { + dst: buf.as_mut_ptr().cast(), + size: buf.len(), + pos: 0, + }; + last = unsafe { ZSTD_decompressStream(zds, &mut outb, &mut inb) }; + if ZSTD_isError(last) != 0 { + break; + } + at += inb.pos; + out.extend_from_slice(&buf[..outb.pos]); + // Input all given and nothing moving: the stream wants no more. + if inb.pos == 0 && outb.pos == 0 { + idle += 1; + if idle > 2 { + break; + } + } else { + idle = 0; + } + } + if let Some(ddict) = ddict { + unsafe { ZSTD_freeDDict(ddict) }; + } + unsafe { ZSTD_freeDStream(zds) }; + (out, last) +} + +/// The stream decodes each frame from input in small pieces into small +/// output, its dictionary referenced as a DDict, and finishes with 0. +#[test] +fn the_stream_decodes_these_frames_in_pieces() { + for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 64 * 1024) { + let (out, last) = stream(&fixture.frame, fixture.dict.as_deref(), 7, 1000); + assert_eq!(last, 0, "{}: {:?}", fixture.name, ZSTD_getErrorCode(last)); + assert!(out == fixture.content, "{}", fixture.name); + } +} + +/// One stream holding RFC 8878 frames and these, in turn, decodes them all. +#[test] +fn the_stream_decodes_these_frames_among_others() { + let fixture = fixtures() + .into_iter() + .find(|f| f.name == "z000001.v06.l19.zst") + .unwrap(); + let rfc = codec::encoding::compress_to_vec( + &b"between"[..], + codec::encoding::CompressionLevel::Fastest, + ); + let mut input = rfc.clone(); + input.extend_from_slice(&fixture.frame); + input.extend_from_slice(&rfc); + let mut expected = b"between".to_vec(); + expected.extend_from_slice(&fixture.content); + expected.extend_from_slice(b"between"); + let (out, last) = stream(&input, None, 5, 333); + assert_eq!(last, 0, "{:?}", ZSTD_getErrorCode(last)); + assert!(out == expected); +} diff --git a/zstd/src/bin/structured-zstd/main.rs b/zstd/src/bin/structured-zstd/main.rs index d3189642..bbf71b0f 100644 --- a/zstd/src/bin/structured-zstd/main.rs +++ b/zstd/src/bin/structured-zstd/main.rs @@ -6650,13 +6650,6 @@ fn encode_frame( encoder.finish().wrap_err("failed to finalize zstd frame") } -/// The magic number every zstd frame opens with (RFC 8878 §3.1.1). -const FRAME_MAGIC: u32 = 0xFD2F_B528; - -/// The 16 magic numbers a skippable frame may open with, less their low -/// nibble (RFC 8878 §3.1.2). -const SKIPPABLE_MAGIC_BASE: u32 = 0x184D_2A50; - /// Streaming decompression core (file, stdout, or sink), optionally /// dict-primed. Returns the number of bytes written. /// @@ -6746,10 +6739,9 @@ fn decode_or_pass( _ => Ok(Decoded::Frames(written)), }; } - let is_frame = read == 4 && { - let magic = u32::from_le_bytes(magic); - magic == FRAME_MAGIC || magic & 0xFFFF_FFF0 == SKIPPABLE_MAGIC_BASE - }; + // The codec's answer, as the reference asks `ZSTD_isFrame`: with the + // `legacy` feature a v0.5-v0.7 frame is one. + let is_frame = read == 4 && structured_zstd::decoding::is_frame(&magic); // Another compressor's stream is named and refused, pass-through or // not, in the order the reference checks it (`FIO_decompressFrames`): // copying it through would hand on compressed bytes as content. Input diff --git a/zstd/src/bin/structured-zstd/tests.rs b/zstd/src/bin/structured-zstd/tests.rs index 3cc44ed5..9674ad4a 100644 --- a/zstd/src/bin/structured-zstd/tests.rs +++ b/zstd/src/bin/structured-zstd/tests.rs @@ -1830,6 +1830,46 @@ fn decoding_names_the_foreign_format_it_cannot_read() { assert!(decode_stream_message(b"some data").contains("unsupported format")); } +/// The v0.5-v0.7 frames the reference command decodes by default decode here +/// too, and a v0.7 frame naming a dictionary it was not given fails as the +/// reference's does. +#[test] +fn decoding_reads_the_v05_to_v07_formats() { + let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("legacy_frames"); + for name in [ + "abc.txt.v05.l19.zst", + "z000000.v06.l1.zst", + "z000001.v07.l19.zst", + ] { + let frame = fs::read(dir.join(name)).unwrap(); + let stem = name.split_once(".v0").unwrap().0; + let source = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("decodecorpus_files") + .join(stem); + let mut out = Vec::new(); + decompress_stream( + &frame[..], + &mut out, + &mut no_dict(), + &DecodeSettings::default(), + ) + .unwrap_or_else(|err| panic!("{name}: {err}")); + assert!(out == fs::read(source).unwrap(), "{name}"); + } + let frame = fs::read(dir.join("ModemManager.service.v07.dict.zst")).unwrap(); + let err = decompress_stream( + &frame[..], + io::sink(), + &mut no_dict(), + &DecodeSettings::default(), + ) + .expect_err("the frame names a dictionary"); + assert_eq!( + named(err, "in.zst").to_string(), + "in.zst : Decoding error (36) : Dictionary mismatch " + ); +} + /// A decoding failure is reported against the input, in the reference /// command's two shapes (`fileio.c` 2463, 2748-2796): `zstd: NAME: message` /// for input that is not a frame, and `NAME : Decoding error (36) : Date: Mon, 19 Oct 2026 21:47:31 +0300 Subject: [PATCH 06/11] perf(decoding): keep v0.5-v0.7 stream history in segments - The stream held its history in one vector and dropped what fell out of reach by moving the rest to the front, so every block past the window moved a whole window of bytes. It now writes in segments of at most the window and a block: a block that would not fit starts the next segment at the buffer's start, and a match reaching past that start reads from the segment before, which the new one has not overwritten yet, as upstream's buffered decoders wrap. No history is moved, and the buffer stays within the window and a block. - FSE and Huffman tables are rebuilt in the storage of the tables they replace, and the Huffman weight table is kept for the next description, instead of a fresh vector per block that sends one. - Fixtures: z000000 eight times over, from each command line, a frame several windows long whose matches cross the segment boundary; checked to decode through libzstd 1.5.7. - Tests: the stream reads those frames with the buffer bounded by the window and a block; a table rebuilt in place is a fresh one. Part of #131 --- c-api/src/tests/legacy.rs | 11 +- scripts/legacy-fixtures.sh | 10 ++ zstd/legacy_frames/z000000x8.v05.l1.zst | Bin 0 -> 20218 bytes zstd/legacy_frames/z000000x8.v06.l1.zst | Bin 0 -> 19719 bytes zstd/legacy_frames/z000000x8.v07.l1.zst | Bin 0 -> 8089 bytes zstd/src/decoding/frame_decoder.rs | 1 - zstd/src/decoding/legacy/entropy.rs | 132 +++++++++----- zstd/src/decoding/legacy/entropy/tests.rs | 35 +++- zstd/src/decoding/legacy/mod.rs | 206 ++++++++++++++++++---- zstd/src/decoding/legacy/tests.rs | 53 +++++- zstd/src/decoding/legacy/v05.rs | 47 +++-- zstd/src/decoding/legacy/v06.rs | 30 +++- zstd/src/decoding/legacy/v07.rs | 40 +++-- 13 files changed, 447 insertions(+), 118 deletions(-) create mode 100644 zstd/legacy_frames/z000000x8.v05.l1.zst create mode 100644 zstd/legacy_frames/z000000x8.v06.l1.zst create mode 100644 zstd/legacy_frames/z000000x8.v07.l1.zst diff --git a/c-api/src/tests/legacy.rs b/c-api/src/tests/legacy.rs index 9ba75f55..055c4c38 100644 --- a/c-api/src/tests/legacy.rs +++ b/c-api/src/tests/legacy.rs @@ -48,14 +48,19 @@ fn fixtures() -> Vec { }) .expect("fixture name carries its version"); let is_dict = name.contains(".dict"); + // `x` is the file N times over. + let (file, times) = stem + .rsplit_once('x') + .and_then(|(file, n)| Some((file, n.parse::().ok()?))) + .unwrap_or((stem.as_str(), 1)); let source = if is_dict { - codec_dir().join("dict_tests/files").join(&stem) + codec_dir().join("dict_tests/files").join(file) } else { - codec_dir().join("decodecorpus_files").join(&stem) + codec_dir().join("decodecorpus_files").join(file) }; Fixture { frame: std::fs::read(dir.join(&name)).unwrap(), - content: std::fs::read(source).unwrap(), + content: std::fs::read(source).unwrap().repeat(times), dict: is_dict .then(|| std::fs::read(dir.join(format!("dictionary.{tag}"))).unwrap()), name, diff --git a/scripts/legacy-fixtures.sh b/scripts/legacy-fixtures.sh index e36b98e6..2dc04e2b 100755 --- a/scripts/legacy-fixtures.sh +++ b/scripts/legacy-fixtures.sh @@ -46,6 +46,15 @@ build() { rm -rf "$out" mkdir -p "$out" +# A frame several windows long, whose matches reach back across the point +# where a buffered decoder starts its next segment: z000000 eight times, each +# copy a match on the one before. Tests rebuild it from the name `x`. +repeated="$work/z000000x8" +: >"$repeated" +for _ in 1 2 3 4 5 6 7 8; do + cat "$corpus/z000000" >>"$repeated" +done + for release in "${releases[@]}"; do read -r version digest <<<"$release" tag="v0${version:2:1}" @@ -56,6 +65,7 @@ for release in "${releases[@]}"; do "$zstd_bin" -q -f "-$level" "$corpus/$input" -o "$out/$input.$tag.l$level.zst" done done + "$zstd_bin" -q -f -1 "$repeated" -o "$out/z000000x8.$tag.l1.zst" # A dictionary trained by the release itself, and frames that need it. # The training set is listed in sorted order, so the dictionary is the same diff --git a/zstd/legacy_frames/z000000x8.v05.l1.zst b/zstd/legacy_frames/z000000x8.v05.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..0a88157fe81534ef5c4865bd3b213dda61b1884b GIT binary patch literal 20218 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­K;Z<6`DiaupFP?I2N(`>i|CPJ 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 MX90kJAOyew0Ae>1sQ>@~ literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000000x8.v06.l1.zst b/zstd/legacy_frames/z000000x8.v06.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..6921fc05601d29b68d43fe6584b77d3b7de1cacc GIT binary patch literal 19719 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≺{YJn+AOm;l=irz7Nk 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 O{PY}}-BAG+zyJW9)B5WG literal 0 HcmV?d00001 diff --git a/zstd/legacy_frames/z000000x8.v07.l1.zst b/zstd/legacy_frames/z000000x8.v07.l1.zst new file mode 100644 index 0000000000000000000000000000000000000000..95ff55f6fcf435758c54f56aac3b80b38568499f GIT binary patch literal 8089 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 U;0-2#1N?vJbl3m6yX89n2dqMDga7~l literal 0 HcmV?d00001 diff --git a/zstd/src/decoding/frame_decoder.rs b/zstd/src/decoding/frame_decoder.rs index ee4ce52d..b97feed9 100644 --- a/zstd/src/decoding/frame_decoder.rs +++ b/zstd/src/decoding/frame_decoder.rs @@ -4169,7 +4169,6 @@ impl FrameDecoder { let mut out = VecOutput { buf: output, start, - dropped: 0, limit: usize::MAX, }; let decoded = legacy::decode_frame(input, legacy_dict(dict), &mut out); diff --git a/zstd/src/decoding/legacy/entropy.rs b/zstd/src/decoding/legacy/entropy.rs index ea6dc446..7b0624cb 100644 --- a/zstd/src/decoding/legacy/entropy.rs +++ b/zstd/src/decoding/legacy/entropy.rs @@ -9,7 +9,6 @@ use super::corrupt; use crate::bit_io::BitReaderReversed; use crate::decoding::errors::LegacyFrameError as Error; -use alloc::vec; use alloc::vec::Vec; /// A backward bitstream, read from its last byte toward its first. @@ -230,15 +229,35 @@ pub(super) enum StreamEnd { } impl FseTable { + /// A table holding nothing yet, for [`Self::build`] and the setters to + /// fill. + pub(super) const fn empty() -> FseTable { + FseTable { + log: 0, + cells: Vec::new(), + fast: false, + } + } + /// The table a distribution spreads (`FSEv07_buildDTable`). pub(super) fn new(dist: &Distribution) -> Result { + let mut table = FseTable::empty(); + table.build(dist)?; + Ok(table) + } + + /// Spread `dist` into this table, in the cells it already has, as a block + /// sending a new description rebuilds its field's table. + pub(super) fn build(&mut self, dist: &Distribution) -> Result<(), Error> { let log = dist.table_log; if log > FSE_MAX_TABLE_LOG { return Err(corrupt("FSE table log too large")); } let size = 1usize << log; let counts = &dist.counts[..=dist.max_symbol]; - let mut cells = vec![FseCell::default(); size]; + let cells = &mut self.cells; + cells.clear(); + cells.resize(size, FseCell::default()); let mut next = [0u16; 256]; // Symbols below one state take the top cells, one each. @@ -275,7 +294,7 @@ impl FseTable { return Err(corrupt("FSE distribution does not fill its table")); } - for cell in &mut cells { + for cell in cells.iter_mut() { let state = next[usize::from(cell.symbol)]; next[usize::from(cell.symbol)] += 1; let nb_bits = log - (31 - u32::from(state).leading_zeros()); @@ -283,37 +302,34 @@ impl FseTable { cell.new_state = ((u32::from(state) << nb_bits) - size as u32) as u16; } let large = 1i16 << (log - 1); - let fast = counts.iter().all(|&count| count < large); - Ok(FseTable { log, cells, fast }) + self.fast = counts.iter().all(|&count| count < large); + self.log = log; + Ok(()) } - /// A table reading every symbol as `nb_bits` raw bits (`FSEv07_buildDTable_raw`). - pub(super) fn raw(nb_bits: u32) -> FseTable { - let cells = (0..1usize << nb_bits) - .map(|symbol| FseCell { + /// Read every symbol as `nb_bits` raw bits (`FSEv07_buildDTable_raw`). + pub(super) fn set_raw(&mut self, nb_bits: u32) { + self.cells.clear(); + self.cells + .extend((0..1usize << nb_bits).map(|symbol| FseCell { new_state: 0, symbol: symbol as u8, nb_bits: nb_bits as u8, - }) - .collect(); - FseTable { - log: nb_bits, - cells, - fast: true, - } + })); + self.log = nb_bits; + self.fast = true; } - /// A table producing `symbol` without reading (`FSEv07_buildDTable_rle`). - pub(super) fn rle(symbol: u8) -> FseTable { - FseTable { - log: 0, - cells: vec![FseCell { - new_state: 0, - symbol, - nb_bits: 0, - }], - fast: false, - } + /// Produce `symbol` without reading (`FSEv07_buildDTable_rle`). + pub(super) fn set_rle(&mut self, symbol: u8) { + self.cells.clear(); + self.cells.push(FseCell { + new_state: 0, + symbol, + nb_bits: 0, + }); + self.log = 0; + self.fast = false; } } @@ -352,6 +368,7 @@ fn fse_decompress( out: &mut [u8; 256], limit: usize, end: StreamEnd, + table: &mut FseTable, ) -> Result { if src.len() < 2 { return Err(Error::Truncated); @@ -360,33 +377,34 @@ fn fse_decompress( if description_len >= src.len() { return Err(Error::Truncated); } - let table = FseTable::new(&dist)?; + table.build(&dist)?; + let table = &*table; let mut bits = open_bits(&src[description_len..])?; - let mut first = FseState::new(&table, &mut bits); - let mut second = FseState::new(&table, &mut bits); + let mut first = FseState::new(table, &mut bits); + let mut second = FseState::new(table, &mut bits); if end == StreamEnd::Exact { - return decode_to_exact_end(&table, &mut bits, [first, second], out, limit); + return decode_to_exact_end(table, &mut bits, [first, second], out, limit); } let mut n = 0usize; loop { if n + 2 > limit { return Err(corrupt("FSE stream decodes past its output")); } - out[n] = first.symbol(&table); + out[n] = first.symbol(table); n += 1; - first.update(&table, &mut bits); + first.update(table, &mut bits); if overflowed(&bits) { - out[n] = second.symbol(&table); + out[n] = second.symbol(table); return Ok(n + 1); } if n + 2 > limit { return Err(corrupt("FSE stream decodes past its output")); } - out[n] = second.symbol(&table); + out[n] = second.symbol(table); n += 1; - second.update(&table, &mut bits); + second.update(table, &mut bits); if overflowed(&bits) { - out[n] = first.symbol(&table); + out[n] = first.symbol(table); return Ok(n + 1); } } @@ -429,6 +447,9 @@ pub(super) struct HufTable { log: u32, /// `(symbol, code length)` per entry. cells: Vec<(u8, u8)>, + /// The table an FSE-coded weight description is read with, kept for the + /// next description. + weight_table: FseTable, } /// Run lengths a description starting with byte `242 + i` stands for: that @@ -436,10 +457,27 @@ pub(super) struct HufTable { const WEIGHT_RUNS: [usize; 14] = [1, 2, 3, 4, 7, 8, 15, 16, 31, 32, 63, 64, 127, 128]; impl HufTable { + /// A table holding nothing yet, for [`Self::read_into`] to fill. + pub(super) const fn empty() -> HufTable { + HufTable { + log: 0, + cells: Vec::new(), + weight_table: FseTable::empty(), + } + } + /// Read a Huffman table description (`HUFv07_readStats` and /// `HUFv07_readDTableX2`), its weights FSE-coded with the ending `end`. /// Returns the table and the bytes read. pub(super) fn read(src: &[u8], end: StreamEnd) -> Result<(HufTable, usize), Error> { + let mut table = HufTable::empty(); + let consumed = table.read_into(src, end)?; + Ok((table, consumed)) + } + + /// [`Self::read`] into this table's own storage, as each block with a + /// new description replaces the table. Returns the bytes read. + pub(super) fn read_into(&mut self, src: &[u8], end: StreamEnd) -> Result { let &header = src.first().ok_or(Error::Truncated)?; let mut weights = [0u8; 256]; let (explicit, consumed) = match header { @@ -460,10 +498,13 @@ impl HufTable { _ => { let len = usize::from(header); let body = src.get(1..1 + len).ok_or(Error::Truncated)?; - (fse_decompress(body, &mut weights, 255, end)?, len + 1) + let explicit = + fse_decompress(body, &mut weights, 255, end, &mut self.weight_table)?; + (explicit, len + 1) } }; - Ok((HufTable::from_weights(&mut weights, explicit)?, consumed)) + self.fill(&mut weights, explicit)?; + Ok(consumed) } /// The table whose weights are an RFC 8878 description's code lengths, @@ -482,12 +523,14 @@ impl HufTable { *weight = if len == 0 { 0 } else { max_bits + 1 - len }; } // The last weight is inferred again, from the others. - HufTable::from_weights(&mut weights, explicit) + let mut table = HufTable::empty(); + table.fill(&mut weights, explicit)?; + Ok(table) } /// Build the table from the first `explicit` weights, inferring the /// weight of the symbol after them. - fn from_weights(weights: &mut [u8; 256], explicit: usize) -> Result { + fn fill(&mut self, weights: &mut [u8; 256], explicit: usize) -> Result<(), Error> { let mut ranks = [0u32; HUF_WEIGHT_LIMIT as usize + 1]; let mut total = 0u32; for &weight in &weights[..explicit] { @@ -527,7 +570,9 @@ impl HufTable { start[weight] = next; next += ranks[weight] << (weight - 1); } - let mut cells = vec![(0u8, 0u8); 1 << log]; + let cells = &mut self.cells; + cells.clear(); + cells.resize(1 << log, (0, 0)); for (symbol, &weight) in weights[..=explicit].iter().enumerate() { if weight == 0 { continue; @@ -537,7 +582,8 @@ impl HufTable { cells[first..first + len].fill((symbol as u8, (log + 1 - u32::from(weight)) as u8)); start[usize::from(weight)] += len as u32; } - Ok(HufTable { log, cells }) + self.log = log; + Ok(()) } #[inline] diff --git a/zstd/src/decoding/legacy/entropy/tests.rs b/zstd/src/decoding/legacy/entropy/tests.rs index e3f78146..a8960103 100644 --- a/zstd/src/decoding/legacy/entropy/tests.rs +++ b/zstd/src/decoding/legacy/entropy/tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::decoding::legacy::v07::{LL_DEFAULT, LL_DEFAULT_LOG}; +use alloc::vec; /// A bitstream opens past the padding up to its end mark; an empty one, or /// one whose last byte has no mark, is refused. @@ -37,6 +38,33 @@ fn predefined_distributions_fill_their_table() { } } +/// A table built over another's storage, a larger one or one set to raw +/// bits, is the table a fresh build gives: nothing of the earlier table stays. +#[test] +fn a_table_rebuilt_in_place_is_a_fresh_one() { + let dist = Distribution::predefined(&LL_DEFAULT, LL_DEFAULT_LOG); + let fresh = FseTable::new(&dist).unwrap(); + let mut reused = FseTable::empty(); + reused.set_raw(9); + reused.build(&dist).unwrap(); + assert_eq!(reused.log, fresh.log); + assert_eq!(reused.fast, fresh.fast); + assert_eq!(reused.cells.len(), fresh.cells.len()); + for (a, b) in reused.cells.iter().zip(&fresh.cells) { + assert_eq!( + (a.symbol, a.nb_bits, a.new_state), + (b.symbol, b.nb_bits, b.new_state) + ); + } + // A Huffman table read over a wider one is the narrower table alone. + let (fresh, _) = HufTable::read(&[127 + 2, 0x11], StreamEnd::PastStart).unwrap(); + let (mut reused, _) = HufTable::read(&[255], StreamEnd::PastStart).unwrap(); + reused + .read_into(&[127 + 2, 0x11], StreamEnd::PastStart) + .unwrap(); + assert_eq!((reused.log, &reused.cells), (fresh.log, &fresh.cells)); +} + /// A distribution that does not sum to its table is refused, as one with /// more below-one symbols than cells. #[test] @@ -64,7 +92,8 @@ fn bad_descriptions_are_refused() { /// backward; a one-symbol table reads nothing. #[test] fn raw_and_rle_tables_read_as_described() { - let raw = FseTable::raw(5); + let mut raw = FseTable::empty(); + raw.set_raw(5); // Under the end mark: 11010 then 11, padded with zeros past the start. let mut bits = open_bits(&[0b1110_1011]).unwrap(); let mut state = FseState::new(&raw, &mut bits); @@ -73,7 +102,9 @@ fn raw_and_rle_tables_read_as_described() { assert_eq!(state.symbol(&raw), 0b11000); assert!(overflowed(&bits)); - let rle = FseTable::rle(42); + // Set over the raw table's storage, as a block's next mode is. + let mut rle = raw; + rle.set_rle(42); let mut bits = open_bits(&[0x80]).unwrap(); let mut state = FseState::new(&rle, &mut bits); for _ in 0..3 { diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index c7d159a4..9e4d255b 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -243,21 +243,28 @@ pub(crate) trait Output { /// content checksum. #[cfg(feature = "hash")] fn since(&self, from: usize) -> &[u8]; + /// Append a match of `len` bytes from `offset` back. An output holding + /// the whole frame takes it from the frame, the dictionary in front of + /// it, or both ([`copy_match`]); the room for `len` bytes is reserved. + fn copy_match(&mut self, offset: usize, len: usize, dict: &[u8]) -> Result<(), Error> + where + Self: Sized, + { + copy_match(self, offset, len, dict) + } } /// Output into a growing vector, the current frame starting at `start`. pub(crate) struct VecOutput<'a> { pub(crate) buf: &'a mut Vec, pub(crate) start: usize, - /// Bytes of the frame already handed on and dropped from `buf`. - pub(crate) dropped: usize, /// Most bytes the frame may produce. pub(crate) limit: usize, } impl Output for VecOutput<'_> { fn produced(&self) -> usize { - self.dropped + self.held() + self.held() } fn held(&self) -> usize { @@ -294,7 +301,7 @@ impl Output for VecOutput<'_> { #[cfg(feature = "hash")] fn since(&self, from: usize) -> &[u8] { - &self.buf[self.start + (from - self.dropped)..] + &self.buf[self.start + from..] } } @@ -405,7 +412,7 @@ fn execute( .ok_or(corrupt("sequence takes more literals than the block has"))?; out.extend(lits); *lit_pos += lit_len; - copy_match(out, offset, match_len, dict) + out.copy_match(offset, match_len, dict) } /// The literal section's header, common to the three formats. @@ -571,6 +578,9 @@ impl Output for Budget<'_, O> { fn since(&self, from: usize) -> &[u8] { self.out.since(from) } + fn copy_match(&mut self, offset: usize, len: usize, dict: &[u8]) -> Result<(), Error> { + self.out.copy_match(offset, len, dict) + } } /// A frame being decoded block by block: the whole-buffer and the streaming @@ -726,14 +736,20 @@ pub(crate) struct LegacyStream { frame: Option, /// The dictionary the frame was started with. dict: Option, - /// Output still within reach of a match, and output not handed out yet. + /// The output segments: the current one at `..end`, and past it what is + /// left of the one before. buf: Vec, - /// Bytes of the frame dropped from the front of `buf`. - dropped: usize, - /// How much of `buf` has been handed out. + /// End of the current segment. + end: usize, + /// Bytes of the frame in segments before the current one. + earlier: usize, + /// End of the segment before the current one, once there is one. + previous_end: Option, + /// How much of the current segment has been handed out. delivered: usize, - /// History kept for matches: the window and one block. - keep: usize, + /// Most the buffer holds before a block starts a new segment: the window + /// and one block, so the segment before keeps the window in reach. + capacity: usize, /// Whether the frame's end block has been read. finished: bool, /// Bytes of the frame read so far, its header included. @@ -748,9 +764,11 @@ impl LegacyStream { frame: None, dict: None, buf: Vec::new(), - dropped: 0, + end: 0, + earlier: 0, + previous_end: None, delivered: 0, - keep: 0, + capacity: 0, finished: false, consumed: 0, block: Vec::new(), @@ -770,12 +788,14 @@ impl LegacyStream { self.frame = Some(LegacyFrame::new(header, legacy_dict)?); self.dict = dict; self.buf.clear(); - self.dropped = 0; + self.end = 0; + self.earlier = 0; + self.previous_end = None; self.delivered = 0; // Every header reader holds the window to its format's largest, at // most 128 MiB, which every target addresses. debug_assert!(header.window <= 1 << 27); - self.keep = header.window as usize + BLOCK_SIZE_MAX; + self.capacity = header.window as usize + BLOCK_SIZE_MAX; self.finished = false; self.consumed = header.size as u64; Ok(()) @@ -786,6 +806,7 @@ impl LegacyStream { self.frame = None; self.dict = None; self.buf.clear(); + self.end = 0; self.delivered = 0; self.finished = false; } @@ -803,12 +824,12 @@ impl LegacyStream { /// Decoded bytes not handed out yet. pub(crate) fn undelivered(&self) -> usize { - self.buf.len() - self.delivered + self.end - self.delivered } /// Bytes the frame has decoded to so far. pub(crate) fn produced(&self) -> u64 { - (self.dropped + self.buf.len()) as u64 + (self.earlier + self.end) as u64 } /// Bytes of the frame read so far, its header included. @@ -869,7 +890,7 @@ impl LegacyStream { /// The undelivered output, which [`Self::mark_delivered`] then hands out. pub(crate) fn undelivered_bytes(&self) -> &[u8] { - &self.buf[self.delivered..] + &self.buf[self.delivered..self.end] } /// Count the first `n` undelivered bytes as handed out. @@ -917,9 +938,11 @@ impl LegacyStream { frame, dict, buf, - dropped, + end, + earlier, + previous_end, delivered, - keep, + capacity, finished, consumed, block: _, @@ -932,25 +955,144 @@ impl LegacyStream { *finished = true; return Ok(()); } - // Drop history out of reach once it has been handed out, a block's - // worth at a time so the move is paid once per block at most. - if buf.len() > *keep + BLOCK_SIZE_MAX { - let drop = (buf.len() - *keep).min(*delivered); - buf.drain(..drop); - *delivered -= drop; - *dropped += drop; + // A block that would not fit starts a new segment at the buffer's + // start, once the current one is all handed out; the one before stays + // where it is for matches, as upstream's buffered decoders wrap + // (`ZBUFFv07_decompressContinue`). Output held back by a caller that + // has not collected it grows the buffer instead. + if *end + BLOCK_SIZE_MAX > *capacity && *delivered == *end && *end > 0 { + *earlier += *end; + *previous_end = Some(*end); + *end = 0; + *delivered = 0; } let dict_bytes = dict .as_ref() .map_or(LegacyDict::None, |dict| LegacyDict::Parsed(dict.as_dict())) .content_bytes(); - let mut out = VecOutput { + let mut out = SegmentOutput { buf, - start: 0, - dropped: *dropped, - limit: usize::MAX, + end: *end, + earlier: *earlier, + previous_end: *previous_end, + capacity: *capacity, }; - frame.block(block, &step[3..], &mut out, dict_bytes) + let decoded = frame.block(block, &step[3..], &mut out, dict_bytes); + *end = out.end; + decoded + } +} + +/// Output into a stream's segments: the current segment from the buffer's +/// start to `end`, and the segment before it, which ends at `previous_end`, +/// past the current one. +struct SegmentOutput<'b> { + buf: &'b mut Vec, + end: usize, + earlier: usize, + previous_end: Option, + /// What the buffer grows to at most while segments wrap. + capacity: usize, +} + +impl SegmentOutput<'_> { + /// Make room for `len` more bytes at `end`; the buffer grows only where + /// the segment runs past what it holds, doubling up to `capacity` and + /// past it only for output a caller holds back. + fn room(&mut self, len: usize) -> core::ops::Range { + let to = self.end + len; + if to > self.buf.capacity() { + // A `Vec` holds at most `isize::MAX` bytes, so doubling its + // length stays within `usize`. + let doubled = 2 * self.buf.len(); + let target = if to <= self.capacity { + doubled.clamp(to, self.capacity) + } else { + doubled.max(to) + }; + self.buf.reserve_exact(target - self.buf.len()); + } + if to > self.buf.len() { + self.buf.resize(to, 0); + } + self.end..to + } +} + +impl Output for SegmentOutput<'_> { + fn produced(&self) -> usize { + self.earlier + self.end + } + + fn held(&self) -> usize { + self.end + } + + fn reserve(&mut self, _len: usize) -> Result<(), Error> { + // The block's own budget bounds what it adds. + Ok(()) + } + + fn extend(&mut self, bytes: &[u8]) { + let at = self.room(bytes.len()); + self.buf[at].copy_from_slice(bytes); + self.end += bytes.len(); + } + + fn repeat(&mut self, byte: u8, len: usize) { + let at = self.room(len); + self.buf[at].fill(byte); + self.end += len; + } + + fn copy_back(&mut self, offset: usize, len: usize) { + let from = self.end - offset; + let at = self.room(len); + if offset >= len { + self.buf.copy_within(from..from + len, at.start); + } else { + for i in 0..len { + self.buf[at.start + i] = self.buf[from + i]; + } + } + self.end += len; + } + + #[cfg(feature = "hash")] + fn since(&self, from: usize) -> &[u8] { + &self.buf[self.end - (self.produced() - from)..self.end] + } + + fn copy_match(&mut self, offset: usize, len: usize, dict: &[u8]) -> Result<(), Error> { + let Some(previous_end) = self.previous_end else { + // One segment so far: it holds the whole frame. + return copy_match(self, offset, len, dict); + }; + if offset == 0 { + return Err(corrupt("match with offset 0")); + } + if offset <= self.end { + self.copy_back(offset, len); + return Ok(()); + } + // Into the segment before, which the current one has not yet + // overwritten from `end` on. + let back = offset - self.end; + let from = previous_end + .checked_sub(back) + .filter(|&from| from >= self.end) + .ok_or(corrupt("match reaches before the window"))?; + let first = len.min(back); + let at = self.room(first); + self.buf.copy_within(from..from + first, at.start); + self.end += first; + let rest = len - first; + if rest > 0 { + // The match runs on from the current segment's first byte. + let distance = self.end; + self.copy_back(distance, rest); + } + Ok(()) } } diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs index 15acd3e2..9374535d 100644 --- a/zstd/src/decoding/legacy/tests.rs +++ b/zstd/src/decoding/legacy/tests.rs @@ -62,16 +62,21 @@ mod fixtures { _ => LegacyVersion::V07, }; let is_dict = name.contains(".dict"); + // `x` is the file N times over. + let (file, times) = stem + .rsplit_once('x') + .and_then(|(file, n)| Some((file, n.parse::().ok()?))) + .unwrap_or((stem.as_str(), 1)); let source: PathBuf = if is_dict { - root().join("dict_tests/files").join(&stem) + root().join("dict_tests/files").join(file) } else { - root().join("decodecorpus_files").join(&stem) + root().join("decodecorpus_files").join(file) }; let dict = is_dict.then(|| std::fs::read(dir.join(format!("dictionary.{rest}"))).unwrap()); Fixture { frame: std::fs::read(dir.join(&name)).unwrap(), - content: std::fs::read(&source).unwrap(), + content: std::fs::read(&source).unwrap().repeat(times), parsed: dict.as_deref().map(|raw| { crate::decoding::Dictionary::from_serialized_or_raw_content(raw).unwrap() }), @@ -92,7 +97,6 @@ fn decode_to_vec(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec, usize), let mut out = VecOutput { buf: &mut buf, start: 0, - dropped: 0, limit: usize::MAX, }; let consumed = decode_frame(frame, dict, &mut out)?; @@ -719,6 +723,47 @@ fn a_stream_holds_its_window_not_the_frame() { assert!(largest <= 4 * BLOCK_SIZE_MAX, "held {largest} bytes"); } +/// A frame several windows long decodes through the stream with its matches +/// reaching into the segment before the current one, the buffer never past +/// the window and a block. +#[cfg(feature = "std")] +#[test] +fn a_stream_reads_matches_from_the_segment_before() { + for fixture in fixtures() + .into_iter() + .filter(|f| f.name.starts_with("z000000x8.")) + { + let header = read_header(&fixture.frame).unwrap(); + let capacity = header.window as usize + BLOCK_SIZE_MAX; + assert!( + fixture.content.len() > 2 * capacity, + "{}: the frame must outrun the buffer", + fixture.name + ); + let mut stream = LegacyStream::new(); + stream.start(header, None).unwrap(); + let mut src = &fixture.frame[header.size..]; + let mut out = Vec::new(); + let mut target = vec![0u8; 4096]; + while !(stream.is_finished() && stream.undelivered() == 0) { + let (read, written) = stream + .decode(src, &mut target) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + assert!(read > 0 || written > 0, "{} stalled", fixture.name); + src = &src[read..]; + out.extend_from_slice(&target[..written]); + } + assert!(out == fixture.content, "{}", fixture.name); + assert!( + stream.heap_bytes() <= capacity, + "{}: held {} bytes for a window of {}", + fixture.name, + stream.heap_bytes(), + header.window + ); + } +} + /// An RLE block repeats its byte, in every format. #[test] fn rle_blocks_repeat_their_byte() { diff --git a/zstd/src/decoding/legacy/v05.rs b/zstd/src/decoding/legacy/v05.rs index b6109a96..8a099957 100644 --- a/zstd/src/decoding/legacy/v05.rs +++ b/zstd/src/decoding/legacy/v05.rs @@ -54,6 +54,8 @@ pub(super) fn read_header(src: &[u8]) -> Result { pub(super) struct Decoder { /// The dictionary's Huffman table, which repeat-table literals use. dict_huf: Option, + /// The table a block's own Huffman description is read into. + huf: HufTable, tables: SeqTables, /// Whether a block may keep the previous tables: once a dictionary has /// been loaded (`flagStaticTables`). @@ -67,6 +69,7 @@ impl Decoder { pub(super) fn new(bytes: &[u8]) -> Result<(Decoder, core::ops::Range), Error> { let mut decoder = Decoder { dict_huf: None, + huf: HufTable::empty(), tables: SeqTables::default(), repeat_tables: false, literals: Vec::new(), @@ -136,7 +139,13 @@ impl Decoder { self.literals.clear(); if single_stream { self.literals.resize(size, 0); - huffman(src, &mut self.literals, false, StreamEnd::Exact)?; + huffman( + &mut self.huf, + src, + &mut self.literals, + false, + StreamEnd::Exact, + )?; } else { // `HUFv05_decompress`: an uncompressed section is not // Huffman literals here, a one-byte section is a run. @@ -147,7 +156,13 @@ impl Decoder { self.literals.resize(size, src[0]); } else { self.literals.resize(size, 0); - huffman(src, &mut self.literals, true, StreamEnd::Exact)?; + huffman( + &mut self.huf, + src, + &mut self.literals, + true, + StreamEnd::Exact, + )?; } } (LiteralSource::Buffer, header + compressed) @@ -335,9 +350,21 @@ fn field_table( field: Field, repeat: bool, ) -> Result { - let (new, len) = match mode { + if mode == 2 { + if !repeat || table.is_none() { + return Err(corrupt("sequence table repeated before one was sent")); + } + return Ok(0); + } + // Built in the previous table's storage; left unset should the + // description be bad, so no later block repeats a half-built table. + let mut new = table.take().unwrap_or_else(FseTable::empty); + let len = match mode { // Every symbol read as raw bits. - 0 => (FseTable::raw(field.raw_bits), 0), + 0 => { + new.set_raw(field.raw_bits); + 0 + } 1 => { if field.checked_rle && at + 2 > src.len() { return Err(Error::Truncated); @@ -350,13 +377,8 @@ fn field_table( } else { symbol }; - (FseTable::rle(symbol), 1) - } - 2 => { - if !repeat || table.is_none() { - return Err(corrupt("sequence table repeated before one was sent")); - } - return Ok(0); + new.set_rle(symbol); + 1 } _ => { let (dist, len) = super::entropy::read_distribution(&src[at..], field.max) @@ -364,7 +386,8 @@ fn field_table( if dist.table_log() > field.max_log { return Err(corrupt("sequence table too large")); } - (FseTable::new(&dist)?, len) + new.build(&dist)?; + len } }; *table = Some(new); diff --git a/zstd/src/decoding/legacy/v06.rs b/zstd/src/decoding/legacy/v06.rs index 9bfb8e16..d7ab7a71 100644 --- a/zstd/src/decoding/legacy/v06.rs +++ b/zstd/src/decoding/legacy/v06.rs @@ -60,6 +60,8 @@ pub(super) fn read_header(src: &[u8]) -> Result { pub(super) struct Decoder { /// The dictionary's Huffman table, which repeat-table literals use. dict_huf: Option, + /// The table a block's own Huffman description is read into. + huf: HufTable, tables: SeqTables, /// Whether the next block may use the dictionary's tables: from loading a /// dictionary until the first sequence section is read @@ -74,6 +76,7 @@ impl Decoder { pub(super) fn new(bytes: &[u8]) -> Result<(Decoder, core::ops::Range), Error> { let mut decoder = Decoder { dict_huf: None, + huf: HufTable::empty(), tables: SeqTables::default(), repeat_tables: false, literals: Vec::new(), @@ -143,7 +146,13 @@ impl Decoder { self.literals.clear(); if single_stream { self.literals.resize(size, 0); - huffman(src, &mut self.literals, false, StreamEnd::PastStart)?; + huffman( + &mut self.huf, + src, + &mut self.literals, + false, + StreamEnd::PastStart, + )?; } else { // `HUFv06_decompress`: a section as long as its content // is the content, a one-byte section is a run of it. @@ -156,7 +165,13 @@ impl Decoder { self.literals.resize(size, src[0]); } else { self.literals.resize(size, 0); - huffman(src, &mut self.literals, true, StreamEnd::PastStart)?; + huffman( + &mut self.huf, + src, + &mut self.literals, + true, + StreamEnd::PastStart, + )?; } } (LiteralSource::Buffer, header + compressed) @@ -289,16 +304,19 @@ impl Decoder { } } -/// Decode Huffman literals with the table their section starts with, into -/// `out` (`HUFv06_decompress1X2` / `HUFv06_decompress4X2`); `end` is how the -/// format ends the table's FSE-coded weights. +/// Decode Huffman literals with the table their section starts with, read +/// into `table`, into `out` (`HUFv06_decompress1X2` / `HUFv06_decompress4X2`); +/// `end` is how the format ends the table's FSE-coded weights. pub(super) fn huffman( + table: &mut HufTable, src: &[u8], out: &mut [u8], four_streams: bool, end: StreamEnd, ) -> Result<(), Error> { - let (table, table_len) = HufTable::read(src, end).map_err(|_| corrupt("bad Huffman table"))?; + let table_len = table + .read_into(src, end) + .map_err(|_| corrupt("bad Huffman table"))?; if table_len >= src.len() { return Err(corrupt("Huffman table fills the literal section")); } diff --git a/zstd/src/decoding/legacy/v07.rs b/zstd/src/decoding/legacy/v07.rs index 19a1652a..7dfd2a74 100644 --- a/zstd/src/decoding/legacy/v07.rs +++ b/zstd/src/decoding/legacy/v07.rs @@ -265,7 +265,12 @@ impl Decoder { if !single_stream && (compressed >= size || compressed <= 1) { return Err(corrupt("Huffman literals no smaller than their content")); } - let (table, table_len) = HufTable::read(src, StreamEnd::PastStart) + // Built in the previous table's storage; left unset should the + // description be bad, so no later block repeats a half-built + // table. + let mut table = self.huf.take().unwrap_or_else(HufTable::empty); + let table_len = table + .read_into(src, StreamEnd::PastStart) .map_err(|_| corrupt("bad Huffman table"))?; if table_len >= src.len() { return Err(corrupt("Huffman table fills the literal section")); @@ -447,26 +452,30 @@ pub(super) fn seq_table( repeat: bool, ) -> Result { let (max, max_log, default, default_log) = field; - let (new, len) = match mode { + // The previous table, in place. + if mode == 2 { + if !repeat || table.is_none() { + return Err(corrupt("sequence table repeated before one was sent")); + } + return Ok(0); + } + // Built in the previous table's storage; left unset should the + // description be bad, so no later block repeats a half-built table. + let mut new = table.take().unwrap_or_else(FseTable::empty); + let len = match mode { // Predefined. - 0 => ( - FseTable::new(&Distribution::predefined(default, default_log))?, - 0, - ), + 0 => { + new.build(&Distribution::predefined(default, default_log))?; + 0 + } // One symbol throughout. 1 => { let &symbol = src.first().ok_or(Error::Truncated)?; if usize::from(symbol) > max { return Err(corrupt("sequence symbol out of range")); } - (FseTable::rle(symbol), 1) - } - // The previous table, in place. - 2 => { - if !repeat || table.is_none() { - return Err(corrupt("sequence table repeated before one was sent")); - } - return Ok(0); + new.set_rle(symbol); + 1 } _ => { let (dist, len) = super::entropy::read_distribution(src, max) @@ -474,7 +483,8 @@ pub(super) fn seq_table( if dist.table_log() > max_log { return Err(corrupt("sequence table too large")); } - (FseTable::new(&dist)?, len) + new.build(&dist)?; + len } }; *table = Some(new); From 403269496260e371f7f1cfb700bc54a6057a6005 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Mon, 19 Oct 2026 21:47:46 +0300 Subject: [PATCH 07/11] test: fuzz, CI and docs for the v0.5-v0.7 decoders - decode_legacy fuzz target: input kept on a v0.5-v0.7 magic number, decoded whole into a slice and through the stream, and the two outputs must agree; CI runs it 90 s seeded with zstd/legacy_frames. - CI: no_std clippy and the x86_64-unknown-none build with `legacy`, and the decoding and command-line tests again with the feature off. - Without `legacy` a v0.5-v0.7 frame is refused as an unknown prefix by the queries and the decoder (test); the command-line test of these frames runs with the feature only. - Crate docs and README: the `legacy` feature and what it reads; the README feature table also marks dict-builder as on by default, as the manifest has it. Part of #131 --- .github/workflows/ci.yml | 20 +++++++ README.md | 5 +- zstd/fuzz/Cargo.toml | 4 ++ zstd/fuzz/fuzz_targets/decode_legacy.rs | 61 +++++++++++++++++++++ zstd/src/bin/structured-zstd/tests.rs | 1 + zstd/src/decoding/frame_inspection_tests.rs | 35 ++++++++++++ zstd/src/lib.rs | 5 ++ 7 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 zstd/fuzz/fuzz_targets/decode_legacy.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 176dfe1e..a43b1256 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -391,6 +391,13 @@ jobs: if: needs.changes.outputs.code == 'true' working-directory: zstd run: cargo nextest run --profile ci -p structured-zstd --features hash,std,dict-builder,caller-workspace + - name: Test (without legacy) + # The default build decodes v0.5-v0.7 frames; without the feature the + # decoders and the command line refuse them as an unknown format. The + # feature changes only those two, so their tests run again here. + if: needs.changes.outputs.code == 'true' + working-directory: zstd + run: cargo nextest run --profile ci -p structured-zstd --no-default-features --features hash,std,dict-builder -E 'test(/^decoding::/) | binary_id(structured-zstd::bin/structured-zstd)' - name: Doc tests if: needs.changes.outputs.code == 'true' run: cargo test --doc -p structured-zstd --features hash,std,dict-builder,caller-workspace @@ -559,6 +566,11 @@ jobs: - name: Clippy (no_std + caller-workspace) working-directory: zstd run: cargo clippy -p structured-zstd --no-default-features --features caller-workspace,hash -- -D warnings + # The v0.5-v0.7 decoders without `hash`, where their content checksum + # is not computed. + - name: Clippy (no_std + legacy) + working-directory: zstd + run: cargo clippy -p structured-zstd --no-default-features --features legacy -- -D warnings # The clippy steps above compile for the host, which has SSE2 and a # float unit. A kernel target has neither (x86_64-unknown-none is # -sse,-sse2,...,+soft-float), so a vector type that @@ -575,6 +587,7 @@ jobs: cargo build -p structured-zstd --lib --profile "$profile" --target x86_64-unknown-none --no-default-features --features hash cargo build -p structured-zstd --lib --profile "$profile" --target x86_64-unknown-none --no-default-features --features kernel-avx2,hash cargo build -p structured-zstd --lib --profile "$profile" --target x86_64-unknown-none --no-default-features --features caller-workspace,hash + cargo build -p structured-zstd --lib --profile "$profile" --target x86_64-unknown-none --no-default-features --features legacy,hash done wasm: @@ -1005,6 +1018,13 @@ jobs: # feature, which the targets above must not get. working-directory: zstd/fuzz run: cargo fuzz run --target "$FUZZ_TARGET_TRIPLE" --features workspace decode_workspace -- -max_total_time=90 -timeout=30 + - name: Fuzz decode_legacy (90s, seeded with the v0.5-v0.7 frames) + # Seeded from `zstd/legacy_frames`: blind mutation rarely gets past + # these formats' entropy headers to their blocks. + working-directory: zstd/fuzz + run: | + mkdir -p corpus/decode_legacy + cargo fuzz run --target "$FUZZ_TARGET_TRIPLE" decode_legacy corpus/decode_legacy ../legacy_frames -- -max_total_time=90 -timeout=30 - name: Upload new crash artifacts on failure if: failure() uses: actions/upload-artifact@v7 diff --git a/README.md b/README.md index 2282d4f9..51e2fbeb 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## Highlights -- **Production-grade decoder** — complete [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878) implementation: dictionary-backed streams, raw / RLE / compressed blocks, the full frame format, optional content checksums, runtime-dispatched SIMD kernels (SSE2 / BMI2 / AVX2 / NEON, opt-in AVX-512). +- **Production-grade decoder** — complete [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878) implementation: dictionary-backed streams, raw / RLE / compressed blocks, the full frame format, optional content checksums, runtime-dispatched SIMD kernels (SSE2 / BMI2 / AVX2 / NEON, opt-in AVX-512). It also reads the v0.5-v0.7 frame formats, as every libzstd 1.5.7 build does by default. - **Full-range encoder** — every C-zstd level (`-131072..=22`) produces valid frames decodable by this crate and by upstream C zstd; named presets, per-knob parameter overrides, long-distance matching, streaming via `std::io::Write`. - **Dictionaries end to end** — compress and decompress with the same dictionary format C zstd consumes; reusable parsed handles; pure-Rust COVER / FastCOVER / legacy training behind the `dict-builder` feature. - **Wire-compatible both ways** — frames interoperate with C zstd in either direction; interop is enforced in CI against the reference implementation. @@ -332,7 +332,8 @@ in pure Rust: | `kernel-simd128` | ✅ | WebAssembly SIMD kernel (needs `-C target-feature=+simd128`) | | `kernel-vbmi2` | ❌ | AVX-512 decode kernel (see note below) | | `kernel-scalar` | ✅ | Marker for the always-compiled scalar fallback | -| `dict-builder` | ❌ | Pure-Rust COVER / FastCOVER / legacy dictionary training | +| `dict-builder` | ✅ | Pure-Rust COVER / FastCOVER / legacy dictionary training | +| `legacy` | ✅ | Decoding of the v0.5, v0.6 and v0.7 frame formats; without it they are refused as an unknown format | | `lsm` | ❌ | [Storage-format extensions](#storage-format-extensions) | | `caller-workspace` | ❌ | [Decoding in caller-owned memory](#decoding-in-caller-owned-memory) | diff --git a/zstd/fuzz/Cargo.toml b/zstd/fuzz/Cargo.toml index 91e50ae5..e05f9824 100644 --- a/zstd/fuzz/Cargo.toml +++ b/zstd/fuzz/Cargo.toml @@ -31,6 +31,10 @@ members = ["."] name = "decode" path = "fuzz_targets/decode.rs" +[[bin]] +name = "decode_legacy" +path = "fuzz_targets/decode_legacy.rs" + [[bin]] name = "decode_direct" path = "fuzz_targets/decode_direct.rs" diff --git a/zstd/fuzz/fuzz_targets/decode_legacy.rs b/zstd/fuzz/fuzz_targets/decode_legacy.rs new file mode 100644 index 00000000..dd0e0bc4 --- /dev/null +++ b/zstd/fuzz/fuzz_targets/decode_legacy.rs @@ -0,0 +1,61 @@ +#![no_main] +#[macro_use] +extern crate libfuzzer_sys; + +use std::io::Read; + +use structured_zstd::decoding::{ + FrameDecoder, LegacyVersion, StreamingDecoder, frame_decompressed_bound, +}; + +/// Output either path may produce before the input is set aside: enough for +/// many blocks, small enough that a bomb costs nothing. +const OUTPUT_CAP: usize = 4 << 20; + +fuzz_target!(|data: &[u8]| { + // Keep the input on a v0.5-v0.7 frame: when it does not open with one of + // their magic numbers, its first byte picks one and the number replaces + // its first four bytes. The fixtures in `zstd/legacy_frames` seed it as + // they are. + if data.len() < 4 { + return; + } + let mut input = data.to_vec(); + if LegacyVersion::of(&input).is_none() { + let minor = 0x25 + input[0] % 3; + input[..4].copy_from_slice(&[minor, 0xB5, 0x2F, 0xFD]); + } + + // The whole-buffer path, into a slice the frame's bound sizes. + let Ok(bound) = frame_decompressed_bound(&input) else { + // A frame whose blocks do not walk still goes to the stream, which + // must refuse it without panicking. + drain(&input, OUTPUT_CAP); + return; + }; + let room = usize::try_from(bound).map_or(OUTPUT_CAP, |bound| bound.min(OUTPUT_CAP)); + let mut whole = vec![0u8; room]; + let decoded = FrameDecoder::new().decode_all(&input, &mut whole); + + // The streaming path, a block at a time with history dropped behind it. + let streamed = drain(&input, room + 1); + + // Both paths decode the same frames to the same bytes. + if let (Ok(len), Some(streamed)) = (decoded, streamed) { + assert!( + streamed == whole[..len], + "the stream and the whole-buffer decode disagree" + ); + } +}); + +/// Read up to `cap` bytes of `input` through a `StreamingDecoder`; `None` +/// when the stream fails. +fn drain(input: &[u8], cap: usize) -> Option> { + let mut out = Vec::new(); + StreamingDecoder::new(input) + .take(cap as u64) + .read_to_end(&mut out) + .ok()?; + Some(out) +} diff --git a/zstd/src/bin/structured-zstd/tests.rs b/zstd/src/bin/structured-zstd/tests.rs index 9674ad4a..b6071f77 100644 --- a/zstd/src/bin/structured-zstd/tests.rs +++ b/zstd/src/bin/structured-zstd/tests.rs @@ -1833,6 +1833,7 @@ fn decoding_names_the_foreign_format_it_cannot_read() { /// The v0.5-v0.7 frames the reference command decodes by default decode here /// too, and a v0.7 frame naming a dictionary it was not given fails as the /// reference's does. +#[cfg(feature = "legacy")] #[test] fn decoding_reads_the_v05_to_v07_formats() { let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("legacy_frames"); diff --git a/zstd/src/decoding/frame_inspection_tests.rs b/zstd/src/decoding/frame_inspection_tests.rs index e99ef0bd..594f1e56 100644 --- a/zstd/src/decoding/frame_inspection_tests.rs +++ b/zstd/src/decoding/frame_inspection_tests.rs @@ -12,6 +12,41 @@ fn frame(content: &[u8]) -> Vec { compress_slice_to_vec(content, CompressionLevel::Default) } +/// Built without `legacy`, a v0.5-v0.7 frame is no frame: every query and +/// the decoder refuse its magic number as an unknown prefix, the error a +/// libzstd built at legacy level 0 returns. +#[cfg(not(feature = "legacy"))] +#[test] +fn without_the_legacy_feature_older_frames_are_unknown() { + use crate::decoding::FrameDecoder; + use crate::decoding::errors::{ErrorCode, FrameDecoderError}; + let frame = include_bytes!("../../legacy_frames/abc.txt.v07.l1.zst"); + assert!(!super::is_frame(frame)); + assert!(matches!( + read_frame_content_size(frame), + Err(ReadFrameHeaderError::BadMagicNumber(0xFD2F_B527)) + )); + assert!(matches!( + find_frame_compressed_size(frame), + Err(FrameSizeError::Header( + ReadFrameHeaderError::BadMagicNumber(0xFD2F_B527) + )) + )); + let err = FrameDecoder::new() + .decode_all(frame, &mut [0u8; 64]) + .unwrap_err(); + assert_eq!(err.code(), ErrorCode::PrefixUnknown); + assert!( + matches!( + err, + FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::BadMagicNumber( + 0xFD2F_B527 + )) + ), + "{err}" + ); +} + /// A single-segment frame declaring `size` in an 8-byte Frame_Content_Size /// field, holding one empty last raw block (RFC 8878 3.1.1). fn frame_declaring(size: u64) -> Vec { diff --git a/zstd/src/lib.rs b/zstd/src/lib.rs index 547567d5..155ca7e4 100644 --- a/zstd/src/lib.rs +++ b/zstd/src/lib.rs @@ -13,6 +13,11 @@ //! * `decoding::workspace` (feature `caller-workspace`): streaming decoder //! that runs entirely in caller-owned memory, never allocating, with //! caller-set limits on window, output and work. +//! * v0.5, v0.6 and v0.7 frames (feature `legacy`, on by default): the +//! formats every libzstd 1.5.7 build reads by default decode through the +//! same entry points, the frame queries and the streaming decoder +//! included; `decoding::LegacyVersion` names them. Without the feature +//! they are refused as an unknown format. //! //! No FFI, no cmake, no system zstd. `no_std` builds are supported by //! disabling the default `std` feature. From ef7aa5f94a80601f6aec24f925a92780a8570520 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Tue, 20 Oct 2026 11:05:03 +0300 Subject: [PATCH 08/11] fix(decoding): apply decoder settings to v0.5-v0.7 frames - decode_from_to on a fresh decoder hands a v0.5-v0.7 frame its header started to the frame's stream instead of panicking - the content checksum mode applies to v0.7's 22-bit checksum: None computes nothing, EmitOnly exposes both values, Verify refuses a mismatch; get_checksum_from_data / get_calculated_checksum report them - set_max_block_size holds these frames' blocks to the limit on every path, and a block its header rules out (past the limit, or RLE in v0.5 and v0.6, which reserve the type as upstream's decoders do) needs only its header - a window ceiling lowered under a started frame refuses its next step - content_size and blocks_decoded describe the frame in progress - a reader error inside the header is returned in the field it hit - the per-format decoder keeps its entropy tables and literal buffer across frames, whole-buffer ones too, and workspace_size counts them - CI replays committed decode_legacy crash inputs before the fresh run - simd_copy tests name the kernel type without a trivial cast Regression tests cover each of the above. Part of #131 --- .github/workflows/ci.yml | 6 + zstd/src/decoding/frame_decoder.rs | 146 ++++++++-- zstd/src/decoding/legacy/entropy.rs | 68 ++++- zstd/src/decoding/legacy/mod.rs | 330 +++++++++++++++++---- zstd/src/decoding/legacy/tests.rs | 420 ++++++++++++++++++++++++--- zstd/src/decoding/legacy/v05.rs | 68 +++-- zstd/src/decoding/legacy/v06.rs | 59 ++-- zstd/src/decoding/legacy/v07.rs | 193 +++++++----- zstd/src/decoding/simd_copy/tests.rs | 4 +- 9 files changed, 1040 insertions(+), 254 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a43b1256..25c7b5e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1024,6 +1024,12 @@ jobs: working-directory: zstd/fuzz run: | mkdir -p corpus/decode_legacy + # Crash inputs committed for this target replay first, as the + # other targets' do. + shopt -s dotglob + if [ -d artifacts/decode_legacy ] && [ -n "$(find artifacts/decode_legacy -mindepth 1 -maxdepth 1 -print -quit)" ]; then + cargo fuzz run --target "$FUZZ_TARGET_TRIPLE" decode_legacy artifacts/decode_legacy/* + fi cargo fuzz run --target "$FUZZ_TARGET_TRIPLE" decode_legacy corpus/decode_legacy ../legacy_frames -- -max_total_time=90 -timeout=30 - name: Upload new crash artifacts on failure if: failure() diff --git a/zstd/src/decoding/frame_decoder.rs b/zstd/src/decoding/frame_decoder.rs index b97feed9..e826a861 100644 --- a/zstd/src/decoding/frame_decoder.rs +++ b/zstd/src/decoding/frame_decoder.rs @@ -1341,22 +1341,32 @@ impl FrameDecoder { source: &mut R, dicts: &[DictionaryHandle], ) -> Result<(), FrameDecoderError> { - use crate::decoding::errors::LegacyFrameError; - use crate::decoding::legacy::{self, LegacyStream}; + use crate::decoding::errors::ReadFrameHeaderError as ReadError; + use crate::decoding::legacy::{self, HeaderFields, LegacyStream}; let version = crate::decoding::LegacyVersion::of(&magic.to_le_bytes()) .expect("the caller checked the magic number"); // The longest header: magic, descriptor, window, dictionary ID and an // eight-byte content size. let mut bytes = [0u8; 18]; bytes[..4].copy_from_slice(&magic.to_le_bytes()); + // A reader's own error is handed back in the field it failed in, as + // for an RFC 8878 header. source .read_exact(&mut bytes[4..5]) - .map_err(|_| LegacyFrameError::Truncated)?; - let len = legacy::header_len(version, &bytes[..5])?; - source - .read_exact(&mut bytes[5..len]) - .map_err(|_| LegacyFrameError::Truncated)?; - let header = legacy::read_header(&bytes[..len])?; + .map_err(ReadError::FrameDescriptorReadError)?; + let fields = HeaderFields::of(version, bytes[4]); + let mut at = 5; + let mut read_field = |len: usize, failed: fn(Error) -> ReadError| { + source + .read_exact(&mut bytes[at..at + len]) + .map_err(failed)?; + at += len; + Ok::<_, ReadError>(()) + }; + read_field(fields.window, ReadError::WindowDescriptorReadError)?; + read_field(fields.dict_id, ReadError::DictionaryIdReadError)?; + read_field(fields.content_size, ReadError::FrameContentSizeReadError)?; + let header = legacy::read_header(&bytes[..at])?; if header.window > self.max_window_size { return Err(FrameDecoderError::WindowSizeTooBig { requested: header.window, @@ -1366,12 +1376,23 @@ impl FrameDecoder { // The stream outlives this call, so it holds the handle: one count, // once per frame. let dict = self.legacy_frame_dict(header.dict_id, dicts)?.cloned(); + let options = self.legacy_frame_options(); self.legacy .get_or_insert_with(|| alloc::boxed::Box::new(LegacyStream::new())) - .start(header, dict)?; + .start(header, dict, options)?; Ok(()) } + /// What the decoder's settings ask of a v0.5-v0.7 frame. + #[cfg(feature = "legacy")] + fn legacy_frame_options(&self) -> decoding::legacy::FrameOptions { + decoding::legacy::FrameOptions { + #[cfg(feature = "hash")] + checksum: self.content_checksum, + block_max: self.max_block_size, + } + } + /// Decode with a named CPU tier instead of the one this machine selected. /// /// Which tier runs is otherwise a property of the host, so a test on any @@ -1629,6 +1650,10 @@ impl FrameDecoder { { state.window_refused_at = (window > size).then_some(size); } + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + stream.set_window_ceiling(size); + } Ok(()) } @@ -1636,6 +1661,15 @@ impl FrameDecoder { /// window after it started (see [`Self::set_max_window_size`]). Every /// entry that decodes a step of the frame asks first. pub(crate) fn check_window_still_admitted(&self) -> Result<(), FrameDecoderError> { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return match stream.window_refusal() { + Some((requested, limit)) => { + Err(FrameDecoderError::WindowSizeTooBig { requested, limit }) + } + None => Ok(()), + }; + } if let Some(state) = self.state.as_ref() && let Some(limit) = state.window_refused_at { @@ -2637,6 +2671,10 @@ impl FrameDecoder { /// Returns how many bytes the frame contains after decompression pub fn content_size(&self) -> u64 { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.content_size(); + } match &self.state { None => 0, Some(s) => s.frame_header.frame_content_size(), @@ -2644,7 +2682,13 @@ impl FrameDecoder { } /// Returns the checksum that was read from the data. Only available after all bytes have been read. It is the last 4 bytes of a zstd-frame + /// (of a v0.7 frame, the 22 bits its end block holds, read where the + /// checksum is computed). pub fn get_checksum_from_data(&self) -> Option { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.stored_checksum(); + } let state = self.state.as_ref()?; state.check_sum @@ -2658,6 +2702,10 @@ impl FrameDecoder { /// frame format declares no trailing digest avoids that wasted work). #[cfg(feature = "hash")] pub fn get_calculated_checksum(&self) -> Option { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.calculated_checksum(); + } let state = self.state.as_ref()?; // `ContentChecksum::None` skips the XXH64 pass entirely, so there is // no calculated digest to report. @@ -2833,6 +2881,10 @@ impl FrameDecoder { /// Counter for how many blocks have already been decoded pub fn blocks_decoded(&self) -> usize { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.blocks_decoded(); + } let state = match &self.state { None => return 0, Some(s) => s, @@ -2850,6 +2902,7 @@ impl FrameDecoder { strat: BlockDecodingStrategy, ) -> Result { use FrameDecoderError as err; + self.check_window_still_admitted()?; #[cfg(feature = "legacy")] if let Some(stream) = self.legacy_frame_mut() { let produced_before = stream.produced(); @@ -2870,7 +2923,6 @@ impl FrameDecoder { } return Ok(stream.is_finished()); } - self.check_window_still_admitted()?; // Apply the content-checksum mode to the streaming drain hash before // any block decodes into the ring. Hash only when a digest is both // wanted (mode != None) AND present in the frame (content_checksum_flag @@ -3507,11 +3559,11 @@ impl FrameDecoder { reserve_window: bool, ) -> Result<(usize, usize), FrameDecoderError> { use FrameDecoderError as err; + self.check_window_still_admitted()?; #[cfg(feature = "legacy")] if let Some(stream) = self.legacy_frame_mut() { return Ok(stream.decode(source, target)?); } - self.check_window_still_admitted()?; let bytes_read_at_start = match &self.state { Some(s) => s.bytes_read_counter, None => 0, @@ -3524,6 +3576,14 @@ impl FrameDecoder { if self.state.is_none() { self.init(&mut mt_source)?; + // The header began a v0.5-v0.7 frame: its stream decodes the + // rest, the header counted in what was read. + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame_mut() { + let header = source.len() - mt_source.len(); + let (read, written) = stream.decode(mt_source, target)?; + return Ok((header + read, written)); + } } //pseudo block to scope "state" so we can borrow self again after the block @@ -4121,57 +4181,91 @@ impl FrameDecoder { /// one registered under it. A frame naming none takes the last of `dicts`. #[cfg(feature = "legacy")] fn decode_legacy_frame( - &self, + &mut self, input: &mut &[u8], output: &mut [u8], dicts: &[DictionaryHandle], ) -> Result { use crate::decoding::errors::LegacyFrameError; - use crate::decoding::legacy::{self, SliceOutput}; - let header = legacy::read_header(input)?; - let dict = self.legacy_frame_dict(header.dict_id, dicts)?; + use crate::decoding::legacy::SliceOutput; let mut out = SliceOutput { buf: output, pos: 0, }; - let consumed = - legacy::decode_frame(input, legacy_dict(dict), &mut out).map_err(|err| match err { - LegacyFrameError::OutputTooSmall => FrameDecoderError::TargetTooSmall, - err => err.into(), + let consumed = self + .decode_whole_legacy_frame(input, dicts, &mut out) + .map_err(|err| match err { + FrameDecoderError::LegacyFrame(LegacyFrameError::OutputTooSmall) => { + FrameDecoderError::TargetTooSmall + } + err => err, })?; *input = &input[consumed..]; Ok(out.pos) } + /// Decode the v0.5-v0.7 frame starting `input` into `out`, with the + /// dictionary [`Self::legacy_frame_dict`] picks from `dicts`, in the + /// stream's decoder so its tables are reused from frame to frame. + /// Returns the bytes the frame took. + #[cfg(feature = "legacy")] + fn decode_whole_legacy_frame( + &mut self, + input: &[u8], + dicts: &[DictionaryHandle], + out: &mut O, + ) -> Result { + use crate::decoding::legacy::{self, LegacyStream}; + // Taken out while the dictionary is borrowed from the decoder. + let mut stream = self + .legacy + .take() + .unwrap_or_else(|| alloc::boxed::Box::new(LegacyStream::new())); + let decoded = legacy::read_header(input) + .map_err(FrameDecoderError::from) + .and_then(|header| self.legacy_frame_dict(header.dict_id, dicts)) + .and_then(|dict| { + Ok(stream.decode_whole( + input, + legacy_dict(dict), + self.legacy_frame_options(), + out, + )?) + }); + self.legacy = Some(stream); + decoded + } + /// Decode the v0.5-v0.7 frame starting `*input`, appending to `output` /// and advancing `*input` past it. With `keep_dictionary` it takes the /// dictionary the decoder holds, else the registry's. #[cfg(feature = "legacy")] fn decode_legacy_frame_to_vec( - &self, + &mut self, input: &mut &[u8], output: &mut Vec, keep_dictionary: bool, ) -> Result<(), FrameDecoderError> { - use crate::decoding::legacy::{self, VecOutput}; + use crate::decoding::legacy::VecOutput; + // A handle count per frame: the held dictionary is borrowed from the + // decoder, which the decode borrows mutably. let held = if keep_dictionary { self.state .as_ref() .and_then(|state| state.active_dict.as_ref()) .or(self.armed_dict.as_ref()) + .cloned() } else { None }; - let dicts = held.map_or(&[][..], core::slice::from_ref); - let header = legacy::read_header(input)?; - let dict = self.legacy_frame_dict(header.dict_id, dicts)?; + let dicts = held.as_slice(); let start = output.len(); let mut out = VecOutput { buf: output, start, limit: usize::MAX, }; - let decoded = legacy::decode_frame(input, legacy_dict(dict), &mut out); + let decoded = self.decode_whole_legacy_frame(input, dicts, &mut out); match decoded { Ok(consumed) => { *input = &input[consumed..]; @@ -4180,7 +4274,7 @@ impl FrameDecoder { Err(err) => { // No byte of a frame that failed is handed out. output.truncate(start); - Err(err.into()) + Err(err) } } } diff --git a/zstd/src/decoding/legacy/entropy.rs b/zstd/src/decoding/legacy/entropy.rs index 7b0624cb..87722471 100644 --- a/zstd/src/decoding/legacy/entropy.rs +++ b/zstd/src/decoding/legacy/entropy.rs @@ -240,6 +240,7 @@ impl FseTable { } /// The table a distribution spreads (`FSEv07_buildDTable`). + #[cfg(test)] pub(super) fn new(dist: &Distribution) -> Result { let mut table = FseTable::empty(); table.build(dist)?; @@ -331,6 +332,43 @@ impl FseTable { self.log = 0; self.fast = false; } + + /// Heap bytes the table holds. + pub(super) fn heap_bytes(&self) -> usize { + self.cells.capacity() * core::mem::size_of::() + } +} + +/// A table a later block may use again, with whether one is set: unsetting +/// it keeps its storage for the next table built into it. +pub(super) struct Slot { + pub(super) table: T, + pub(super) set: bool, +} + +impl Slot { + pub(super) const fn empty() -> Self { + Slot { + table: FseTable::empty(), + set: false, + } + } +} + +impl Slot { + pub(super) const fn empty() -> Self { + Slot { + table: HufTable::empty(), + set: false, + } + } +} + +impl Slot { + /// The table, once one is set. + pub(super) fn get(&self) -> Option<&T> { + self.set.then_some(&self.table) + } } /// A position in an [`FseTable`]. @@ -466,17 +504,23 @@ impl HufTable { } } - /// Read a Huffman table description (`HUFv07_readStats` and - /// `HUFv07_readDTableX2`), its weights FSE-coded with the ending `end`. - /// Returns the table and the bytes read. + /// [`Self::read_into`] a new table. Returns the table and the bytes read. + #[cfg(test)] pub(super) fn read(src: &[u8], end: StreamEnd) -> Result<(HufTable, usize), Error> { let mut table = HufTable::empty(); let consumed = table.read_into(src, end)?; Ok((table, consumed)) } - /// [`Self::read`] into this table's own storage, as each block with a - /// new description replaces the table. Returns the bytes read. + /// Heap bytes the table holds, its weight table's included. + pub(super) fn heap_bytes(&self) -> usize { + self.cells.capacity() * core::mem::size_of::<(u8, u8)>() + self.weight_table.heap_bytes() + } + + /// Read a Huffman table description (`HUFv07_readStats` and + /// `HUFv07_readDTableX2`), its weights FSE-coded with the ending `end`, + /// into this table's own storage, as each block with a new description + /// replaces the table. Returns the bytes read. pub(super) fn read_into(&mut self, src: &[u8], end: StreamEnd) -> Result { let &header = src.first().ok_or(Error::Truncated)?; let mut weights = [0u8; 256]; @@ -507,9 +551,13 @@ impl HufTable { Ok(consumed) } - /// The table whose weights are an RFC 8878 description's code lengths, - /// the last one included, under a longest code of `max_bits`. - pub(super) fn from_code_lengths(lengths: &[u8], max_bits: u8) -> Result { + /// Build the table whose weights are an RFC 8878 description's code + /// lengths, the last one included, under a longest code of `max_bits`. + pub(super) fn fill_from_code_lengths( + &mut self, + lengths: &[u8], + max_bits: u8, + ) -> Result<(), Error> { let explicit = lengths .len() .checked_sub(1) @@ -523,9 +571,7 @@ impl HufTable { *weight = if len == 0 { 0 } else { max_bits + 1 - len }; } // The last weight is inferred again, from the others. - let mut table = HufTable::empty(); - table.fill(&mut weights, explicit)?; - Ok(table) + self.fill(&mut weights, explicit) } /// Build the table from the first `explicit` weights, inferring the diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index 9e4d255b..bb9e7d74 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -14,6 +14,8 @@ mod v05; mod v06; mod v07; +#[cfg(feature = "hash")] +use crate::decoding::ContentChecksum; use crate::decoding::Dictionary; use crate::decoding::errors::LegacyFrameError as Error; use alloc::vec::Vec; @@ -118,9 +120,7 @@ pub(crate) struct FrameHeader { pub(crate) content_size: Option, /// The dictionary the frame names, 0 for none. pub(crate) dict_id: u32, - /// Whether a v0.7 frame carries a content checksum. Read only where the - /// checksum can be computed, under `hash`; the header parses it always. - #[cfg_attr(not(feature = "hash"), allow(dead_code))] + /// Whether a v0.7 frame carries a content checksum. pub(crate) checksum: bool, } @@ -171,6 +171,21 @@ impl Block { Block::End => 0, } } + + /// Refuse a block its header alone rules out: an RLE block in v0.5 or + /// v0.6, which reserve the type (`ZSTDv0{5,6}_decompressFrame` fail on + /// `bt_rle`), and a body past `block_max`. + fn check(self, version: LegacyVersion, block_max: usize) -> Result<(), Error> { + if let Block::Rle(_) = self + && version != LegacyVersion::V07 + { + return Err(corrupt("reserved block type")); + } + if self.body_len() > block_max { + return Err(corrupt("block larger than the block maximum")); + } + Ok(()) + } } /// Whether `block` ends the frame. v0.5 and v0.6 end it at any block whose @@ -188,18 +203,45 @@ fn ends_frame(version: LegacyVersion, block: Block) -> bool { /// (`ZSTDv0{6,7}_frameHeaderSize`; v0.5 headers are always 5 bytes). pub(crate) fn header_len(version: LegacyVersion, src: &[u8]) -> Result { let descriptor = *src.get(4).ok_or(Error::Truncated)?; - Ok(match version { - LegacyVersion::V05 => 5, - LegacyVersion::V06 => 5 + [0, 1, 2, 8][usize::from(descriptor >> 6)], - LegacyVersion::V07 => { - let single_segment = descriptor & 0x20 != 0; - let fcs = [0, 2, 4, 8][usize::from(descriptor >> 6)]; - let window = usize::from(!single_segment); - // A single-segment frame always carries its size, one byte at least. - let short_size = usize::from(single_segment && fcs == 0); - 5 + window + [0, 1, 2, 4][usize::from(descriptor & 3)] + fcs + short_size + let fields = HeaderFields::of(version, descriptor); + Ok(5 + fields.window + fields.dict_id + fields.content_size) +} + +/// The lengths of the fields that follow a frame header's descriptor byte, +/// in the order they follow it. +#[derive(Debug, Clone, Copy)] +pub(crate) struct HeaderFields { + pub(crate) window: usize, + pub(crate) dict_id: usize, + pub(crate) content_size: usize, +} + +impl HeaderFields { + pub(crate) fn of(version: LegacyVersion, descriptor: u8) -> HeaderFields { + match version { + LegacyVersion::V05 => HeaderFields { + window: 0, + dict_id: 0, + content_size: 0, + }, + LegacyVersion::V06 => HeaderFields { + window: 0, + dict_id: 0, + content_size: [0, 1, 2, 8][usize::from(descriptor >> 6)], + }, + LegacyVersion::V07 => { + let single_segment = descriptor & 0x20 != 0; + let fcs = [0, 2, 4, 8][usize::from(descriptor >> 6)]; + HeaderFields { + window: usize::from(!single_segment), + dict_id: [0, 1, 2, 4][usize::from(descriptor & 3)], + // A single-segment frame always carries its size, one + // byte at least. + content_size: fcs + usize::from(single_segment && fcs == 0), + } + } } - }) + } } /// Where the `version` frame starting `src` ends, and the most it can decode @@ -538,13 +580,52 @@ enum LiteralSource { Buffer, } -/// The per-format state of a frame being decoded. +/// The per-format state of a frame being decoded: its entropy tables and +/// literal buffer, kept from frame to frame so their storage is reused. enum VersionDecoder { V05(v05::Decoder), V06(v06::Decoder), V07(v07::Decoder), } +impl VersionDecoder { + fn version(&self) -> LegacyVersion { + match self { + VersionDecoder::V05(_) => LegacyVersion::V05, + VersionDecoder::V06(_) => LegacyVersion::V06, + VersionDecoder::V07(_) => LegacyVersion::V07, + } + } + + fn heap_bytes(&self) -> usize { + match self { + VersionDecoder::V05(decoder) => decoder.heap_bytes(), + VersionDecoder::V06(decoder) => decoder.heap_bytes(), + VersionDecoder::V07(decoder) => decoder.heap_bytes(), + } + } +} + +/// What the caller set on the decoder that a frame of these formats obeys. +#[derive(Debug, Clone, Copy)] +pub(crate) struct FrameOptions { + /// Without `hash` no checksum is computed, whatever the mode. + #[cfg(feature = "hash")] + pub(crate) checksum: ContentChecksum, + /// Most bytes a block may hold, compressed or decoded. + pub(crate) block_max: usize, +} + +impl Default for FrameOptions { + fn default() -> FrameOptions { + FrameOptions { + #[cfg(feature = "hash")] + checksum: ContentChecksum::EmitOnly, + block_max: BLOCK_SIZE_MAX, + } + } +} + /// An output that refuses to grow past `end` produced bytes: a block decodes /// to at most [`BLOCK_SIZE_MAX`], however many sequences it holds. struct Budget<'o, O: Output> { @@ -586,53 +667,91 @@ impl Output for Budget<'_, O> { /// A frame being decoded block by block: the whole-buffer and the streaming /// decoders both drive one. The dictionary it was started with is passed to /// each block again, as [`LegacyDict::content_bytes`], so the frame holds no -/// borrow and a stream can keep it beside the dictionary's handle. -pub(crate) struct LegacyFrame { +/// borrow and a stream can keep it beside the dictionary's handle; the +/// per-format decoder is passed too, so it outlives the frame. +struct LegacyFrame { version: LegacyVersion, - decoder: VersionDecoder, /// Where the dictionary's content lies within its bytes. content: core::ops::Range, + /// Most bytes a block may hold, the caller's limit within the formats'. + block_max: usize, + #[cfg(feature = "hash")] + checksum_mode: ContentChecksum, + /// The declared content size, 0 when the frame declares none. + content_size: u64, + /// Data blocks decoded so far. + blocks: usize, + /// Whether the end block holds a content checksum. + has_checksum: bool, + /// The checksum the end block stores, once read. + stored_checksum: Option, + /// The checksum the content gives, once the end block is read and the + /// mode computes one. + #[cfg(feature = "hash")] + calculated_checksum: Option, #[cfg(feature = "hash")] hash: Option, } impl LegacyFrame { - /// Start decoding the frame `header` describes. - pub(crate) fn new(header: FrameHeader, dict: LegacyDict<'_>) -> Result { + /// Start decoding the frame `header` describes, readying `decoder` for + /// it: the one the previous frame left, when of the same format. + fn new( + header: FrameHeader, + dict: LegacyDict<'_>, + options: FrameOptions, + decoder: &mut Option, + ) -> Result { + if decoder.as_ref().map(VersionDecoder::version) != Some(header.version) { + *decoder = Some(match header.version { + LegacyVersion::V05 => VersionDecoder::V05(v05::Decoder::empty()), + LegacyVersion::V06 => VersionDecoder::V06(v06::Decoder::empty()), + LegacyVersion::V07 => VersionDecoder::V07(v07::Decoder::empty()), + }); + } let bytes = dict.content_bytes(); - let (decoder, content) = match header.version { - LegacyVersion::V07 => { - let (decoder, content) = v07::Decoder::new(dict)?; + let content = match decoder.as_mut().expect("set above") { + VersionDecoder::V07(decoder) => { + let content = decoder.reset(dict)?; if header.dict_id != 0 && header.dict_id != decoder.dict_id { return Err(Error::DictionaryMismatch { expected: header.dict_id, provided: decoder.dict_id, }); } - (VersionDecoder::V07(decoder), content) - } - LegacyVersion::V06 => { - let (decoder, content) = v06::Decoder::new(bytes)?; - (VersionDecoder::V06(decoder), content) - } - LegacyVersion::V05 => { - let (decoder, content) = v05::Decoder::new(bytes)?; - (VersionDecoder::V05(decoder), content) + content } + VersionDecoder::V06(decoder) => decoder.reset(bytes)?, + VersionDecoder::V05(decoder) => decoder.reset(bytes)?, }; + // Without `hash` no digest can be computed, so `Verify` checks + // nothing, as for an RFC 8878 frame. + #[cfg(feature = "hash")] + let hash = (header.checksum && options.checksum != ContentChecksum::None) + .then(|| twox_hash::XxHash64::with_seed(0)); Ok(LegacyFrame { version: header.version, - decoder, content, + block_max: options.block_max.min(BLOCK_SIZE_MAX), + #[cfg(feature = "hash")] + checksum_mode: options.checksum, + content_size: header.content_size.unwrap_or(0), + blocks: 0, + has_checksum: header.checksum, + stored_checksum: None, #[cfg(feature = "hash")] - hash: header.checksum.then(|| twox_hash::XxHash64::with_seed(0)), + calculated_checksum: None, + #[cfg(feature = "hash")] + hash, }) } /// Decode one block, `body` being the bytes after its header, with the - /// bytes of the dictionary the frame was started with. + /// bytes of the dictionary the frame was started with. The block has + /// passed [`Block::check`]. fn block( &mut self, + decoder: &mut VersionDecoder, block: Block, body: &[u8], out: &mut O, @@ -640,12 +759,12 @@ impl LegacyFrame { ) -> Result<(), Error> { let before = out.produced(); let mut out = Budget { - end: before + BLOCK_SIZE_MAX, + end: before + self.block_max, out, }; let dict = &dict_bytes[self.content.clone()]; match block { - Block::Compressed(_) => match &mut self.decoder { + Block::Compressed(_) => match decoder { VersionDecoder::V05(decoder) => decoder.block(body, &mut out, dict)?, VersionDecoder::V06(decoder) => decoder.block(body, &mut out, dict)?, VersionDecoder::V07(decoder) => decoder.block(body, &mut out, dict)?, @@ -665,14 +784,13 @@ impl LegacyFrame { use core::hash::Hasher; hash.write(out.since(before)); } + self.blocks += 1; Ok(()) } /// Close the frame at its end block, whose header is `end`: the content - /// checksum a v0.7 frame keeps there. - /// - /// Upstream checks it only while streaming; a frame failing it is - /// corrupt whichever way it is read, so it is checked here for both. + /// checksum a v0.7 frame keeps there, held against the content as the + /// caller's [`ContentChecksum`] mode says. /// /// The declared content size is not checked against the output. It is /// no integrity field in these formats: the v0.6.2 command line writes @@ -681,39 +799,47 @@ impl LegacyFrame { /// such an archive whole; its one-shot `ZSTD_decompress` calls it /// corrupt, which would refuse intact data. fn finish(&mut self, end: [u8; 3]) -> Result<(), Error> { + // Only a v0.7 header announces a checksum; with none the end block's + // size bits are 0 and nothing reads them. + if !self.has_checksum { + return Ok(()); + } + let expected = u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16; + self.stored_checksum = Some(expected); #[cfg(feature = "hash")] if let Some(hash) = &self.hash { use core::hash::Hasher; - let expected = - u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16; let calculated = ((hash.finish() >> 11) & 0x3F_FFFF) as u32; - if expected != calculated { + self.calculated_checksum = Some(calculated); + if self.checksum_mode == ContentChecksum::Verify && expected != calculated { return Err(Error::ChecksumMismatch { expected, calculated, }); } } - #[cfg(not(feature = "hash"))] - let _ = end; Ok(()) } } -/// Decode the frame starting `src` into `out`. Returns the bytes the frame -/// took. -pub(crate) fn decode_frame( +/// Decode the frame starting `src` into `out`, in `decoder`'s storage. +/// Returns the bytes the frame took. +fn decode_frame( src: &[u8], dict: LegacyDict<'_>, + options: FrameOptions, + decoder: &mut Option, out: &mut O, ) -> Result { let header = read_header(src)?; - let mut frame = LegacyFrame::new(header, dict)?; + let mut frame = LegacyFrame::new(header, dict, options, decoder)?; + let decoder = decoder.as_mut().expect("the frame readied it"); let dict_bytes = dict.content_bytes(); let mut at = header.size; loop { let block_header = src.get(at..at + 3).ok_or(Error::Truncated)?; let block = Block::read(block_header)?; + block.check(header.version, frame.block_max)?; let end = [block_header[0], block_header[1], block_header[2]]; at += 3; let body = src.get(at..at + block.body_len()).ok_or(Error::Truncated)?; @@ -721,7 +847,7 @@ pub(crate) fn decode_frame( frame.finish(end)?; return Ok(at); } - frame.block(block, body, out, dict_bytes)?; + frame.block(decoder, block, body, out, dict_bytes)?; at += body.len(); } } @@ -734,8 +860,15 @@ pub(crate) fn decode_frame( pub(crate) struct LegacyStream { /// The frame in progress; `None` when no frame is. frame: Option, + /// The per-format decoder, kept across frames, whole-buffer ones too. + decoder: Option, /// The dictionary the frame was started with. dict: Option, + /// The window the frame needs. + window: u64, + /// The window ceiling, once the caller lowered it below `window` after + /// the frame started; the next step refuses the frame. + window_refused_at: Option, /// The output segments: the current one at `..end`, and past it what is /// left of the one before. buf: Vec, @@ -762,7 +895,10 @@ impl LegacyStream { pub(crate) const fn new() -> LegacyStream { LegacyStream { frame: None, + decoder: None, dict: None, + window: 0, + window_refused_at: None, buf: Vec::new(), end: 0, earlier: 0, @@ -781,12 +917,21 @@ impl LegacyStream { &mut self, header: FrameHeader, dict: Option, + options: FrameOptions, ) -> Result<(), Error> { + self.frame = None; let legacy_dict = dict .as_ref() .map_or(LegacyDict::None, |dict| LegacyDict::Parsed(dict.as_dict())); - self.frame = Some(LegacyFrame::new(header, legacy_dict)?); + self.frame = Some(LegacyFrame::new( + header, + legacy_dict, + options, + &mut self.decoder, + )?); self.dict = dict; + self.window = header.window; + self.window_refused_at = None; self.buf.clear(); self.end = 0; self.earlier = 0; @@ -801,16 +946,68 @@ impl LegacyStream { Ok(()) } - /// Leave the frame in progress, if any, keeping the buffer's capacity. + /// Leave the frame in progress, if any, keeping the buffer's capacity + /// and the decoder's. pub(crate) fn end(&mut self) { self.frame = None; self.dict = None; + self.window_refused_at = None; self.buf.clear(); self.end = 0; self.delivered = 0; self.finished = false; } + /// Decode the whole frame starting `src` into `out`, in the stream's + /// decoder, leaving any frame the stream had in progress. Returns the + /// bytes the frame took. + pub(crate) fn decode_whole( + &mut self, + src: &[u8], + dict: LegacyDict<'_>, + options: FrameOptions, + out: &mut O, + ) -> Result { + self.end(); + decode_frame(src, dict, options, &mut self.decoder, out) + } + + /// The content size the frame declares, 0 when it declares none. + pub(crate) fn content_size(&self) -> u64 { + self.frame.as_ref().map_or(0, |frame| frame.content_size) + } + + /// Data blocks the frame has decoded so far. + pub(crate) fn blocks_decoded(&self) -> usize { + self.frame.as_ref().map_or(0, |frame| frame.blocks) + } + + /// The checksum the frame's end block stores, once read. + pub(crate) fn stored_checksum(&self) -> Option { + self.frame.as_ref()?.stored_checksum + } + + /// The checksum the frame's content gives, once its end block is read + /// and the checksum mode computes one. + #[cfg(feature = "hash")] + pub(crate) fn calculated_checksum(&self) -> Option { + self.frame.as_ref()?.calculated_checksum + } + + /// Take a new window ceiling: a frame whose window it no longer admits + /// is refused at its next step, one it admits again goes on. + pub(crate) fn set_window_ceiling(&mut self, limit: u64) { + if self.frame.is_some() && !self.finished { + self.window_refused_at = (self.window > limit).then_some(limit); + } + } + + /// The frame's window and the ceiling it was refused at, when lowered + /// below it after the frame started. + pub(crate) fn window_refusal(&self) -> Option<(u64, u64)> { + Some((self.window, self.window_refused_at?)) + } + /// Whether a frame is in progress, or finished with output still to hand /// out. pub(crate) fn active(&self) -> bool { @@ -837,20 +1034,28 @@ impl LegacyStream { self.consumed } - /// Heap bytes the stream holds. + /// Heap bytes the stream holds: its buffers and the decoder's tables and + /// literal buffer. pub(crate) fn heap_bytes(&self) -> usize { - self.buf.capacity() + self.block.capacity() + self.buf.capacity() + + self.block.capacity() + + self.decoder.as_ref().map_or(0, VersionDecoder::heap_bytes) } /// How many bytes from the start of `pending` the next step needs: a - /// block header, then the whole block; 0 once the frame is finished. + /// block header, then the whole block; 0 once the frame is finished. A + /// block its header rules out needs only the header, whose step then + /// refuses it. pub(crate) fn input_needed(&self, pending: &[u8]) -> usize { if self.finished { return 0; } - match Block::read(pending) { - Ok(block) => 3 + block.body_len(), - Err(_) => 3, + let Ok(block) = Block::read(pending) else { + return 3; + }; + match &self.frame { + Some(frame) if block.check(frame.version, frame.block_max).is_err() => 3, + _ => 3 + block.body_len(), } } @@ -936,7 +1141,10 @@ impl LegacyStream { fn step(&mut self, step: &[u8]) -> Result<(), Error> { let LegacyStream { frame, + decoder, dict, + window: _, + window_refused_at: _, buf, end, earlier, @@ -948,7 +1156,9 @@ impl LegacyStream { block: _, } = self; let frame = frame.as_mut().ok_or(corrupt("no frame in progress"))?; + let decoder = decoder.as_mut().expect("started with the frame"); let block = Block::read(step)?; + block.check(frame.version, frame.block_max)?; *consumed += step.len() as u64; if ends_frame(frame.version, block) { frame.finish([step[0], step[1], step[2]])?; @@ -977,7 +1187,7 @@ impl LegacyStream { previous_end: *previous_end, capacity: *capacity, }; - let decoded = frame.block(block, &step[3..], &mut out, dict_bytes); + let decoded = frame.block(decoder, block, &step[3..], &mut out, dict_bytes); *end = out.end; decoded } diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs index 9374535d..54375572 100644 --- a/zstd/src/decoding/legacy/tests.rs +++ b/zstd/src/decoding/legacy/tests.rs @@ -93,21 +93,44 @@ mod fixtures { use fixtures::all as fixtures; fn decode_to_vec(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec, usize), Error> { + decode_to_vec_in(frame, dict, FrameOptions::default(), &mut None) +} + +/// [`decode_to_vec`] refusing a frame whose content checksum does not match. +fn decode_verified(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec, usize), Error> { + let options = FrameOptions { + #[cfg(feature = "hash")] + checksum: crate::decoding::ContentChecksum::Verify, + ..FrameOptions::default() + }; + decode_to_vec_in(frame, dict, options, &mut None) +} + +/// [`decode_to_vec`] with `options`, in `decoder`, the storage an earlier +/// frame left. +fn decode_to_vec_in( + frame: &[u8], + dict: LegacyDict<'_>, + options: FrameOptions, + decoder: &mut Option, +) -> Result<(Vec, usize), Error> { let mut buf = Vec::new(); let mut out = VecOutput { buf: &mut buf, start: 0, limit: usize::MAX, }; - let consumed = decode_frame(frame, dict, &mut out)?; + let consumed = decode_frame(frame, dict, options, decoder, &mut out)?; Ok((buf, consumed)) } /// Each fixture names its own format and decodes byte-exact, taking exactly -/// its own bytes. +/// its own bytes: in a fresh decoder, and in one every fixture before it +/// left its tables in, whatever its format and dictionary. #[cfg(feature = "std")] #[test] fn fixtures_decode_to_their_content() { + let mut reused = None; for fixture in fixtures() { assert_eq!( LegacyVersion::of(&fixture.frame), @@ -115,14 +138,21 @@ fn fixtures_decode_to_their_content() { "{}", fixture.name ); - let (decoded, consumed) = decode_to_vec(&fixture.frame, fixture.legacy_dict()) + for decoder in [&mut None, &mut reused] { + let (decoded, consumed) = decode_to_vec_in( + &fixture.frame, + fixture.legacy_dict(), + FrameOptions::default(), + decoder, + ) .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); - assert_eq!(consumed, fixture.frame.len(), "{}", fixture.name); - assert!( - decoded == fixture.content, - "{} decodes to other bytes", - fixture.name - ); + assert_eq!(consumed, fixture.frame.len(), "{}", fixture.name); + assert!( + decoded == fixture.content, + "{} decodes to other bytes", + fixture.name + ); + } } } @@ -138,8 +168,14 @@ fn fixtures_decode_into_a_slice() { buf: &mut buf, pos: 0, }; - decode_frame(&fixture.frame, dict, &mut out) - .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); + decode_frame( + &fixture.frame, + dict, + FrameOptions::default(), + &mut None, + &mut out, + ) + .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); assert_eq!(out.pos, fixture.content.len(), "{}", fixture.name); assert!(buf == fixture.content, "{}", fixture.name); @@ -150,7 +186,13 @@ fn fixtures_decode_into_a_slice() { pos: 0, }; assert_eq!( - decode_frame(&fixture.frame, dict, &mut out), + decode_frame( + &fixture.frame, + dict, + FrameOptions::default(), + &mut None, + &mut out + ), Err(Error::OutputTooSmall), "{}", fixture.name @@ -519,9 +561,9 @@ fn truncated_frames_are_refused() { } /// Flipping any one bit of a frame is refused or decodes to some output, but -/// never panics; a v0.7 frame with a checksum decodes to other content only -/// when that content's checksum is the one the frame carries, which with 22 -/// bits of it does happen. +/// never panics; a v0.7 frame with a checksum, verified, decodes to other +/// content only when that content's checksum is the one the frame carries, +/// which with 22 bits of it does happen. #[cfg(feature = "std")] #[test] fn damaged_frames_never_panic() { @@ -531,7 +573,7 @@ fn damaged_frames_never_panic() { for byte in 0..frame.len() { for bit in 0..8 { frame[byte] ^= 1 << bit; - let decoded = decode_to_vec(&frame, dict); + let decoded = decode_verified(&frame, dict); #[cfg(feature = "hash")] if let Ok((content, end)) = &decoded && *content != fixture.content @@ -555,22 +597,20 @@ fn damaged_frames_never_panic() { } /// A v0.7 frame whose content checksum does not match is refused with the -/// checksum error. +/// checksum error when verified, and decodes when not. #[cfg(all(feature = "std", feature = "hash"))] #[test] fn a_wrong_v07_checksum_is_refused() { - let fixture = fixtures() - .into_iter() - .find(|f| f.name == "abc.txt.v07.l1.zst") - .unwrap(); + let fixture = fixture("abc.txt.v07.l1.zst"); let mut frame = fixture.frame.clone(); // The end block's header holds the checksum in its low 22 bits. let last = frame.len() - 1; frame[last] ^= 1; assert!(matches!( - decode_to_vec(&frame, LegacyDict::None), + decode_verified(&frame, LegacyDict::None), Err(Error::ChecksumMismatch { .. }) )); + assert!(decode_to_vec(&frame, LegacyDict::None).unwrap().0 == fixture.content); } /// The headers of the three formats: version, size, window and content size. @@ -704,7 +744,7 @@ fn a_stream_holds_its_window_not_the_frame() { frame.extend_from_slice(&[0xC0, 0, 0]); let header = read_header(&frame).unwrap(); let mut stream = LegacyStream::new(); - stream.start(header, None).unwrap(); + stream.start(header, None, FrameOptions::default()).unwrap(); let mut src = &frame[header.size..]; let mut out = Vec::new(); let mut target = vec![0u8; 50_000]; @@ -741,7 +781,7 @@ fn a_stream_reads_matches_from_the_segment_before() { fixture.name ); let mut stream = LegacyStream::new(); - stream.start(header, None).unwrap(); + stream.start(header, None, FrameOptions::default()).unwrap(); let mut src = &fixture.frame[header.size..]; let mut out = Vec::new(); let mut target = vec![0u8; 4096]; @@ -755,27 +795,341 @@ fn a_stream_reads_matches_from_the_segment_before() { } assert!(out == fixture.content, "{}", fixture.name); assert!( - stream.heap_bytes() <= capacity, + stream.buf.capacity() <= capacity, "{}: held {} bytes for a window of {}", fixture.name, - stream.heap_bytes(), + stream.buf.capacity(), header.window ); } } -/// An RLE block repeats its byte, in every format. +/// The fixture of this name. +#[cfg(feature = "std")] +fn fixture(name: &str) -> fixtures::Fixture { + fixtures() + .into_iter() + .find(|f| f.name == name) + .unwrap_or_else(|| panic!("no fixture {name}")) +} + +/// A fresh decoder handed one of these frames by `decode_from_to` starts it +/// itself, as it starts an RFC 8878 frame, and decodes it to its content. +#[cfg(feature = "std")] #[test] -fn rle_blocks_repeat_their_byte() { +fn decode_from_to_starts_these_frames_on_a_fresh_decoder() { + use crate::decoding::FrameDecoder; + for name in [ + "abc.txt.v05.l1.zst", + "z000001.v06.l19.zst", + "z000001.v07.l19.zst", + ] { + let fixture = fixture(name); + let mut decoder = FrameDecoder::new(); + let mut src = &fixture.frame[..]; + let mut out = Vec::new(); + let mut target = [0u8; 500]; + loop { + let (read, written) = decoder + .decode_from_to(src, &mut target) + .unwrap_or_else(|err| panic!("{name}: {err}")); + src = &src[read..]; + out.extend_from_slice(&target[..written]); + if decoder.is_finished() && decoder.can_collect() == 0 { + break; + } + assert!(read > 0 || written > 0, "{name} stalled"); + } + assert!(src.is_empty(), "{name}"); + assert!(out == fixture.content, "{name}"); + } +} + +/// The checksum mode applies to a v0.7 frame's 22-bit checksum as to an +/// RFC 8878 frame's: `Verify` refuses a frame whose content does not match +/// it, `EmitOnly` decodes it and exposes both values, `None` computes +/// nothing. +#[cfg(all(feature = "std", feature = "hash"))] +#[test] +fn the_checksum_mode_applies_to_v07_frames() { + use crate::decoding::errors::FrameDecoderError; + use crate::decoding::{BlockDecodingStrategy, ContentChecksum, FrameDecoder}; + let fixture = fixture("abc.txt.v07.l1.zst"); + let mut frame = fixture.frame.clone(); + let last = frame.len() - 1; + frame[last] ^= 1; + let end = &frame[frame.len() - 3..]; + let stored = u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16; + let run = |mode| { + let mut decoder = FrameDecoder::new(); + decoder.set_content_checksum(mode); + let mut src = &frame[..]; + decoder.reset(&mut src)?; + decoder.decode_blocks(&mut src, BlockDecodingStrategy::All)?; + let content = decoder.collect().unwrap(); + Ok::<_, FrameDecoderError>(( + content, + decoder.get_checksum_from_data(), + decoder.get_calculated_checksum(), + )) + }; + assert!(matches!( + run(ContentChecksum::Verify), + Err(FrameDecoderError::LegacyFrame( + Error::ChecksumMismatch { .. } + )) + )); + let (content, from_data, calculated) = run(ContentChecksum::EmitOnly).unwrap(); + assert!(content == fixture.content); + assert_eq!(from_data, Some(stored)); + assert!(calculated.is_some_and(|calculated| calculated != stored)); + let (content, _, calculated) = run(ContentChecksum::None).unwrap(); + assert!(content == fixture.content); + assert_eq!(calculated, None); + + // The same on the whole-buffer path. + let mut out = vec![0u8; fixture.content.len()]; + let mut decoder = FrameDecoder::new(); + decoder.set_content_checksum(ContentChecksum::Verify); + assert!(decoder.decode_all(&frame, &mut out).is_err()); + for mode in [ContentChecksum::EmitOnly, ContentChecksum::None] { + decoder.set_content_checksum(mode); + assert_eq!( + decoder.decode_all(&frame, &mut out).unwrap(), + fixture.content.len() + ); + } +} + +/// A block limit set on the decoder holds these frames' blocks to it, on +/// both paths. +#[cfg(feature = "std")] +#[test] +fn the_block_limit_applies_to_these_frames() { + use crate::decoding::errors::FrameDecoderError; + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + for name in [ + "z000000.v05.l1.zst", + "z000000.v06.l1.zst", + "z000000.v07.l1.zst", + ] { + let fixture = fixture(name); + let mut decoder = FrameDecoder::new(); + decoder.set_max_block_size(Some(1024)); + let mut out = vec![0u8; fixture.content.len()]; + assert!( + matches!( + decoder.decode_all(&fixture.frame, &mut out), + Err(FrameDecoderError::LegacyFrame(_)) + ), + "{name}" + ); + let mut src = &fixture.frame[..]; + decoder.reset(&mut src).unwrap(); + assert!( + matches!( + decoder.decode_blocks(&mut src, BlockDecodingStrategy::All), + Err(FrameDecoderError::LegacyFrame(_)) + ), + "{name}" + ); + decoder.set_max_block_size(None); + assert_eq!( + decoder.decode_all(&fixture.frame, &mut out).unwrap(), + out.len(), + "{name}" + ); + } +} + +/// A window ceiling lowered under a started frame's window refuses its next +/// step, as it refuses an RFC 8878 frame's. +#[cfg(feature = "std")] +#[test] +fn a_lowered_window_ceiling_binds_a_started_frame() { + use crate::decoding::errors::FrameDecoderError; + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + let fixture = fixture("z000000.v07.l1.zst"); + let window = read_header(&fixture.frame).unwrap().window; + let mut decoder = FrameDecoder::new(); + let mut src = &fixture.frame[..]; + decoder.reset(&mut src).unwrap(); + decoder.set_max_window_size(window / 2).unwrap(); + assert!(matches!( + decoder.decode_blocks(&mut src, BlockDecodingStrategy::All), + Err(FrameDecoderError::WindowSizeTooBig { requested, limit }) + if requested == window && limit == window / 2 + )); + let mut target = [0u8; 64]; + assert!(matches!( + decoder.decode_from_to(src, &mut target), + Err(FrameDecoderError::WindowSizeTooBig { .. }) + )); + // Raised again, the frame decodes on. + decoder.set_max_window_size(window).unwrap(); + decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap(); + assert!(decoder.collect().unwrap() == fixture.content); +} + +/// The decoder's accessors describe a started frame of these formats: its +/// declared content size, and the blocks decoded so far, not what the frame +/// before left. +#[cfg(feature = "std")] +#[test] +fn the_accessors_describe_these_frames() { + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + let rfc = crate::encoding::compress_slice_to_vec( + &[7u8; 500], + crate::encoding::CompressionLevel::Fastest, + ); + let mut decoder = FrameDecoder::new(); + let mut src = &rfc[..]; + decoder.reset(&mut src).unwrap(); + decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap(); + assert_eq!(decoder.content_size(), 500); + + // v0.6.2 declares its first chunk's size. + let v06 = fixture("z000000.v06.l1.zst"); + let mut src = &v06.frame[..]; + decoder.reset(&mut src).unwrap(); + assert_eq!(decoder.content_size(), 128 * 1024); + assert_eq!(decoder.blocks_decoded(), 0); + decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap(); + let (_, bound) = frame_size(LegacyVersion::V06, &v06.frame).unwrap(); + assert_eq!( + decoder.blocks_decoded() as u64, + bound / BLOCK_SIZE_MAX as u64 + ); + + // v0.5 declares none. + let v05 = fixture("abc.txt.v05.l1.zst"); + let mut src = &v05.frame[..]; + decoder.reset(&mut src).unwrap(); + assert_eq!(decoder.content_size(), 0); +} + +/// A reader failing after the magic number hands its own error back, in the +/// header field it failed in, rather than calling the frame truncated. +#[cfg(feature = "std")] +#[test] +fn reader_errors_inside_the_header_are_kept() { + use crate::decoding::FrameDecoder; + use crate::decoding::errors::{FrameDecoderError, ReadFrameHeaderError}; + struct Stalls<'a>(&'a [u8]); + impl std::io::Read for Stalls<'_> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if self.0.is_empty() { + return Err(std::io::ErrorKind::WouldBlock.into()); + } + let n = buf.len().min(self.0.len()); + buf[..n].copy_from_slice(&self.0[..n]); + self.0 = &self.0[n..]; + Ok(n) + } + } + let mut decoder = FrameDecoder::new(); + let err = decoder + .reset(Stalls(&[0x27, 0xB5, 0x2F, 0xFD])) + .unwrap_err(); + assert!( + matches!( + &err, + FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::FrameDescriptorReadError(e)) + if e.kind() == std::io::ErrorKind::WouldBlock + ), + "{err:?}" + ); + // v0.7 with a window byte still to come. + let err = decoder + .reset(Stalls(&[0x27, 0xB5, 0x2F, 0xFD, 0x00])) + .unwrap_err(); + assert!( + matches!( + &err, + FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::WindowDescriptorReadError(e)) + if e.kind() == std::io::ErrorKind::WouldBlock + ), + "{err:?}" + ); + // v0.6 with a two-byte content size still to come. + let err = decoder + .reset(Stalls(&[0x26, 0xB5, 0x2F, 0xFD, 0x80, 1])) + .unwrap_err(); + assert!( + matches!( + &err, + FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::FrameContentSizeReadError(e)) + if e.kind() == std::io::ErrorKind::WouldBlock + ), + "{err:?}" + ); +} + +/// The stream counts in its footprint the entropy tables and literal buffer +/// its frame holds, not only its output buffer. +#[cfg(feature = "std")] +#[test] +fn the_stream_footprint_counts_its_tables() { + let fixture = fixture("z000000.v07.l1.zst"); + let header = read_header(&fixture.frame).unwrap(); + let mut stream = LegacyStream::new(); + stream.start(header, None, FrameOptions::default()).unwrap(); + let mut target = vec![0u8; fixture.content.len()]; + stream + .decode(&fixture.frame[header.size..], &mut target) + .unwrap(); + assert!( + stream.heap_bytes() > stream.buf.capacity() + stream.block.capacity(), + "{} bytes held, {} of them the output buffer", + stream.heap_bytes(), + stream.buf.capacity() + ); +} + +/// A block whose header declares more than the largest block is refused from +/// the header alone: the stream asks for no more than it. +#[test] +fn an_oversized_block_is_refused_from_its_header() { + // v0.7 with a 1 KiB window. + let frame = [0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00]; + let header = read_header(&frame).unwrap(); + let mut stream = LegacyStream::new(); + stream.start(header, None, FrameOptions::default()).unwrap(); + // A raw block of 0x3FFFF bytes, compressed and raw alike. + for kind in [0x00u8, 0x40] { + let block = [kind | 0x03, 0xFF, 0xFF]; + assert_eq!(stream.input_needed(&block), 3); + assert!(stream.decode(&block, &mut [0u8; 16]).is_err()); + } +} + +/// An RLE block repeats its byte in v0.7; v0.5 and v0.6 reserve the type, +/// and their decoders refuse it (`ZSTDv0{5,6}_decompressFrame`). +#[test] +fn rle_blocks_repeat_their_byte_in_v07_only() { for magic in [0x25u8, 0x26, 0x27] { let mut frame = vec![magic, 0xB5, 0x2F, 0xFD, 0x00]; if magic == 0x27 { frame.push(0x00); } frame.extend_from_slice(&[0x80, 0, 5, b'z', 0xC0, 0, 0]); - let (decoded, consumed) = decode_to_vec(&frame, LegacyDict::None).unwrap(); - assert_eq!(decoded, b"zzzzz"); - assert_eq!(consumed, frame.len()); + let decoded = decode_to_vec(&frame, LegacyDict::None); + if magic == 0x27 { + let (decoded, consumed) = decoded.unwrap(); + assert_eq!(decoded, b"zzzzz"); + assert_eq!(consumed, frame.len()); + } else { + assert!( + matches!(decoded, Err(Error::Corrupted { .. })), + "{magic:#x}: {decoded:?}" + ); + } } } @@ -804,9 +1158,9 @@ fn a_block_past_the_largest_size_is_refused() { #[cfg(feature = "std")] #[test] fn the_declared_content_size_is_not_enforced() { - // v0.6 with a one-byte content size of 4 and an RLE block of 5. + // v0.6 with a one-byte content size of 4 and a raw block of 5. let frame = [ - 0x26, 0xB5, 0x2F, 0xFD, 0x40, 4, 0x80, 0, 5, b'z', 0xC0, 0, 0, + 0x26, 0xB5, 0x2F, 0xFD, 0x40, 4, 0x40, 0, 5, b'z', b'z', b'z', b'z', b'z', 0xC0, 0, 0, ]; assert_eq!(declared_content_size(&frame), Some(4)); assert_eq!( diff --git a/zstd/src/decoding/legacy/v05.rs b/zstd/src/decoding/legacy/v05.rs index 8a099957..389ebce2 100644 --- a/zstd/src/decoding/legacy/v05.rs +++ b/zstd/src/decoding/legacy/v05.rs @@ -2,7 +2,7 @@ //! symbols with long values in a separate "dumps" area, a single repeat //! offset, and a minimum match of 4. -use super::entropy::{FseState, FseTable, HufTable, StreamEnd, open_bits, overflowed}; +use super::entropy::{FseState, FseTable, HufTable, Slot, StreamEnd, open_bits, overflowed}; use super::v06::huffman; use super::v07::{SeqTables, sequence_count}; use super::{ @@ -53,7 +53,7 @@ pub(super) fn read_header(src: &[u8]) -> Result { /// The decoding state a v0.5 frame carries from block to block. pub(super) struct Decoder { /// The dictionary's Huffman table, which repeat-table literals use. - dict_huf: Option, + dict_huf: Slot, /// The table a block's own Huffman description is read into. huf: HufTable, tables: SeqTables, @@ -64,29 +64,41 @@ pub(super) struct Decoder { } impl Decoder { - /// A decoder primed with the dictionary `bytes`, and where its content - /// lies in them. - pub(super) fn new(bytes: &[u8]) -> Result<(Decoder, core::ops::Range), Error> { - let mut decoder = Decoder { - dict_huf: None, + pub(super) const fn empty() -> Decoder { + Decoder { + dict_huf: Slot::::empty(), huf: HufTable::empty(), - tables: SeqTables::default(), + tables: SeqTables::empty(), repeat_tables: false, literals: Vec::new(), - }; + } + } + + /// Ready the decoder for a frame primed with the dictionary `bytes`, in + /// the storage the previous frame left, and say where its content lies in + /// them. + pub(super) fn reset(&mut self, bytes: &[u8]) -> Result, Error> { + self.dict_huf.set = false; + self.tables.unset(); + self.repeat_tables = false; + self.literals.clear(); if bytes.len() < 4 || u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) != DICT_MAGIC { - return Ok((decoder, 0..bytes.len())); + return Ok(0..bytes.len()); } // `ZSTDv05_loadEntropy`. let src = &bytes[4..]; let bad = |_| Error::DictionaryCorrupted; - let (huf, mut at) = HufTable::read(src, StreamEnd::Exact).map_err(bad)?; + let mut at = self + .dict_huf + .table + .read_into(src, StreamEnd::Exact) + .map_err(bad)?; for (slot, max, max_log) in [ - (&mut decoder.tables.of, MAX_OFF, OFF_LOG), - (&mut decoder.tables.ml, MAX_ML, ML_LOG), - (&mut decoder.tables.ll, MAX_LL, LL_LOG), + (&mut self.tables.of, MAX_OFF, OFF_LOG), + (&mut self.tables.ml, MAX_ML, ML_LOG), + (&mut self.tables.ll, MAX_LL, LL_LOG), ] { let (dist, len) = super::entropy::read_distribution( src.get(at..).ok_or(Error::DictionaryCorrupted)?, @@ -96,12 +108,21 @@ impl Decoder { if dist.table_log() > max_log { return Err(Error::DictionaryCorrupted); } - *slot = Some(FseTable::new(&dist).map_err(bad)?); + slot.table.build(&dist).map_err(bad)?; + slot.set = true; at += len; } - decoder.dict_huf = Some(huf); - decoder.repeat_tables = true; - Ok((decoder, 4 + at..bytes.len())) + self.dict_huf.set = true; + self.repeat_tables = true; + Ok(4 + at..bytes.len()) + } + + /// Heap bytes the decoder holds. + pub(super) fn heap_bytes(&self) -> usize { + self.dict_huf.table.heap_bytes() + + self.huf.heap_bytes() + + self.tables.heap_bytes() + + self.literals.capacity() } /// Decode one compressed block into `out` (`ZSTDv05_decompressBlock_internal`). @@ -171,7 +192,7 @@ impl Decoder { if !self.repeat_tables { return Err(Error::DictionaryCorrupted); } - let table = self.dict_huf.as_ref().ok_or(Error::DictionaryCorrupted)?; + let table = self.dict_huf.get().ok_or(Error::DictionaryCorrupted)?; self.literals.clear(); self.literals.resize(size, 0); table @@ -343,7 +364,7 @@ impl Field { /// Set one field's table by its mode, the description starting at /// `src[at..]` (`ZSTDv05_decodeSeqHeaders`). Returns the bytes it took. fn field_table( - table: &mut Option, + slot: &mut Slot, mode: u8, src: &[u8], at: usize, @@ -351,14 +372,15 @@ fn field_table( repeat: bool, ) -> Result { if mode == 2 { - if !repeat || table.is_none() { + if !repeat || !slot.set { return Err(corrupt("sequence table repeated before one was sent")); } return Ok(0); } // Built in the previous table's storage; left unset should the // description be bad, so no later block repeats a half-built table. - let mut new = table.take().unwrap_or_else(FseTable::empty); + slot.set = false; + let new = &mut slot.table; let len = match mode { // Every symbol read as raw bits. 0 => { @@ -390,7 +412,7 @@ fn field_table( len } }; - *table = Some(new); + slot.set = true; Ok(len) } diff --git a/zstd/src/decoding/legacy/v06.rs b/zstd/src/decoding/legacy/v06.rs index d7ab7a71..0f66ddd0 100644 --- a/zstd/src/decoding/legacy/v06.rs +++ b/zstd/src/decoding/legacy/v06.rs @@ -2,7 +2,7 @@ //! a dictionary ID or checksum, repeat offsets that restart every block, and //! offsets coded two higher. -use super::entropy::{FseState, FseTable, HufTable, StreamEnd, open_bits, overflowed}; +use super::entropy::{FseState, HufTable, Slot, StreamEnd, open_bits, overflowed}; use super::v07::{ LL_BASE, LL_BITS, LL_DEFAULT, LL_DEFAULT_LOG, ML_BASE, ML_BITS, ML_DEFAULT, ML_DEFAULT_LOG, OF_DEFAULT, OF_DEFAULT_LOG, SeqTables, seq_table, sequence_count, @@ -59,7 +59,7 @@ pub(super) fn read_header(src: &[u8]) -> Result { /// The decoding state a v0.6 frame carries from block to block. pub(super) struct Decoder { /// The dictionary's Huffman table, which repeat-table literals use. - dict_huf: Option, + dict_huf: Slot, /// The table a block's own Huffman description is read into. huf: HufTable, tables: SeqTables, @@ -71,29 +71,41 @@ pub(super) struct Decoder { } impl Decoder { - /// A decoder primed with `dict`, and where the dictionary's content lies - /// in `bytes`, the bytes `dict` reads as one run. - pub(super) fn new(bytes: &[u8]) -> Result<(Decoder, core::ops::Range), Error> { - let mut decoder = Decoder { - dict_huf: None, + pub(super) const fn empty() -> Decoder { + Decoder { + dict_huf: Slot::::empty(), huf: HufTable::empty(), - tables: SeqTables::default(), + tables: SeqTables::empty(), repeat_tables: false, literals: Vec::new(), - }; + } + } + + /// Ready the decoder for a frame primed with the dictionary `bytes`, in + /// the storage the previous frame left, and say where its content lies in + /// them. + pub(super) fn reset(&mut self, bytes: &[u8]) -> Result, Error> { + self.dict_huf.set = false; + self.tables.unset(); + self.repeat_tables = false; + self.literals.clear(); if bytes.len() < 4 || u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) != DICT_MAGIC { - return Ok((decoder, 0..bytes.len())); + return Ok(0..bytes.len()); } // `ZSTDv06_loadEntropy`. let src = &bytes[4..]; let bad = |_| Error::DictionaryCorrupted; - let (huf, mut at) = HufTable::read(src, StreamEnd::PastStart).map_err(bad)?; + let mut at = self + .dict_huf + .table + .read_into(src, StreamEnd::PastStart) + .map_err(bad)?; for (slot, max, max_log) in [ - (&mut decoder.tables.of, MAX_OFF, OFF_LOG), - (&mut decoder.tables.ml, MAX_ML, ML_LOG), - (&mut decoder.tables.ll, MAX_LL, LL_LOG), + (&mut self.tables.of, MAX_OFF, OFF_LOG), + (&mut self.tables.ml, MAX_ML, ML_LOG), + (&mut self.tables.ll, MAX_LL, LL_LOG), ] { let (dist, len) = super::entropy::read_distribution( src.get(at..).ok_or(Error::DictionaryCorrupted)?, @@ -103,12 +115,21 @@ impl Decoder { if dist.table_log() > max_log { return Err(Error::DictionaryCorrupted); } - *slot = Some(FseTable::new(&dist).map_err(bad)?); + slot.table.build(&dist).map_err(bad)?; + slot.set = true; at += len; } - decoder.dict_huf = Some(huf); - decoder.repeat_tables = true; - Ok((decoder, 4 + at..bytes.len())) + self.dict_huf.set = true; + self.repeat_tables = true; + Ok(4 + at..bytes.len()) + } + + /// Heap bytes the decoder holds. + pub(super) fn heap_bytes(&self) -> usize { + self.dict_huf.table.heap_bytes() + + self.huf.heap_bytes() + + self.tables.heap_bytes() + + self.literals.capacity() } /// Decode one compressed block into `out` (`ZSTDv06_decompressBlock_internal`). @@ -180,7 +201,7 @@ impl Decoder { if !self.repeat_tables { return Err(Error::DictionaryCorrupted); } - let table = self.dict_huf.as_ref().ok_or(Error::DictionaryCorrupted)?; + let table = self.dict_huf.get().ok_or(Error::DictionaryCorrupted)?; self.literals.clear(); self.literals.resize(size, 0); table diff --git a/zstd/src/decoding/legacy/v07.rs b/zstd/src/decoding/legacy/v07.rs index 7dfd2a74..2c6d2268 100644 --- a/zstd/src/decoding/legacy/v07.rs +++ b/zstd/src/decoding/legacy/v07.rs @@ -3,7 +3,7 @@ //! from block to block. use super::entropy::{ - Distribution, FseState, FseTable, HufTable, StreamEnd, open_bits, overflowed, + Distribution, FseState, FseTable, HufTable, Slot, StreamEnd, open_bits, overflowed, }; use super::{ BLOCK_SIZE_MAX, FrameHeader, LegacyDict, LegacyVersion, LiteralSource, Literals, Output, @@ -129,68 +129,39 @@ pub(super) fn read_header(src: &[u8]) -> Result { }) } -/// The tables and offsets a dictionary of the v0.7 layout primes, taken from -/// what the RFC 8878 parser read of it: the two layouts are the same bytes, -/// so the parsed distributions and code lengths are the ones -/// `ZSTDv07_loadEntropy` would read, held to the v0.7 symbol limits. -/// -/// The repeat offsets are not held to the dictionary's size here, as the -/// parser kept no count of its table bytes; one reaching past the content -/// fails at the match that uses it. -fn parsed_entropy(dict: &crate::decoding::Dictionary) -> Result { - let bad = |_| Error::DictionaryCorrupted; - let huf_table = &dict.huf.table; - let huf = HufTable::from_code_lengths(huf_table.code_lengths(), huf_table.max_num_bits) - .map_err(bad)?; - let table = |fse: &crate::decoding::scratch::AlignedFSETable, - max: usize, - max_log: u32| - -> Result { - let log = u32::from(fse.accuracy_log); - if log > max_log { - return Err(Error::DictionaryCorrupted); - } - let dist = - Distribution::from_probabilities(&fse.symbol_probabilities, log, max).map_err(bad)?; - FseTable::new(&dist).map_err(bad) - }; - let of = table(&dict.fse.offsets, MAX_OFF, OFF_LOG)?; - let ml = table(&dict.fse.match_lengths, MAX_ML, ML_LOG)?; - let ll = table(&dict.fse.literal_lengths, MAX_LL, LL_LOG)?; - let mut reps = [0usize; 3]; - for (rep, &offset) in reps.iter_mut().zip(&dict.offset_hist) { - if offset == 0 { - return Err(Error::DictionaryCorrupted); - } - *rep = offset as usize; - } - Ok(Decoder { - huf: Some(huf), - tables: SeqTables { - ll: Some(ll), - of: Some(of), - ml: Some(ml), - }, - repeat_tables: true, - reps, - literals: Vec::new(), - dict_id: 0, - }) -} - /// The literal length, offset and match length tables last in use, which a /// block may keep instead of sending new ones. -#[derive(Default)] pub(super) struct SeqTables { - pub(super) ll: Option, - pub(super) of: Option, - pub(super) ml: Option, + pub(super) ll: Slot, + pub(super) of: Slot, + pub(super) ml: Slot, +} + +impl SeqTables { + pub(super) const fn empty() -> SeqTables { + SeqTables { + ll: Slot::::empty(), + of: Slot::::empty(), + ml: Slot::::empty(), + } + } + + /// Set none of the tables, keeping their storage. + pub(super) fn unset(&mut self) { + self.ll.set = false; + self.of.set = false; + self.ml.set = false; + } + + pub(super) fn heap_bytes(&self) -> usize { + self.ll.table.heap_bytes() + self.of.table.heap_bytes() + self.ml.table.heap_bytes() + } } /// The decoding state a v0.7 frame carries from block to block. pub(super) struct Decoder { /// The last Huffman table, which repeat-table literals reuse. - huf: Option, + huf: Slot, tables: SeqTables, /// Whether a block may keep the previous tables: once a dictionary or a /// block with sequences has set them (`fseEntropy`). @@ -202,34 +173,94 @@ pub(super) struct Decoder { } impl Decoder { - /// A decoder primed with `dict`, and where the dictionary's content lies - /// within [`LegacyDict::content_bytes`]. - pub(super) fn new(dict: LegacyDict<'_>) -> Result<(Decoder, core::ops::Range), Error> { - let fresh = || Decoder { - huf: None, - tables: SeqTables::default(), + pub(super) const fn empty() -> Decoder { + Decoder { + huf: Slot::::empty(), + tables: SeqTables::empty(), repeat_tables: false, reps: [1, 4, 8], literals: Vec::new(), dict_id: 0, - }; + } + } + + /// Ready the decoder for a frame primed with `dict`, in the storage the + /// previous frame left, and say where the dictionary's content lies + /// within [`LegacyDict::content_bytes`]. + pub(super) fn reset(&mut self, dict: LegacyDict<'_>) -> Result, Error> { + self.huf.set = false; + self.tables.unset(); + self.repeat_tables = false; + self.reps = [1, 4, 8]; + self.literals.clear(); + self.dict_id = 0; match dict { - LegacyDict::None => Ok((fresh(), 0..0)), + LegacyDict::None => Ok(0..0), LegacyDict::Parsed(dict) => { - let content = 0..dict.dict_content.len(); // A raw-content dictionary has no tables to load. - if dict.huf.table.max_num_bits == 0 { - let mut decoder = fresh(); - decoder.dict_id = dict.id; - return Ok((decoder, content)); + if dict.huf.table.max_num_bits != 0 { + self.load_parsed(dict)?; } - let mut decoder = parsed_entropy(dict)?; - decoder.dict_id = dict.id; - Ok((decoder, content)) + self.dict_id = dict.id; + Ok(0..dict.dict_content.len()) } } } + /// Heap bytes the decoder holds. + pub(super) fn heap_bytes(&self) -> usize { + self.huf.table.heap_bytes() + self.tables.heap_bytes() + self.literals.capacity() + } + + /// Load the tables and offsets a dictionary of the v0.7 layout primes, + /// from what the RFC 8878 parser read of it: the two layouts are the same + /// bytes, so the parsed distributions and code lengths are the ones + /// `ZSTDv07_loadEntropy` would read, held to the v0.7 symbol limits. + /// + /// The repeat offsets are not held to the dictionary's size here, as the + /// parser kept no count of its table bytes; one reaching past the content + /// fails at the match that uses it. + fn load_parsed(&mut self, dict: &crate::decoding::Dictionary) -> Result<(), Error> { + let bad = |_| Error::DictionaryCorrupted; + let huf_table = &dict.huf.table; + self.huf + .table + .fill_from_code_lengths(huf_table.code_lengths(), huf_table.max_num_bits) + .map_err(bad)?; + self.huf.set = true; + let load = |slot: &mut Slot, + fse: &crate::decoding::scratch::AlignedFSETable, + max: usize, + max_log: u32| + -> Result<(), Error> { + let log = u32::from(fse.accuracy_log); + if log > max_log { + return Err(Error::DictionaryCorrupted); + } + let dist = Distribution::from_probabilities(&fse.symbol_probabilities, log, max) + .map_err(bad)?; + slot.table.build(&dist).map_err(bad)?; + slot.set = true; + Ok(()) + }; + load(&mut self.tables.of, &dict.fse.offsets, MAX_OFF, OFF_LOG)?; + load(&mut self.tables.ml, &dict.fse.match_lengths, MAX_ML, ML_LOG)?; + load( + &mut self.tables.ll, + &dict.fse.literal_lengths, + MAX_LL, + LL_LOG, + )?; + for (rep, &offset) in self.reps.iter_mut().zip(&dict.offset_hist) { + if offset == 0 { + return Err(Error::DictionaryCorrupted); + } + *rep = offset as usize; + } + self.repeat_tables = true; + Ok(()) + } + /// Decode one compressed block into `out` (`ZSTDv07_decompressBlock_internal`). pub(super) fn block( &mut self, @@ -268,7 +299,8 @@ impl Decoder { // Built in the previous table's storage; left unset should the // description be bad, so no later block repeats a half-built // table. - let mut table = self.huf.take().unwrap_or_else(HufTable::empty); + self.huf.set = false; + let table = &mut self.huf.table; let table_len = table .read_into(src, StreamEnd::PastStart) .map_err(|_| corrupt("bad Huffman table"))?; @@ -284,11 +316,11 @@ impl Decoder { table.decode_four_streams(streams, &mut self.literals) } .map_err(|_| corrupt("bad Huffman literals"))?; - self.huf = Some(table); + self.huf.set = true; (LiteralSource::Buffer, header + compressed) } Literals::Repeat { size, compressed } => { - let table = self.huf.as_ref().ok_or(Error::DictionaryCorrupted)?; + let table = self.huf.get().ok_or(Error::DictionaryCorrupted)?; self.literals.clear(); self.literals.resize(size, 0); table @@ -432,7 +464,7 @@ pub(super) fn sequence_count(src: &[u8], long_form: bool) -> Result<(usize, usiz impl SeqTables { /// The three tables, all set once a block has used them. pub(super) fn all(&self) -> Result<(&FseTable, &FseTable, &FseTable), Error> { - match (&self.ll, &self.of, &self.ml) { + match (self.ll.get(), self.of.get(), self.ml.get()) { (Some(ll), Some(of), Some(ml)) => Ok((ll, of, ml)), _ => Err(corrupt("sequence table repeated before one was sent")), } @@ -441,11 +473,11 @@ impl SeqTables { /// Set the table one sequence field is coded with, by its mode /// (`ZSTDv07_buildSeqTable`): `field` gives the largest symbol, the largest -/// table log, and the predefined distribution; the repeat mode keeps `table` +/// table log, and the predefined distribution; the repeat mode keeps `slot` /// as it is, where `repeat` allows it. Returns the bytes the mode's /// description took. pub(super) fn seq_table( - table: &mut Option, + slot: &mut Slot, mode: u8, src: &[u8], field: (usize, u32, &[i16], u32), @@ -454,14 +486,15 @@ pub(super) fn seq_table( let (max, max_log, default, default_log) = field; // The previous table, in place. if mode == 2 { - if !repeat || table.is_none() { + if !repeat || !slot.set { return Err(corrupt("sequence table repeated before one was sent")); } return Ok(0); } // Built in the previous table's storage; left unset should the // description be bad, so no later block repeats a half-built table. - let mut new = table.take().unwrap_or_else(FseTable::empty); + slot.set = false; + let new = &mut slot.table; let len = match mode { // Predefined. 0 => { @@ -487,6 +520,6 @@ pub(super) fn seq_table( len } }; - *table = Some(new); + slot.set = true; Ok(len) } diff --git a/zstd/src/decoding/simd_copy/tests.rs b/zstd/src/decoding/simd_copy/tests.rs index a80477db..b4dfcbb7 100644 --- a/zstd/src/decoding/simd_copy/tests.rs +++ b/zstd/src/decoding/simd_copy/tests.rs @@ -46,14 +46,14 @@ fn runnable_medium_kernels() -> vec::Vec<(&'static str, CopyKernel)> { feature = "kernel-sse" ))] std::arch::is_x86_feature_detected!("sse2") - .then_some(("copy_exact_sse2", copy_exact_sse2 as CopyKernel)), + .then_some::<(&'static str, CopyKernel)>(("copy_exact_sse2", copy_exact_sse2)), #[cfg(all( feature = "std", any(target_arch = "x86", target_arch = "x86_64"), feature = "kernel-avx2" ))] std::arch::is_x86_feature_detected!("avx2") - .then_some(("copy_exact_avx2", copy_exact_avx2 as CopyKernel)), + .then_some::<(&'static str, CopyKernel)>(("copy_exact_avx2", copy_exact_avx2)), #[cfg(all( target_arch = "aarch64", target_feature = "neon", From 817c90a1d58d7c1037da5c7a8f296d1bf8ea5789 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Tue, 20 Oct 2026 12:36:46 +0300 Subject: [PATCH 09/11] fix(decoding): hold v0.5-v0.7 frames to every decoder contract - the caller-workspace decoder reports a v0.5-v0.7 frame as Unsupported::LegacyFrame rather than malformed: the owned decoders read it, and its decoders allocate, as upstream's static context refuses them - pinned dictionary IDs and window descriptors bind these frames on the streaming and whole-buffer paths: v0.7 carries both fields, v0.5 and v0.6 neither - per-block checksums cover their blocks on every path - after decode_all the accessors describe the frame decoded last, which stays current, finished, until the next header - find_decompressed_size refuses a v0.5-v0.7 tail shorter than a frame's first five bytes, as upstream does - the whole-buffer path borrows a held dictionary instead of counting a handle per frame - a new header ends any v0.5-v0.7 frame before it is read, so a header that fails to read leaves no stale frame behind - drop VecOutput's limit, which every caller set to usize::MAX, and the copy_match branches no output can reach, now a debug assertion Regression tests cover each fix; further tests cover the error codes, literal headers, one-symbol sequence tables, reader errors inside a block, held-back output, and arbitrary descriptions and overwritten frames, which must not panic. Part of #131 --- zstd/src/decoding/frame_decoder.rs | 121 +++-- zstd/src/decoding/legacy/entropy/tests.rs | 41 ++ zstd/src/decoding/legacy/mod.rs | 142 ++++-- zstd/src/decoding/legacy/tests.rs | 569 ++++++++++++++++++++-- zstd/src/decoding/mod.rs | 8 + zstd/src/decoding/workspace.rs | 23 + zstd/src/decoding/workspace/tests.rs | 30 ++ 7 files changed, 817 insertions(+), 117 deletions(-) diff --git a/zstd/src/decoding/frame_decoder.rs b/zstd/src/decoding/frame_decoder.rs index e826a861..efd1d5da 100644 --- a/zstd/src/decoding/frame_decoder.rs +++ b/zstd/src/decoding/frame_decoder.rs @@ -1301,6 +1301,8 @@ impl FrameDecoder { source: &mut R, #[cfg_attr(not(feature = "legacy"), allow(unused_variables))] dicts: &[DictionaryHandle], ) -> Result { + // A new header ends any v0.5-v0.7 frame, whether or not it reads. + self.end_legacy_frame(); let magicless = self.magicless; let max_window = self.max_window_size; let block_limit = self.max_block_size; @@ -1317,10 +1319,7 @@ impl FrameDecoder { } }; match parsed { - Ok(()) => { - self.end_legacy_frame(); - Ok(true) - } + Ok(()) => Ok(true), #[cfg(feature = "legacy")] Err(FrameDecoderError::ReadFrameHeaderError( crate::decoding::errors::ReadFrameHeaderError::BadMagicNumber(magic), @@ -1367,6 +1366,8 @@ impl FrameDecoder { read_field(fields.dict_id, ReadError::DictionaryIdReadError)?; read_field(fields.content_size, ReadError::FrameContentSizeReadError)?; let header = legacy::read_header(&bytes[..at])?; + #[cfg(feature = "lsm")] + self.validate_legacy_expectations(&header, &bytes[..at])?; if header.window > self.max_window_size { return Err(FrameDecoderError::WindowSizeTooBig { requested: header.window, @@ -1390,6 +1391,8 @@ impl FrameDecoder { #[cfg(feature = "hash")] checksum: self.content_checksum, block_max: self.max_block_size, + #[cfg(all(feature = "lsm", feature = "hash"))] + block_digests: self.per_block_checksums_enabled, } } @@ -1466,6 +1469,10 @@ impl FrameDecoder { /// Behind `all(feature = "lsm", feature = "hash")`. #[cfg(all(feature = "lsm", feature = "hash"))] pub fn computed_block_checksums(&self) -> &[u32] { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.block_digests(); + } &self.computed_block_checksums } @@ -1546,9 +1553,38 @@ impl FrameDecoder { fn validate_expectations( &self, frame_header: &frame::FrameHeader, + ) -> Result<(), FrameDecoderError> { + self.validate_pinned_fields( + frame_header.dictionary_id(), + frame_header.window_descriptor(), + ) + } + + /// [`Self::validate_expectations`] for a v0.5-v0.7 header, whose fields + /// are held to the same pins: a v0.7 header carries both, v0.5 and v0.6 + /// ones neither. + #[cfg(all(feature = "lsm", feature = "legacy"))] + fn validate_legacy_expectations( + &self, + header: &decoding::legacy::FrameHeader, + header_bytes: &[u8], + ) -> Result<(), FrameDecoderError> { + self.validate_pinned_fields( + header.dictionary_id(), + header.window_descriptor(header_bytes), + ) + } + + /// Hold a header's dictionary ID and window descriptor, `None` where it + /// carries none, to the pinned expectations. + #[cfg(feature = "lsm")] + fn validate_pinned_fields( + &self, + dictionary_id: Option, + window_descriptor: Option, ) -> Result<(), FrameDecoderError> { if let Some(expected) = self.expect_dict_id { - let found = frame_header.dictionary_id(); + let found = dictionary_id; // `Some(0)` is the "no dictionary expected" sentinel — // matches a frame whose header omits the optional // dict_id field (which is reported as `None` by the @@ -1566,7 +1602,7 @@ impl FrameDecoder { } } if let Some(expected) = self.expect_window_descriptor { - let found = frame_header.window_descriptor(); + let found = window_descriptor; if found != Some(expected) { return Err(FrameDecoderError::UnexpectedWindowDescriptor { expected, found }); } @@ -4192,48 +4228,46 @@ impl FrameDecoder { buf: output, pos: 0, }; - let consumed = self - .decode_whole_legacy_frame(input, dicts, &mut out) - .map_err(|err| match err { - FrameDecoderError::LegacyFrame(LegacyFrameError::OutputTooSmall) => { - FrameDecoderError::TargetTooSmall - } - err => err, - })?; + let mut stream = self.take_legacy_stream(); + let decoded = self.decode_whole_legacy_frame(&mut stream, input, dicts, &mut out); + self.legacy = Some(stream); + let consumed = decoded.map_err(|err| match err { + FrameDecoderError::LegacyFrame(LegacyFrameError::OutputTooSmall) => { + FrameDecoderError::TargetTooSmall + } + err => err, + })?; *input = &input[consumed..]; Ok(out.pos) } + /// The decoder's v0.5-v0.7 stream, taken out so a whole-buffer decode can + /// run in its workspace while dictionaries are borrowed from the decoder; + /// the caller puts it back. + #[cfg(feature = "legacy")] + fn take_legacy_stream(&mut self) -> alloc::boxed::Box { + self.legacy + .take() + .unwrap_or_else(|| alloc::boxed::Box::new(decoding::legacy::LegacyStream::new())) + } + /// Decode the v0.5-v0.7 frame starting `input` into `out`, with the - /// dictionary [`Self::legacy_frame_dict`] picks from `dicts`, in the - /// stream's decoder so its tables are reused from frame to frame. + /// dictionary [`Self::legacy_frame_dict`] picks from `dicts`, in + /// `stream`'s workspace so its tables are reused from frame to frame. /// Returns the bytes the frame took. #[cfg(feature = "legacy")] fn decode_whole_legacy_frame( - &mut self, + &self, + stream: &mut decoding::legacy::LegacyStream, input: &[u8], dicts: &[DictionaryHandle], out: &mut O, ) -> Result { - use crate::decoding::legacy::{self, LegacyStream}; - // Taken out while the dictionary is borrowed from the decoder. - let mut stream = self - .legacy - .take() - .unwrap_or_else(|| alloc::boxed::Box::new(LegacyStream::new())); - let decoded = legacy::read_header(input) - .map_err(FrameDecoderError::from) - .and_then(|header| self.legacy_frame_dict(header.dict_id, dicts)) - .and_then(|dict| { - Ok(stream.decode_whole( - input, - legacy_dict(dict), - self.legacy_frame_options(), - out, - )?) - }); - self.legacy = Some(stream); - decoded + let header = decoding::legacy::read_header(input)?; + #[cfg(feature = "lsm")] + self.validate_legacy_expectations(&header, input)?; + let dict = self.legacy_frame_dict(header.dict_id, dicts)?; + Ok(stream.decode_whole(input, legacy_dict(dict), self.legacy_frame_options(), out)?) } /// Decode the v0.5-v0.7 frame starting `*input`, appending to `output` @@ -4247,25 +4281,20 @@ impl FrameDecoder { keep_dictionary: bool, ) -> Result<(), FrameDecoderError> { use crate::decoding::legacy::VecOutput; - // A handle count per frame: the held dictionary is borrowed from the - // decoder, which the decode borrows mutably. + let mut stream = self.take_legacy_stream(); let held = if keep_dictionary { self.state .as_ref() .and_then(|state| state.active_dict.as_ref()) .or(self.armed_dict.as_ref()) - .cloned() } else { None }; - let dicts = held.as_slice(); + let dicts = held.map_or(&[][..], core::slice::from_ref); let start = output.len(); - let mut out = VecOutput { - buf: output, - start, - limit: usize::MAX, - }; - let decoded = self.decode_whole_legacy_frame(input, dicts, &mut out); + let mut out = VecOutput { buf: output, start }; + let decoded = self.decode_whole_legacy_frame(&mut stream, input, dicts, &mut out); + self.legacy = Some(stream); match decoded { Ok(consumed) => { *input = &input[consumed..]; diff --git a/zstd/src/decoding/legacy/entropy/tests.rs b/zstd/src/decoding/legacy/entropy/tests.rs index a8960103..6fa62a07 100644 --- a/zstd/src/decoding/legacy/entropy/tests.rs +++ b/zstd/src/decoding/legacy/entropy/tests.rs @@ -178,3 +178,44 @@ fn four_streams_fill_their_quarters() { src.extend_from_slice(&[stream, stream, stream, stream]); assert!(table.decode_four_streams(&src, &mut out).is_err()); } + +/// Arbitrary bytes read as FSE or Huffman descriptions, and as Huffman +/// streams under whatever table they described, are refused or decode, but +/// never panic or read past their input: the descriptions in a damaged +/// frame are attacker-chosen. +#[test] +fn arbitrary_descriptions_never_panic() { + // xorshift64: deterministic, so a failure reproduces. + let mut state = 0x9E37_79B9_7F4A_7C15u64; + let mut next = move || { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + state + }; + let mut table = FseTable::empty(); + let mut huf = HufTable::empty(); + for _ in 0..20_000 { + let len = (next() % 24) as usize + 1; + let src: vec::Vec = (0..len).map(|_| next() as u8).collect(); + for max_symbol in [28, 35, 52, 255] { + if let Ok((dist, consumed)) = read_distribution(&src, max_symbol) { + assert!(consumed <= src.len()); + // A description may still ask for a table too large to build. + let _built = table.build(&dist).is_ok(); + } + } + for end in [StreamEnd::PastStart, StreamEnd::Exact] { + let Ok(consumed) = huf.read_into(&src, end) else { + continue; + }; + assert!(consumed <= src.len()); + let stream: vec::Vec = (0..(next() % 16) as usize + 1) + .map(|_| next() as u8) + .collect(); + let mut out = vec![0u8; (next() % 32) as usize + 1]; + let _one = huf.decode_one_stream(&stream, &mut out).is_ok(); + let _four = huf.decode_four_streams(&stream, &mut out).is_ok(); + } + } +} diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index bb9e7d74..6ac4d9dc 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -207,6 +207,24 @@ pub(crate) fn header_len(version: LegacyVersion, src: &[u8]) -> Result Option { + let fields = HeaderFields::of(self.version, src[4]); + (fields.window == 1).then(|| src[5]) + } + + /// The dictionary ID the header names, `None` for none. + #[cfg(feature = "lsm")] + pub(crate) fn dictionary_id(&self) -> Option { + (self.dict_id != 0).then_some(self.dict_id) + } +} + /// The lengths of the fields that follow a frame header's descriptor byte, /// in the order they follow it. #[derive(Debug, Clone, Copy)] @@ -300,8 +318,6 @@ pub(crate) trait Output { pub(crate) struct VecOutput<'a> { pub(crate) buf: &'a mut Vec, pub(crate) start: usize, - /// Most bytes the frame may produce. - pub(crate) limit: usize, } impl Output for VecOutput<'_> { @@ -314,9 +330,7 @@ impl Output for VecOutput<'_> { } fn reserve(&mut self, len: usize) -> Result<(), Error> { - if len > self.limit - self.produced() { - return Err(Error::OutputTooSmall); - } + // The block budget in front of this output bounds `len`. self.buf.reserve(len); Ok(()) } @@ -399,8 +413,12 @@ impl Output for SliceOutput<'_> { /// Copy a match of `len` bytes from `offset` back: within the frame, from /// the dictionary's content in front of it, or across both -/// (`ZSTDv07_execSequence`). The output has room for `len` more bytes. +/// (`ZSTDv07_execSequence`). The output holds the whole frame so far and has +/// room for `len` more bytes. fn copy_match(out: &mut O, offset: usize, len: usize, dict: &[u8]) -> Result<(), Error> { + // An output that drops older bytes reads matches through its own + // `Output::copy_match`. + debug_assert_eq!(out.held(), out.produced()); if offset == 0 { return Err(corrupt("match with offset 0")); } @@ -409,11 +427,7 @@ fn copy_match(out: &mut O, offset: usize, len: usize, dict: &[u8]) -> out.copy_back(offset, len); return Ok(()); } - let produced = out.produced(); - if offset <= produced { - return Err(corrupt("match reaches before the window")); - } - let back = offset - produced; + let back = offset - held; if back > dict.len() { return Err(corrupt("match reaches before the dictionary")); } @@ -423,9 +437,6 @@ fn copy_match(out: &mut O, offset: usize, len: usize, dict: &[u8]) -> let rest = len - first; if rest > 0 { // The match runs on from the frame's first byte. - if produced != held { - return Err(corrupt("match reaches before the window")); - } let distance = out.held(); out.copy_back(distance, rest); } @@ -606,6 +617,25 @@ impl VersionDecoder { } } +/// What a frame decodes in, kept from frame to frame so its storage is +/// reused: the per-format decoder, and the per-block digests when asked for. +#[derive(Default)] +struct Workspace { + decoder: Option, + /// The current frame's per-block XXH64 digests, low 32 bits. + #[cfg(all(feature = "lsm", feature = "hash"))] + block_digests: Vec, +} + +impl Workspace { + fn heap_bytes(&self) -> usize { + let bytes = self.decoder.as_ref().map_or(0, VersionDecoder::heap_bytes); + #[cfg(all(feature = "lsm", feature = "hash"))] + let bytes = bytes + self.block_digests.capacity() * core::mem::size_of::(); + bytes + } +} + /// What the caller set on the decoder that a frame of these formats obeys. #[derive(Debug, Clone, Copy)] pub(crate) struct FrameOptions { @@ -614,6 +644,10 @@ pub(crate) struct FrameOptions { pub(crate) checksum: ContentChecksum, /// Most bytes a block may hold, compressed or decoded. pub(crate) block_max: usize, + /// Whether each data block's output is digested, for the per-block + /// checksums. + #[cfg(all(feature = "lsm", feature = "hash"))] + pub(crate) block_digests: bool, } impl Default for FrameOptions { @@ -622,6 +656,8 @@ impl Default for FrameOptions { #[cfg(feature = "hash")] checksum: ContentChecksum::EmitOnly, block_max: BLOCK_SIZE_MAX, + #[cfg(all(feature = "lsm", feature = "hash"))] + block_digests: false, } } } @@ -691,17 +727,22 @@ struct LegacyFrame { calculated_checksum: Option, #[cfg(feature = "hash")] hash: Option, + #[cfg(all(feature = "lsm", feature = "hash"))] + block_digests: bool, } impl LegacyFrame { - /// Start decoding the frame `header` describes, readying `decoder` for - /// it: the one the previous frame left, when of the same format. + /// Start decoding the frame `header` describes, readying `workspace` for + /// it: the decoder the previous frame left, when of the same format. fn new( header: FrameHeader, dict: LegacyDict<'_>, options: FrameOptions, - decoder: &mut Option, + workspace: &mut Workspace, ) -> Result { + #[cfg(all(feature = "lsm", feature = "hash"))] + workspace.block_digests.clear(); + let decoder = &mut workspace.decoder; if decoder.as_ref().map(VersionDecoder::version) != Some(header.version) { *decoder = Some(match header.version { LegacyVersion::V05 => VersionDecoder::V05(v05::Decoder::empty()), @@ -743,6 +784,8 @@ impl LegacyFrame { calculated_checksum: None, #[cfg(feature = "hash")] hash, + #[cfg(all(feature = "lsm", feature = "hash"))] + block_digests: options.block_digests, }) } @@ -751,12 +794,13 @@ impl LegacyFrame { /// passed [`Block::check`]. fn block( &mut self, - decoder: &mut VersionDecoder, + workspace: &mut Workspace, block: Block, body: &[u8], out: &mut O, dict_bytes: &[u8], ) -> Result<(), Error> { + let decoder = workspace.decoder.as_mut().expect("readied with the frame"); let before = out.produced(); let mut out = Budget { end: before + self.block_max, @@ -784,6 +828,12 @@ impl LegacyFrame { use core::hash::Hasher; hash.write(out.since(before)); } + #[cfg(all(feature = "lsm", feature = "hash"))] + if self.block_digests { + let digest = twox_hash::XxHash64::oneshot(0, out.since(before)); + // The low 32 bits, as for an RFC 8878 frame's blocks. + workspace.block_digests.push(digest as u32); + } self.blocks += 1; Ok(()) } @@ -822,18 +872,17 @@ impl LegacyFrame { } } -/// Decode the frame starting `src` into `out`, in `decoder`'s storage. -/// Returns the bytes the frame took. +/// Decode the frame starting `src` into `out`, in `workspace`. Returns the +/// bytes the frame took and the frame, finished. fn decode_frame( src: &[u8], dict: LegacyDict<'_>, options: FrameOptions, - decoder: &mut Option, + workspace: &mut Workspace, out: &mut O, -) -> Result { +) -> Result<(usize, LegacyFrame), Error> { let header = read_header(src)?; - let mut frame = LegacyFrame::new(header, dict, options, decoder)?; - let decoder = decoder.as_mut().expect("the frame readied it"); + let mut frame = LegacyFrame::new(header, dict, options, workspace)?; let dict_bytes = dict.content_bytes(); let mut at = header.size; loop { @@ -845,9 +894,9 @@ fn decode_frame( let body = src.get(at..at + block.body_len()).ok_or(Error::Truncated)?; if ends_frame(header.version, block) { frame.finish(end)?; - return Ok(at); + return Ok((at, frame)); } - frame.block(decoder, block, body, out, dict_bytes)?; + frame.block(workspace, block, body, out, dict_bytes)?; at += body.len(); } } @@ -860,8 +909,8 @@ fn decode_frame( pub(crate) struct LegacyStream { /// The frame in progress; `None` when no frame is. frame: Option, - /// The per-format decoder, kept across frames, whole-buffer ones too. - decoder: Option, + /// What frames decode in, kept across frames, whole-buffer ones too. + workspace: Workspace, /// The dictionary the frame was started with. dict: Option, /// The window the frame needs. @@ -895,7 +944,11 @@ impl LegacyStream { pub(crate) const fn new() -> LegacyStream { LegacyStream { frame: None, - decoder: None, + workspace: Workspace { + decoder: None, + #[cfg(all(feature = "lsm", feature = "hash"))] + block_digests: Vec::new(), + }, dict: None, window: 0, window_refused_at: None, @@ -927,7 +980,7 @@ impl LegacyStream { header, legacy_dict, options, - &mut self.decoder, + &mut self.workspace, )?); self.dict = dict; self.window = header.window; @@ -959,8 +1012,9 @@ impl LegacyStream { } /// Decode the whole frame starting `src` into `out`, in the stream's - /// decoder, leaving any frame the stream had in progress. Returns the - /// bytes the frame took. + /// workspace, leaving any frame the stream had in progress. The frame + /// stays the stream's, finished with all its output handed out, so the + /// decoder's accessors describe it. Returns the bytes the frame took. pub(crate) fn decode_whole( &mut self, src: &[u8], @@ -969,7 +1023,14 @@ impl LegacyStream { out: &mut O, ) -> Result { self.end(); - decode_frame(src, dict, options, &mut self.decoder, out) + let before = out.produced(); + let (consumed, frame) = decode_frame(src, dict, options, &mut self.workspace, out)?; + self.frame = Some(frame); + self.earlier = out.produced() - before; + self.previous_end = None; + self.finished = true; + self.consumed = consumed as u64; + Ok(consumed) } /// The content size the frame declares, 0 when it declares none. @@ -994,6 +1055,12 @@ impl LegacyStream { self.frame.as_ref()?.calculated_checksum } + /// The frame's per-block digests so far, when they were asked for. + #[cfg(all(feature = "lsm", feature = "hash"))] + pub(crate) fn block_digests(&self) -> &[u32] { + &self.workspace.block_digests + } + /// Take a new window ceiling: a frame whose window it no longer admits /// is refused at its next step, one it admits again goes on. pub(crate) fn set_window_ceiling(&mut self, limit: u64) { @@ -1037,9 +1104,7 @@ impl LegacyStream { /// Heap bytes the stream holds: its buffers and the decoder's tables and /// literal buffer. pub(crate) fn heap_bytes(&self) -> usize { - self.buf.capacity() - + self.block.capacity() - + self.decoder.as_ref().map_or(0, VersionDecoder::heap_bytes) + self.buf.capacity() + self.block.capacity() + self.workspace.heap_bytes() } /// How many bytes from the start of `pending` the next step needs: a @@ -1141,7 +1206,7 @@ impl LegacyStream { fn step(&mut self, step: &[u8]) -> Result<(), Error> { let LegacyStream { frame, - decoder, + workspace, dict, window: _, window_refused_at: _, @@ -1156,7 +1221,6 @@ impl LegacyStream { block: _, } = self; let frame = frame.as_mut().ok_or(corrupt("no frame in progress"))?; - let decoder = decoder.as_mut().expect("started with the frame"); let block = Block::read(step)?; block.check(frame.version, frame.block_max)?; *consumed += step.len() as u64; @@ -1187,7 +1251,7 @@ impl LegacyStream { previous_end: *previous_end, capacity: *capacity, }; - let decoded = frame.block(decoder, block, &step[3..], &mut out, dict_bytes); + let decoded = frame.block(workspace, block, &step[3..], &mut out, dict_bytes); *end = out.end; decoded } diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs index 54375572..65626a71 100644 --- a/zstd/src/decoding/legacy/tests.rs +++ b/zstd/src/decoding/legacy/tests.rs @@ -93,7 +93,12 @@ mod fixtures { use fixtures::all as fixtures; fn decode_to_vec(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec, usize), Error> { - decode_to_vec_in(frame, dict, FrameOptions::default(), &mut None) + decode_to_vec_in( + frame, + dict, + FrameOptions::default(), + &mut Workspace::default(), + ) } /// [`decode_to_vec`] refusing a frame whose content checksum does not match. @@ -103,24 +108,23 @@ fn decode_verified(frame: &[u8], dict: LegacyDict<'_>) -> Result<(Vec, usize checksum: crate::decoding::ContentChecksum::Verify, ..FrameOptions::default() }; - decode_to_vec_in(frame, dict, options, &mut None) + decode_to_vec_in(frame, dict, options, &mut Workspace::default()) } -/// [`decode_to_vec`] with `options`, in `decoder`, the storage an earlier +/// [`decode_to_vec`] with `options`, in `workspace`, the storage an earlier /// frame left. fn decode_to_vec_in( frame: &[u8], dict: LegacyDict<'_>, options: FrameOptions, - decoder: &mut Option, + workspace: &mut Workspace, ) -> Result<(Vec, usize), Error> { let mut buf = Vec::new(); let mut out = VecOutput { buf: &mut buf, start: 0, - limit: usize::MAX, }; - let consumed = decode_frame(frame, dict, options, decoder, &mut out)?; + let (consumed, _) = decode_frame(frame, dict, options, workspace, &mut out)?; Ok((buf, consumed)) } @@ -130,7 +134,7 @@ fn decode_to_vec_in( #[cfg(feature = "std")] #[test] fn fixtures_decode_to_their_content() { - let mut reused = None; + let mut reused = Workspace::default(); for fixture in fixtures() { assert_eq!( LegacyVersion::of(&fixture.frame), @@ -138,12 +142,12 @@ fn fixtures_decode_to_their_content() { "{}", fixture.name ); - for decoder in [&mut None, &mut reused] { + for workspace in [&mut Workspace::default(), &mut reused] { let (decoded, consumed) = decode_to_vec_in( &fixture.frame, fixture.legacy_dict(), FrameOptions::default(), - decoder, + workspace, ) .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); assert_eq!(consumed, fixture.frame.len(), "{}", fixture.name); @@ -172,7 +176,7 @@ fn fixtures_decode_into_a_slice() { &fixture.frame, dict, FrameOptions::default(), - &mut None, + &mut Workspace::default(), &mut out, ) .unwrap_or_else(|err| panic!("{}: {err}", fixture.name)); @@ -185,15 +189,17 @@ fn fixtures_decode_into_a_slice() { buf: &mut buf, pos: 0, }; - assert_eq!( - decode_frame( - &fixture.frame, - dict, - FrameOptions::default(), - &mut None, - &mut out + assert!( + matches!( + decode_frame( + &fixture.frame, + dict, + FrameOptions::default(), + &mut Workspace::default(), + &mut out + ), + Err(Error::OutputTooSmall) ), - Err(Error::OutputTooSmall), "{}", fixture.name ); @@ -971,6 +977,14 @@ fn a_lowered_window_ceiling_binds_a_started_frame() { .decode_blocks(&mut src, BlockDecodingStrategy::All) .unwrap(); assert!(decoder.collect().unwrap() == fixture.content); + + // A ceiling below the window refuses the frame from its header. + decoder.set_max_window_size(window / 2).unwrap(); + assert!(matches!( + decoder.reset(&fixture.frame[..]), + Err(FrameDecoderError::WindowSizeTooBig { requested, limit }) + if requested == window && limit == window / 2 + )); } /// The decoder's accessors describe a started frame of these formats: its @@ -1006,6 +1020,9 @@ fn the_accessors_describe_these_frames() { decoder.blocks_decoded() as u64, bound / BLOCK_SIZE_MAX as u64 ); + // The stream's buffers, past a block of output, count toward the + // decoder's footprint. + assert!(decoder.workspace_size() > BLOCK_SIZE_MAX); // v0.5 declares none. let v05 = fixture("abc.txt.v05.l1.zst"); @@ -1014,6 +1031,202 @@ fn the_accessors_describe_these_frames() { assert_eq!(decoder.content_size(), 0); } +/// Each failure carries the code libzstd returns for it, and keeps that code, +/// its message and itself as the source when the frame decoder wraps it. +#[cfg(feature = "std")] +#[test] +fn errors_report_libzstd_codes() { + use crate::decoding::errors::{ErrorCode, FrameDecoderError}; + use std::error::Error as _; + use std::string::ToString; + let cases = [ + ( + Error::Truncated, + ErrorCode::CorruptionDetected, + "ends inside", + ), + ( + Error::UnsupportedParameter, + ErrorCode::FrameParameterUnsupported, + "unsupported setting", + ), + ( + Error::Corrupted { + reason: "bad table", + }, + ErrorCode::CorruptionDetected, + "bad table", + ), + ( + Error::OutputTooSmall, + ErrorCode::DstSizeTooSmall, + "too small", + ), + ( + Error::DictionaryCorrupted, + ErrorCode::DictionaryCorrupted, + "dictionary could not be read", + ), + ( + Error::DictionaryMismatch { + expected: 0xAB, + provided: 0, + }, + ErrorCode::DictionaryWrong, + "dictionary 0xAB", + ), + ( + Error::ChecksumMismatch { + expected: 1, + calculated: 2, + }, + ErrorCode::ChecksumWrong, + "0x000001", + ), + ]; + for (err, code, text) in cases { + assert_eq!(err.code(), code, "{err:?}"); + let message = err.to_string(); + assert!(message.contains(text), "{message}"); + let wrapped = FrameDecoderError::from(err); + assert_eq!(wrapped.code(), code); + assert_eq!(wrapped.to_string(), message); + assert!( + wrapped + .source() + .is_some_and(|source| source.to_string() == message) + ); + } +} + +/// After `decode_all` the accessors describe the frame it decoded last, as +/// they do an RFC 8878 one, not the frame decoded before it. +#[cfg(feature = "std")] +#[test] +fn the_accessors_describe_a_frame_decoded_whole() { + use crate::decoding::FrameDecoder; + let rfc = crate::encoding::compress_slice_to_vec( + &[7u8; 500], + crate::encoding::CompressionLevel::Fastest, + ); + let v06 = fixture("z000000.v06.l1.zst"); + let mut decoder = FrameDecoder::new(); + let mut out = vec![0u8; v06.content.len()]; + decoder.decode_all(&rfc, &mut out).unwrap(); + assert_eq!(decoder.content_size(), 500); + + assert_eq!( + decoder.decode_all(&v06.frame, &mut out).unwrap(), + v06.content.len() + ); + let (_, bound) = frame_size(LegacyVersion::V06, &v06.frame).unwrap(); + assert_eq!(decoder.content_size(), 128 * 1024); + assert_eq!( + decoder.blocks_decoded() as u64, + bound / BLOCK_SIZE_MAX as u64 + ); + assert_eq!(decoder.bytes_read_from_source(), v06.frame.len() as u64); + assert!(decoder.is_finished()); + assert_eq!(decoder.can_collect(), 0); + + #[cfg(feature = "hash")] + { + let v07 = fixture("abc.txt.v07.l1.zst"); + let end = &v07.frame[v07.frame.len() - 3..]; + let stored = u32::from(end[2]) | u32::from(end[1]) << 8 | u32::from(end[0] & 0x3F) << 16; + decoder.decode_all(&v07.frame, &mut out).unwrap(); + assert_eq!(decoder.get_checksum_from_data(), Some(stored)); + assert_eq!(decoder.get_calculated_checksum(), Some(stored)); + } +} + +/// Per-block checksums cover these frames' blocks on every path, as they +/// cover an RFC 8878 frame's. +#[cfg(all(feature = "std", feature = "lsm", feature = "hash"))] +#[test] +fn per_block_checksums_cover_these_frames() { + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + // One data block: its digest is the content's. + let one = fixture("abc.txt.v07.l1.zst"); + let digest = twox_hash::XxHash64::oneshot(0, &one.content) as u32; + let many = fixture("z000000.v07.l1.zst"); + let (_, bound) = frame_size(LegacyVersion::V07, &many.frame).unwrap(); + let blocks = (bound / BLOCK_SIZE_MAX as u64) as usize; + + let mut decoder = FrameDecoder::new(); + decoder.enable_per_block_checksums(); + let mut out = vec![0u8; many.content.len()]; + decoder.decode_all(&one.frame, &mut out).unwrap(); + assert_eq!(decoder.computed_block_checksums(), [digest]); + decoder.decode_all(&many.frame, &mut out).unwrap(); + assert_eq!(decoder.computed_block_checksums().len(), blocks); + + let mut src = &one.frame[..]; + decoder.reset(&mut src).unwrap(); + decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap(); + assert_eq!(decoder.computed_block_checksums(), [digest]); +} + +/// A pinned dictionary ID or window descriptor holds these frames to it, on +/// both paths: v0.7 carries both fields, v0.5 and v0.6 neither. +#[cfg(all(feature = "std", feature = "lsm"))] +#[test] +fn pinned_header_fields_bind_these_frames() { + use crate::decoding::FrameDecoder; + use crate::decoding::errors::FrameDecoderError; + let v07 = fixture("z000000x8.v07.l1.zst"); + // Not single-segment: the window byte follows the descriptor. + assert_eq!(v07.frame[4] & 0x20, 0); + let window = v07.frame[5]; + let v05 = fixture("abc.txt.v05.l1.zst"); + let mut out = vec![0u8; v07.content.len()]; + + let mut decoder = FrameDecoder::new(); + decoder.expect_dict_id(Some(5)); + assert!(matches!( + decoder.decode_all(&v07.frame, &mut out), + Err(FrameDecoderError::UnexpectedDictId { + expected: Some(5), + found: None + }) + )); + assert!(matches!( + decoder.reset(&v07.frame[..]), + Err(FrameDecoderError::UnexpectedDictId { .. }) + )); + decoder.expect_dict_id(Some(0)); + decoder.expect_window_descriptor(Some(window ^ 1)); + assert!(matches!( + decoder.decode_all(&v07.frame, &mut out), + Err(FrameDecoderError::UnexpectedWindowDescriptor { found: Some(w), .. }) if w == window + )); + assert!(matches!( + decoder.reset(&v05.frame[..]), + Err(FrameDecoderError::UnexpectedWindowDescriptor { found: None, .. }) + )); + decoder.expect_window_descriptor(Some(window)); + assert_eq!( + decoder.decode_all(&v07.frame, &mut out).unwrap(), + v07.content.len() + ); +} + +/// A v0.5-v0.7 magic number with nothing after it is a truncated frame to +/// the aggregate size query, as upstream's `ZSTD_findDecompressedSize` +/// refuses a tail shorter than a frame's first five bytes. +#[cfg(feature = "std")] +#[test] +fn a_bare_magic_number_is_a_truncated_frame() { + use crate::decoding::find_decompressed_size; + let magic = [0x27, 0xB5, 0x2F, 0xFD]; + assert!(find_decompressed_size(&magic).is_err()); + let mut stream = fixture("z000000.v07.l1.zst").frame; + stream.extend_from_slice(&magic); + assert!(find_decompressed_size(&stream).is_err()); +} + /// A reader failing after the magic number hands its own error back, in the /// header field it failed in, rather than calling the frame truncated. #[cfg(feature = "std")] @@ -1057,6 +1270,18 @@ fn reader_errors_inside_the_header_are_kept() { ), "{err:?}" ); + // v0.7 with its window byte read and a one-byte dictionary ID to come. + let err = decoder + .reset(Stalls(&[0x27, 0xB5, 0x2F, 0xFD, 0x01, 0x00])) + .unwrap_err(); + assert!( + matches!( + &err, + FrameDecoderError::ReadFrameHeaderError(ReadFrameHeaderError::DictionaryIdReadError(e)) + if e.kind() == std::io::ErrorKind::WouldBlock + ), + "{err:?}" + ); // v0.6 with a two-byte content size still to come. let err = decoder .reset(Stalls(&[0x26, 0xB5, 0x2F, 0xFD, 0x80, 1])) @@ -1076,20 +1301,26 @@ fn reader_errors_inside_the_header_are_kept() { #[cfg(feature = "std")] #[test] fn the_stream_footprint_counts_its_tables() { - let fixture = fixture("z000000.v07.l1.zst"); - let header = read_header(&fixture.frame).unwrap(); - let mut stream = LegacyStream::new(); - stream.start(header, None, FrameOptions::default()).unwrap(); - let mut target = vec![0u8; fixture.content.len()]; - stream - .decode(&fixture.frame[header.size..], &mut target) - .unwrap(); - assert!( - stream.heap_bytes() > stream.buf.capacity() + stream.block.capacity(), - "{} bytes held, {} of them the output buffer", - stream.heap_bytes(), - stream.buf.capacity() - ); + for name in [ + "z000000.v05.l1.zst", + "z000000.v06.l1.zst", + "z000000.v07.l1.zst", + ] { + let fixture = fixture(name); + let header = read_header(&fixture.frame).unwrap(); + let mut stream = LegacyStream::new(); + stream.start(header, None, FrameOptions::default()).unwrap(); + let mut target = vec![0u8; fixture.content.len()]; + stream + .decode(&fixture.frame[header.size..], &mut target) + .unwrap(); + assert!( + stream.heap_bytes() > stream.buf.capacity() + stream.block.capacity(), + "{name}: {} bytes held, {} of them the output buffer", + stream.heap_bytes(), + stream.buf.capacity() + ); + } } /// A block whose header declares more than the largest block is refused from @@ -1101,6 +1332,8 @@ fn an_oversized_block_is_refused_from_its_header() { let header = read_header(&frame).unwrap(); let mut stream = LegacyStream::new(); stream.start(header, None, FrameOptions::default()).unwrap(); + // Less than a block header asks for the header. + assert_eq!(stream.input_needed(&[0x40]), 3); // A raw block of 0x3FFFF bytes, compressed and raw alike. for kind in [0x00u8, 0x40] { let block = [kind | 0x03, 0xFF, 0xFF]; @@ -1109,6 +1342,278 @@ fn an_oversized_block_is_refused_from_its_header() { } } +/// A v0.7 block whose sequence tables are each one symbol decodes, and one +/// naming a symbol past its field is refused. +#[test] +fn one_symbol_sequence_tables_decode_in_v07() { + // Raw literals "a"; one sequence; every table one symbol: literal length + // code 1, offset code 2 (two extra bits, here 0, so offset 1), match + // length code 0 (3 bytes). The bitstream holds those two bits under its + // end mark. + let block = [0x81, b'a', 0x01, 0x54, 1, 2, 0, 0x04]; + let mut frame = vec![0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00, 0x00, 0x00]; + frame.push(block.len() as u8); + frame.extend_from_slice(&block); + frame.extend_from_slice(&[0xC0, 0, 0]); + assert_eq!(decode_to_vec(&frame, LegacyDict::None).unwrap().0, b"aaaa"); + // A match length symbol past the field's largest. + let at = frame.len() - 4; + frame[at - 1] = 200; + assert!(matches!( + decode_to_vec(&frame, LegacyDict::None), + Err(Error::Corrupted { .. }) + )); +} + +/// A sequence count takes one byte below 0x80, two up to 0x7EFF, and three +/// after a 0xFF where the format allows them. +#[test] +fn sequence_counts_take_their_forms() { + assert_eq!(v07::sequence_count(&[0x7F], true).unwrap(), (0x7F, 1)); + assert_eq!( + v07::sequence_count(&[0x81, 0x02], true).unwrap(), + (0x102, 2) + ); + assert_eq!( + v07::sequence_count(&[0xFF, 0x01, 0x00], true).unwrap(), + (0x7F01, 3) + ); + assert_eq!( + v07::sequence_count(&[0xFF, 0x01], false).unwrap(), + (0x7F01, 2) + ); + assert!(v07::sequence_count(&[], true).is_err()); + assert!(v07::sequence_count(&[0xFF, 0x01], true).is_err()); + assert!(v07::sequence_count(&[0x80], true).is_err()); +} + +/// The version queries: each format's minor version, and a header's length +/// from its size fields once the descriptor is in hand. +#[test] +fn versions_answer_their_queries() { + assert_eq!(LegacyVersion::V05.minor(), 5); + assert_eq!(LegacyVersion::V06.minor(), 6); + assert_eq!(LegacyVersion::V07.minor(), 7); + let v05 = [0x25, 0xB5, 0x2F, 0xFD, 0x01]; + assert_eq!(LegacyVersion::V05.header_size(&v05[..4]), None); + assert_eq!(LegacyVersion::V05.header_size(&v05), Some(5)); + // v0.6 with a two-byte content size. + assert_eq!( + LegacyVersion::V06.header_size(&[0x26, 0xB5, 0x2F, 0xFD, 0x80]), + Some(7) + ); + // v0.7, single-segment with an eight-byte content size and a four-byte + // dictionary ID. + assert_eq!( + LegacyVersion::V07.header_size(&[0x27, 0xB5, 0x2F, 0xFD, 0xE3]), + Some(17) + ); +} + +/// A literal section header the block cannot hold, or one past the largest +/// block, is refused before anything decodes. +#[test] +fn literal_headers_refuse_what_does_not_fit() { + for header in [ + // Huffman literals with less than their five-byte minimum. + &[0x00, 0, 0, 0][..], + // Huffman literals declaring more than a block. + &[0x3F, 0xFF, 0xFC, 0, 0, 0], + // Huffman literals running past the block. + &[0x00, 0x04, 0xFF, 0, 0], + // Repeated-table literals in a form other than the small one. + &[0x40, 0, 0], + // Repeated-table literals running past the block. + &[0x50, 0x03, 0xFF], + // Raw literals running past the block. + &[0x9F, 0, 0], + // A run with a three-byte header and nothing after it. + &[0xF0, 0, 0], + // A run longer than a block. + &[0xFF, 0xFF, 0xFF, 0], + // Less than any literal section. + &[0x80, 0], + ] { + assert!( + matches!(Literals::read(header), Err(Error::Corrupted { .. })), + "{header:02X?}" + ); + } +} + +/// A block's budget passes everything but its size check to the output it +/// guards. +#[cfg(feature = "hash")] +#[test] +fn a_budget_passes_through_to_its_output() { + let mut buf = Vec::new(); + let mut inner = VecOutput { + buf: &mut buf, + start: 0, + }; + let mut out = Budget { + out: &mut inner, + end: 8, + }; + out.reserve(2).unwrap(); + out.extend(b"ab"); + out.repeat(b'c', 2); + out.copy_back(4, 2); + assert_eq!((out.produced(), out.held()), (6, 6)); + assert_eq!(out.since(2), b"ccab"); + assert!(out.reserve(3).is_err()); + out.copy_match(1, 2, &[]).unwrap(); + assert_eq!(buf, b"abccabbb"); +} + +/// The size walk refuses a frame cut inside a block's body. +#[cfg(feature = "std")] +#[test] +fn frame_size_refuses_a_cut_block() { + let fixture = fixture("z000000.v07.l1.zst"); + let frame = &fixture.frame[..fixture.frame.len() - 10]; + assert_eq!(frame_size(LegacyVersion::V07, frame), None); +} + +/// An RLE block decodes into a caller's slice and through a stream as it +/// does into a vector. +#[cfg(feature = "std")] +#[test] +fn rle_blocks_decode_on_every_path() { + use crate::decoding::FrameDecoder; + let frame = [ + 0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00, 0x80, 0, 5, b'z', 0xC0, 0, 0, + ]; + let mut out = [0u8; 5]; + assert_eq!(FrameDecoder::new().decode_all(&frame, &mut out).unwrap(), 5); + assert_eq!(&out, b"zzzzz"); + let mut decoder = FrameDecoder::new(); + let mut target = [0u8; 8]; + let (read, written) = decoder.decode_from_to(&frame, &mut target).unwrap(); + assert_eq!((read, &target[..written]), (frame.len(), &b"zzzzz"[..])); +} + +/// A `Read` source failing inside a block hands back its own error, in the +/// block header or the body it failed in. +#[cfg(feature = "std")] +#[test] +fn reader_errors_inside_a_block_are_kept() { + use crate::decoding::errors::FrameDecoderError; + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + let fixture = fixture("z000000.v07.l1.zst"); + let header = read_header(&fixture.frame).unwrap(); + for (cut, header_error) in [(header.size + 1, true), (header.size + 4, false)] { + let mut decoder = FrameDecoder::new(); + let mut src = &fixture.frame[..cut]; + decoder.reset(&mut src).unwrap(); + let err = decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap_err(); + if header_error { + assert!( + matches!(err, FrameDecoderError::FailedToReadBlockHeader(_)), + "{err:?}" + ); + } else { + assert!( + matches!(err, FrameDecoderError::FailedToReadBlockBody(_)), + "{err:?}" + ); + } + } +} + +/// `read_to_end` over an RFC 8878 frame and then a v0.7 one decodes both +/// with the dictionary the reader was forced to, and hands out nothing of a +/// v0.7 frame that fails. +#[cfg(feature = "std")] +#[test] +fn read_to_end_keeps_a_forced_dictionary_for_these_frames() { + use crate::decoding::{DictionaryHandle, StreamingDecoder}; + use std::io::Read; + let v07 = fixture("ModemManager.service.v07.dict.zst"); + let dict = + DictionaryHandle::from_serialized_or_raw_content(v07.dict.as_deref().unwrap()).unwrap(); + let leading = b"a leading frame"; + let mut input = + crate::encoding::compress_slice_to_vec(leading, crate::encoding::CompressionLevel::Fastest); + let rfc_len = input.len(); + input.extend_from_slice(&v07.frame); + let mut out = Vec::new(); + StreamingDecoder::new_with_dictionary_handle(&input[..], &dict) + .unwrap() + .read_to_end(&mut out) + .unwrap(); + let mut expected = leading.to_vec(); + expected.extend_from_slice(&v07.content); + assert!(out == expected); + + // The v0.7 frame cut short: its bytes are not handed out. + let cut = &input[..input.len() - 1]; + assert!(cut.len() > rfc_len); + let mut out = Vec::new(); + assert!( + StreamingDecoder::new_with_dictionary_handle(cut, &dict) + .unwrap() + .read_to_end(&mut out) + .is_err() + ); + assert_eq!(out, leading); +} + +/// Output a caller does not collect stays in the buffer however far past the +/// window it grows, and all of it is handed out once collected. +#[cfg(feature = "std")] +#[test] +fn output_held_back_grows_the_buffer() { + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + let fixture = fixture("z000000x8.v07.l1.zst"); + let mut decoder = FrameDecoder::new(); + let mut src = &fixture.frame[..]; + decoder.reset(&mut src).unwrap(); + decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap(); + assert!(decoder.collect().unwrap() == fixture.content); +} + +/// Damaged frames are refused or decode to some output, but never panic, +/// wherever the damage lands: random bytes overwritten, through every format +/// and both decoding paths. +#[cfg(feature = "std")] +#[test] +fn overwritten_bytes_never_panic() { + use crate::decoding::FrameDecoder; + let mut state = 0x2545_F491_4F6C_DD1Du64; + let mut next = move || { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + state + }; + for fixture in fixtures().into_iter().filter(|f| f.frame.len() < 16384) { + let dict = fixture.legacy_dict(); + let header = read_header(&fixture.frame).unwrap().size; + for _ in 0..64 { + let mut frame = fixture.frame.clone(); + for _ in 0..(next() % 4) + 1 { + let at = header + (next() as usize) % (frame.len() - header); + frame[at] = next() as u8; + } + let _whole = decode_verified(&frame, dict).is_ok(); + let mut decoder = FrameDecoder::new(); + let mut target = vec![0u8; 4096]; + let mut src = &frame[..]; + while let Ok((read, written)) = decoder.decode_from_to(src, &mut target) { + if read == 0 && written == 0 { + break; + } + src = &src[read..]; + } + } + } +} + /// An RLE block repeats its byte in v0.7; v0.5 and v0.6 reserve the type, /// and their decoders refuse it (`ZSTDv0{5,6}_decompressFrame`). #[test] diff --git a/zstd/src/decoding/mod.rs b/zstd/src/decoding/mod.rs index 93dd85bd..b887aced 100644 --- a/zstd/src/decoding/mod.rs +++ b/zstd/src/decoding/mod.rs @@ -211,6 +211,14 @@ pub fn find_decompressed_size(src: &[u8]) -> Result { total = total diff --git a/zstd/src/decoding/workspace.rs b/zstd/src/decoding/workspace.rs index 2d7acac0..a8aa7d75 100644 --- a/zstd/src/decoding/workspace.rs +++ b/zstd/src/decoding/workspace.rs @@ -16,6 +16,8 @@ //! //! Frames that name a dictionary are refused with //! [`Unsupported::Dictionary`]; this decoder keeps no dictionary registry. +//! With the `legacy` feature, v0.5-v0.7 frames are refused with +//! `Unsupported::LegacyFrame`, since their decoders allocate. //! //! The decoding itself is the same code the other decoders run, so output is //! identical to theirs. @@ -356,6 +358,14 @@ pub enum Unsupported { /// The dictionary id the frame header names. id: u32, }, + /// A v0.5, v0.6 or v0.7 frame, which the owned decoders read and this + /// one does not: their decoders allocate. Upstream's static context + /// refuses them likewise. + #[cfg(feature = "legacy")] + LegacyFrame { + /// The format of the frame. + version: crate::decoding::LegacyVersion, + }, } /// How the input breaks the zstd format. @@ -437,6 +447,12 @@ impl core::fmt::Display for WorkspaceDecodeError { "frame needs dictionary {id}, which this decoder does not take" ) } + #[cfg(feature = "legacy")] + Self::Unsupported(Unsupported::LegacyFrame { version }) => write!( + f, + "v0.{} frame, which this decoder does not read", + version.minor() + ), Self::Truncated => write!(f, "input ended in the middle of a frame"), Self::Failed => write!(f, "an earlier step failed; reset the decoder"), } @@ -1000,6 +1016,13 @@ impl<'w> WorkspaceDecoder<'w> { } else if magic == MAGIC_NUM { self.expect_header(Phase::Descriptor, 5); } else { + #[cfg(feature = "legacy")] + if let Some(version) = crate::decoding::LegacyVersion::of(&magic.to_le_bytes()) + { + return Err(WorkspaceDecodeError::Unsupported( + Unsupported::LegacyFrame { version }, + )); + } return Err(Malformed(MalformedInput::FrameHeader( ReadFrameHeaderError::BadMagicNumber(magic), ))); diff --git a/zstd/src/decoding/workspace/tests.rs b/zstd/src/decoding/workspace/tests.rs index ac61df9d..8086871f 100644 --- a/zstd/src/decoding/workspace/tests.rs +++ b/zstd/src/decoding/workspace/tests.rs @@ -506,6 +506,36 @@ fn dictionary_frame_is_unsupported() { )); } +/// A v0.5-v0.7 frame, which the owned decoders read, is refused as +/// unsupported rather than as malformed: decoding it would allocate. +#[cfg(feature = "legacy")] +#[test] +fn v05_to_v07_frames_are_unsupported() { + // v0.7 with a one-byte dictionary ID of 0 and no blocks before its end. + let frame = [ + 0x27, 0xB5, 0x2F, 0xFD, 0x51, 0x00, 0x00, 0xFA, 0x00, 0xFF, 0x00, 0x00, + ]; + let mut out = [0u8; 16]; + assert_eq!( + crate::decoding::FrameDecoder::new() + .decode_all(&frame, &mut out) + .unwrap(), + 0 + ); + assert!(matches!( + decode_whole(&frame, DecodeLimits::new(1 << 20)), + Err(WorkspaceDecodeError::Unsupported( + Unsupported::LegacyFrame { + version: crate::decoding::LegacyVersion::V07 + } + )) + )); + let message = alloc::string::ToString::to_string( + &decode_whole(&frame, DecodeLimits::new(1 << 20)).unwrap_err(), + ); + assert!(message.contains("v0.7"), "{message}"); +} + /// Bytes after the last frame that are not a frame are malformed; the error /// holds until a reset, after which the decoder works again. #[test] From 06e84e4f17a67fe5d3992a517fe5124f9463addd Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Tue, 20 Oct 2026 13:53:29 +0300 Subject: [PATCH 10/11] fix(decoding): carry dictionaries and header queries to v0.5-v0.7 - force_dict gives a started v0.5-v0.7 frame the registered dictionary, as it does an RFC 8878 frame: its tables, repeat offsets and content prime the blocks still to come; a v0.7 header naming another dictionary still refuses it - FrameDecoder::dictionary reports the handle such a frame decodes with - frame_header_size, and so ZSTD_frameHeaderSize, measures these headers by their own size fields instead of refusing the frames ZSTD_isFrame accepts - a reader failing inside a raw or RLE block's body names that block type in the error Each fix carries a regression test. Part of #131 --- c-api/src/tests/legacy.rs | 6 ++- zstd/src/decoding/frame.rs | 2 +- zstd/src/decoding/frame_decoder.rs | 14 ++++++ zstd/src/decoding/legacy/mod.rs | 81 ++++++++++++++++++++++++------ zstd/src/decoding/legacy/tests.rs | 77 ++++++++++++++++++++++++++++ zstd/src/decoding/mod.rs | 12 ++++- 6 files changed, 173 insertions(+), 19 deletions(-) diff --git a/c-api/src/tests/legacy.rs b/c-api/src/tests/legacy.rs index 055c4c38..221f54d3 100644 --- a/c-api/src/tests/legacy.rs +++ b/c-api/src/tests/legacy.rs @@ -9,7 +9,7 @@ use crate::attach::ZSTD_decompress_usingDict; use crate::cdict::{ZSTD_createDDict, ZSTD_freeDDict}; use crate::context::{ZSTD_createDCtx, ZSTD_freeDCtx}; use crate::error::{ZSTD_ErrorCode, ZSTD_getErrorCode, ZSTD_isError}; -use crate::frame::{ZSTD_decompressBound, ZSTD_isFrame}; +use crate::frame::{ZSTD_decompressBound, ZSTD_frameHeaderSize, ZSTD_isFrame}; use crate::simple::{ZSTD_decompress, ZSTD_findFrameCompressedSize, ZSTD_getFrameContentSize}; use crate::streaming::{ ZSTD_createDStream, ZSTD_decompressStream, ZSTD_freeDStream, ZSTD_inBuffer, @@ -87,6 +87,10 @@ fn frame_queries_read_these_frames() { "{}", fixture.name ); + // A frame `ZSTD_isFrame` accepts has a header to measure. + let header = unsafe { ZSTD_frameHeaderSize(frame.as_ptr(), frame.len()) }; + assert_eq!(ZSTD_isError(header), 0, "{}", fixture.name); + assert!((5..=18).contains(&header), "{}: {header}", fixture.name); let bound = unsafe { ZSTD_decompressBound(frame.as_ptr(), frame.len()) }; assert!(bound >= fixture.content.len() as u64, "{}", fixture.name); assert_eq!(bound % (128 * 1024), 0, "{}", fixture.name); diff --git a/zstd/src/decoding/frame.rs b/zstd/src/decoding/frame.rs index aca9aad6..8a4281e9 100644 --- a/zstd/src/decoding/frame.rs +++ b/zstd/src/decoding/frame.rs @@ -107,7 +107,7 @@ pub fn read_frame_header_with_format( Ok((frame_header, bytes_read as u8)) } -fn eof() -> crate::io::Error { +pub(crate) fn eof() -> crate::io::Error { crate::io::Error::from(crate::io::ErrorKind::UnexpectedEof) } diff --git a/zstd/src/decoding/frame_decoder.rs b/zstd/src/decoding/frame_decoder.rs index efd1d5da..9efc28ff 100644 --- a/zstd/src/decoding/frame_decoder.rs +++ b/zstd/src/decoding/frame_decoder.rs @@ -2680,6 +2680,16 @@ impl FrameDecoder { pub fn force_dict(&mut self, dict_id: u32) -> Result<(), FrameDecoderError> { use FrameDecoderError as err; + #[cfg(feature = "legacy")] + if self.legacy_frame().is_some() { + // The stream outlives this call, so it holds the handle. + let dict = self + .registered_dict(dict_id) + .ok_or(err::DictNotProvided { dict_id })? + .clone(); + let stream = self.legacy_frame_mut().expect("checked above"); + return Ok(stream.force_dict(dict)?); + } let state = self.state.as_mut().ok_or(err::NotYetInitialized)?; let owned_dicts = &self.owned_dicts; #[cfg(target_has_atomic = "ptr")] @@ -2893,6 +2903,10 @@ impl FrameDecoder { /// assert!(FrameDecoder::new().dictionary().is_none()); /// ``` pub fn dictionary(&self) -> Option<&DictionaryHandle> { + #[cfg(feature = "legacy")] + if let Some(stream) = self.legacy_frame() { + return stream.dictionary(); + } self.state.as_ref().and_then(|s| s.active_dict.as_ref()) } diff --git a/zstd/src/decoding/legacy/mod.rs b/zstd/src/decoding/legacy/mod.rs index 6ac4d9dc..16d9c64c 100644 --- a/zstd/src/decoding/legacy/mod.rs +++ b/zstd/src/decoding/legacy/mod.rs @@ -707,6 +707,8 @@ impl Output for Budget<'_, O> { /// per-format decoder is passed too, so it outlives the frame. struct LegacyFrame { version: LegacyVersion, + /// The dictionary the header names, 0 for none. + dict_id: u32, /// Where the dictionary's content lies within its bytes. content: core::ops::Range, /// Most bytes a block may hold, the caller's limit within the formats'. @@ -750,21 +752,7 @@ impl LegacyFrame { LegacyVersion::V07 => VersionDecoder::V07(v07::Decoder::empty()), }); } - let bytes = dict.content_bytes(); - let content = match decoder.as_mut().expect("set above") { - VersionDecoder::V07(decoder) => { - let content = decoder.reset(dict)?; - if header.dict_id != 0 && header.dict_id != decoder.dict_id { - return Err(Error::DictionaryMismatch { - expected: header.dict_id, - provided: decoder.dict_id, - }); - } - content - } - VersionDecoder::V06(decoder) => decoder.reset(bytes)?, - VersionDecoder::V05(decoder) => decoder.reset(bytes)?, - }; + let content = prime(header.dict_id, dict, workspace)?; // Without `hash` no digest can be computed, so `Verify` checks // nothing, as for an RFC 8878 frame. #[cfg(feature = "hash")] @@ -772,6 +760,7 @@ impl LegacyFrame { .then(|| twox_hash::XxHash64::with_seed(0)); Ok(LegacyFrame { version: header.version, + dict_id: header.dict_id, content, block_max: options.block_max.min(BLOCK_SIZE_MAX), #[cfg(feature = "hash")] @@ -789,6 +778,15 @@ impl LegacyFrame { }) } + /// Prime the frame's decoder with `dict` for the blocks still to come, as + /// an RFC 8878 frame's decoder takes a forced dictionary: its tables and + /// repeat offsets become the dictionary's, and matches reach into its + /// content. What the frame has decoded so far stands. + fn set_dict(&mut self, dict: LegacyDict<'_>, workspace: &mut Workspace) -> Result<(), Error> { + self.content = prime(self.dict_id, dict, workspace)?; + Ok(()) + } + /// Decode one block, `body` being the bytes after its header, with the /// bytes of the dictionary the frame was started with. The block has /// passed [`Block::check`]. @@ -872,6 +870,34 @@ impl LegacyFrame { } } +/// Load `dict` into `workspace`'s decoder, readied for the frame's format, +/// and say where the dictionary's content lies in its bytes. A v0.7 header +/// naming a dictionary (`dict_id`, 0 for none) takes only that one, as +/// `ZSTDv07_decompressFrameHeader` refuses another. +fn prime( + dict_id: u32, + dict: LegacyDict<'_>, + workspace: &mut Workspace, +) -> Result, Error> { + let bytes = dict.content_bytes(); + Ok( + match workspace.decoder.as_mut().expect("readied for the frame") { + VersionDecoder::V07(decoder) => { + let content = decoder.reset(dict)?; + if dict_id != 0 && dict_id != decoder.dict_id { + return Err(Error::DictionaryMismatch { + expected: dict_id, + provided: decoder.dict_id, + }); + } + content + } + VersionDecoder::V06(decoder) => decoder.reset(bytes)?, + VersionDecoder::V05(decoder) => decoder.reset(bytes)?, + }, + ) +} + /// Decode the frame starting `src` into `out`, in `workspace`. Returns the /// bytes the frame took and the frame, finished. fn decode_frame( @@ -1055,6 +1081,23 @@ impl LegacyStream { self.frame.as_ref()?.calculated_checksum } + /// Decode the rest of the frame with `dict`, as an RFC 8878 frame takes a + /// forced dictionary. + pub(crate) fn force_dict( + &mut self, + dict: crate::decoding::DictionaryHandle, + ) -> Result<(), Error> { + let frame = self.frame.as_mut().ok_or(corrupt("no frame in progress"))?; + frame.set_dict(LegacyDict::Parsed(dict.as_dict()), &mut self.workspace)?; + self.dict = Some(dict); + Ok(()) + } + + /// The dictionary the frame decodes with, when the stream holds one. + pub(crate) fn dictionary(&self) -> Option<&crate::decoding::DictionaryHandle> { + self.dict.as_ref() + } + /// The frame's per-block digests so far, when they were asked for. #[cfg(all(feature = "lsm", feature = "hash"))] pub(crate) fn block_digests(&self) -> &[u32] { @@ -1190,9 +1233,15 @@ impl LegacyStream { .and_then(|()| { let need = self.input_needed(&block); block.resize(need, 0); + // The header names the block whose body is read. + let step = match Block::read(&block) { + Ok(Block::Raw(_)) => BlockType::Raw, + Ok(Block::Rle(_)) => BlockType::RLE, + _ => BlockType::Compressed, + }; source.read_exact(&mut block[3..]).map_err(|source| { FrameDecoderError::FailedToReadBlockBody(DecodeBlockContentError::ReadError { - step: BlockType::Compressed, + step, source, }) }) diff --git a/zstd/src/decoding/legacy/tests.rs b/zstd/src/decoding/legacy/tests.rs index 65626a71..9f421936 100644 --- a/zstd/src/decoding/legacy/tests.rs +++ b/zstd/src/decoding/legacy/tests.rs @@ -1561,6 +1561,83 @@ fn read_to_end_keeps_a_forced_dictionary_for_these_frames() { assert_eq!(out, leading); } +/// `force_dict` gives a started v0.7 frame that names no dictionary the +/// registered one, as it does an RFC 8878 frame, and `dictionary()` reports +/// the handle the frame decodes with. +#[cfg(all(feature = "std", target_has_atomic = "ptr"))] +#[test] +fn force_dict_and_dictionary_cover_these_frames() { + use crate::decoding::{BlockDecodingStrategy, DictionaryHandle, FrameDecoder}; + let noid = fixture("ModemManager.service.v07.dict-noid.zst"); + let dict = + DictionaryHandle::from_serialized_or_raw_content(noid.dict.as_deref().unwrap()).unwrap(); + let id = dict.id(); + let mut decoder = FrameDecoder::new(); + decoder.add_dict_handle(dict).unwrap(); + + let mut src = &noid.frame[..]; + decoder.reset(&mut src).unwrap(); + assert!(decoder.dictionary().is_none()); + decoder.force_dict(id).unwrap(); + assert_eq!(decoder.dictionary().map(DictionaryHandle::id), Some(id)); + decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap(); + assert!(decoder.collect().unwrap() == noid.content); + + // A frame naming the dictionary takes it from the registry. + let named = fixture("ModemManager.service.v07.dict.zst"); + decoder.reset(&named.frame[..]).unwrap(); + assert_eq!(decoder.dictionary().map(DictionaryHandle::id), Some(id)); +} + +/// The header size query measures these frames by their own size fields. +#[test] +fn frame_header_size_measures_these_frames() { + use crate::decoding::frame_header_size; + assert_eq!( + frame_header_size(&[0x25, 0xB5, 0x2F, 0xFD, 0x01]).unwrap(), + 5 + ); + assert_eq!( + frame_header_size(&[0x26, 0xB5, 0x2F, 0xFD, 0x80]).unwrap(), + 7 + ); + assert_eq!( + frame_header_size(&[0x27, 0xB5, 0x2F, 0xFD, 0xE3]).unwrap(), + 17 + ); + assert!(frame_header_size(&[0x27, 0xB5, 0x2F, 0xFD]).is_err()); +} + +/// A reader failing inside a raw or RLE block's body names that block's +/// type, not a compressed block's. +#[cfg(feature = "std")] +#[test] +fn reader_errors_name_the_block_type() { + use crate::blocks::block::BlockType; + use crate::decoding::errors::{DecodeBlockContentError, FrameDecoderError}; + use crate::decoding::{BlockDecodingStrategy, FrameDecoder}; + // v0.7, a raw block of five bytes cut after two. + let frame = [0x27, 0xB5, 0x2F, 0xFD, 0x00, 0x00, 0x40, 0, 5, b'a', b'b']; + let mut decoder = FrameDecoder::new(); + let mut src = &frame[..]; + decoder.reset(&mut src).unwrap(); + let err = decoder + .decode_blocks(&mut src, BlockDecodingStrategy::All) + .unwrap_err(); + assert!( + matches!( + err, + FrameDecoderError::FailedToReadBlockBody(DecodeBlockContentError::ReadError { + step: BlockType::Raw, + .. + }) + ), + "{err:?}" + ); +} + /// Output a caller does not collect stays in the buffer however far past the /// window it grows, and all of it is handed out once collected. #[cfg(feature = "std")] diff --git a/zstd/src/decoding/mod.rs b/zstd/src/decoding/mod.rs index b887aced..63e5691a 100644 --- a/zstd/src/decoding/mod.rs +++ b/zstd/src/decoding/mod.rs @@ -546,7 +546,9 @@ pub struct FrameHeaderInfo { /// prefix learns how much of the header is still to read. Upstream's /// `ZSTD_frameHeaderSize` measures it the same way /// (`zstd_decompress.c:416-429`), without looking at the descriptor's other -/// bits, so a header this library refuses to decode is still measured. +/// bits, so a header this library refuses to decode is still measured. With +/// the `legacy` feature a v0.5-v0.7 header is measured by its own format's +/// size fields, as [`LegacyVersion::header_size`] does. /// /// # Errors /// [`ReadFrameHeaderError`](errors::ReadFrameHeaderError) when `src` is shorter @@ -558,6 +560,14 @@ pub struct FrameHeaderInfo { /// assert_eq!(frame_header_size(&[0x28, 0xB5, 0x2F, 0xFD, 0x00]).unwrap(), 6); /// ``` pub fn frame_header_size(src: &[u8]) -> Result { + // A v0.5-v0.7 header is measured by its own format's size fields. Upstream + // reads every header with RFC 8878's layout, which only v0.7 shares. + #[cfg(feature = "legacy")] + if let Some(version) = LegacyVersion::of(src) { + return version + .header_size(src) + .ok_or_else(|| errors::ReadFrameHeaderError::FrameDescriptorReadError(frame::eof())); + } frame::measure_header_from_slice(src) } From 9b5814a9ffc0514b5ac6ede777d588f74c466fed Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Tue, 20 Oct 2026 13:54:09 +0300 Subject: [PATCH 11/11] docs(c-api): say which headers ZSTD_frameHeaderSize measures --- c-api/src/frame.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/c-api/src/frame.rs b/c-api/src/frame.rs index 4f45bfdd..d2bd8b74 100644 --- a/c-api/src/frame.rs +++ b/c-api/src/frame.rs @@ -65,7 +65,9 @@ pub struct ZSTD_FrameHeader { } /// `size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize)` — length of -/// the frame header (including the magic number), or an error code. +/// the frame header (including the magic number), or an error code. A +/// v0.5-v0.7 header, which `ZSTD_isFrame` accepts, is measured by its own +/// format's size fields. /// /// # Safety /// `src` must be valid for `src_size` bytes (or `NULL` with `src_size == 0`).