znippy-zip-fixtures/src/lib.rs
//! Synthetic ZIP / JAR / wheel fixtures — the ONE writer.
//!
//! # Why this crate exists
//!
//! Four files in this workspace carried their own hand-rolled `crc32`, and three
//! of them carried a byte-for-byte identical ~55-line `build_zip_stored`:
//!
//! | file | crc32 | stored | deflated |
//! |---|---|---|---|
//! | `tests/tests/maven_bench.rs` | ✔ | ✔ | — |
//! | `tests/tests/plugin_read_api_test.rs` | ✔ | ✔ | — |
//! | `tests/tests/plugin_deflate_entry.rs` | ✔ | — | ✔ |
//! | `xtask/examples/nornir-bench.rs` | ✔ | ✔ | — |
//!
//! LAW 5 (reuse, do not twin) says share it. The split above is also *how the
//! bug happened*: `plugin_deflate_entry.rs` grew the only method-8 builder in
//! the workspace and nobody carried it to the bench, so `MavenExtractBench`
//! timed a **stored** JAR — i.e. it timed ZIP central-directory parsing and
//! never once ran an inflate, while reporting a `speedup_x` for the house
//! DEFLATE core. A number that is real, precise, and about nothing.
//!
//! # Fixed by construction
//!
//! There is exactly one ZIP writer here — [`build_zip_entry`] — and the stored
//! and deflated constructors are thin calls into it that differ only in the
//! method field and the payload. A future "stored where deflate was meant"
//! cannot come back as a second, drifting copy of the container code.
//!
//! Decompression is never done here: this crate only ever *produces* input for
//! the house engine (`linflate` / `ljar` / `lzip-parallel`). `miniz_oxide` is
//! the encoder only.
/// ZIP compression method 0 — stored, no compression.
pub const METHOD_STORED: u16 = 0;
/// ZIP compression method 8 — raw DEFLATE (no zlib wrapper), per APPNOTE.
pub const METHOD_DEFLATE: u16 = 8;
/// The ZIP/PKZIP CRC-32 (reflected, polynomial 0xEDB8_8320).
pub fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for &byte in data {
crc ^= byte as u32;
for _ in 0..8 {
crc = if crc & 1 != 0 { (crc >> 1) ^ 0xEDB8_8320 } else { crc >> 1 };
}
}
!crc
}
/// Build a minimal, valid single-entry ZIP: local file header, payload, central
/// directory header, end-of-central-directory.
///
/// `uncompressed` supplies the CRC and the uncompressed size; `payload` is the
/// bytes actually stored in the entry, and `method` says how a reader should
/// interpret them. Every fixture in the workspace goes through here.
pub fn build_zip_entry(
entry_name: &str,
uncompressed: &[u8],
method: u16,
payload: &[u8],
) -> Vec<u8> {
let name = entry_name.as_bytes();
let crc = crc32(uncompressed);
let usize_ = uncompressed.len() as u32;
let csize = payload.len() as u32;
let mut out = Vec::new();
// ── Local file header ──
let local_offset = out.len() as u32;
out.extend_from_slice(b"PK\x03\x04");
out.extend_from_slice(&20u16.to_le_bytes()); // version needed
out.extend_from_slice(&0u16.to_le_bytes()); // flags
out.extend_from_slice(&method.to_le_bytes()); // compression method
out.extend_from_slice(&0u16.to_le_bytes()); // mod time
out.extend_from_slice(&0u16.to_le_bytes()); // mod date
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&csize.to_le_bytes());
out.extend_from_slice(&usize_.to_le_bytes());
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); // extra field length
out.extend_from_slice(name);
out.extend_from_slice(payload);
// ── Central directory header ──
let cd_offset = out.len() as u32;
out.extend_from_slice(b"PK\x01\x02");
out.extend_from_slice(&20u16.to_le_bytes()); // version made by
out.extend_from_slice(&20u16.to_le_bytes()); // version needed
out.extend_from_slice(&0u16.to_le_bytes()); // flags
out.extend_from_slice(&method.to_le_bytes()); // compression method
out.extend_from_slice(&0u16.to_le_bytes()); // mod time
out.extend_from_slice(&0u16.to_le_bytes()); // mod date
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&csize.to_le_bytes());
out.extend_from_slice(&usize_.to_le_bytes());
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); // extra field length
out.extend_from_slice(&0u16.to_le_bytes()); // comment length
out.extend_from_slice(&0u16.to_le_bytes()); // disk start
out.extend_from_slice(&0u16.to_le_bytes()); // internal attr
out.extend_from_slice(&0u32.to_le_bytes()); // external attr
out.extend_from_slice(&local_offset.to_le_bytes());
out.extend_from_slice(name);
// ── End of central directory ──
let cd_size = out.len() as u32 - cd_offset;
out.extend_from_slice(b"PK\x05\x06");
out.extend_from_slice(&0u16.to_le_bytes()); // disk number
out.extend_from_slice(&0u16.to_le_bytes()); // disk with cd
out.extend_from_slice(&1u16.to_le_bytes()); // entries on disk
out.extend_from_slice(&1u16.to_le_bytes()); // total entries
out.extend_from_slice(&cd_size.to_le_bytes());
out.extend_from_slice(&cd_offset.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); // comment length
out
}
/// A single-entry ZIP whose entry is **stored** (method 0).
pub fn build_zip_stored(entry_name: &str, entry_data: &[u8]) -> Vec<u8> {
build_zip_entry(entry_name, entry_data, METHOD_STORED, entry_data)
}
/// A single-entry ZIP whose entry is really **DEFLATE-compressed** (method 8,
/// raw deflate stream, no zlib wrapper).
pub fn build_zip_deflated(entry_name: &str, entry_data: &[u8]) -> Vec<u8> {
let deflated = miniz_oxide::deflate::compress_to_vec(entry_data, 6);
build_zip_entry(entry_name, entry_data, METHOD_DEFLATE, &deflated)
}
/// Same as [`build_zip_deflated`], but the compressed payload is supplied
/// verbatim — lets a test feed a CORRUPTED deflate stream through an otherwise
/// perfectly valid ZIP container.
pub fn build_zip_method8_with_payload(
entry_name: &str,
uncompressed: &[u8],
payload: &[u8],
) -> Vec<u8> {
build_zip_entry(entry_name, uncompressed, METHOD_DEFLATE, payload)
}
/// Read the compression method / compressed size / uncompressed size straight
/// back out of a fixture's local file header.
///
/// Exists so a caller can ASSERT what it built rather than trust the function
/// name — the whole maven-bench defect was a fixture that said "jar" and meant
/// "stored". Returns `(method, compressed_size, uncompressed_size)`.
pub fn read_local_header(zip: &[u8]) -> (u16, u32, u32) {
assert!(zip.len() >= 30 && &zip[0..4] == b"PK\x03\x04", "not a local file header");
let m = u16::from_le_bytes([zip[8], zip[9]]);
let c = u32::from_le_bytes([zip[18], zip[19], zip[20], zip[21]]);
let u = u32::from_le_bytes([zip[22], zip[23], zip[24], zip[25]]);
(m, c, u)
}
/// The small canonical `pom.xml` every maven fixture used to inline by hand.
pub fn pom_xml(group: &str, artifact: &str, version: &str) -> String {
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project>\n \
<groupId>{group}</groupId>\n <artifactId>{artifact}</artifactId>\n \
<version>{version}</version>\n <packaging>jar</packaging>\n</project>"
)
}
/// A pom big and repetitive enough that the encoder emits a real dynamic-Huffman
/// DEFLATE block with long back-references — i.e. it exercises `linflate`'s
/// fastloop and SIMD match-copy, not just a stored block. ~200 dependencies,
/// ~20 kB uncompressed.
pub fn big_pom(group: &str, artifact: &str, version: &str) -> String {
let mut deps = String::new();
for i in 0..200 {
deps.push_str(&format!(
" <dependency>\n <groupId>org.example.dep{i}</groupId>\n \
<artifactId>artifact-{i}</artifactId>\n <version>1.0.{i}</version>\n \
</dependency>\n"
));
}
format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project>\n \
<groupId>{group}</groupId>\n <artifactId>{artifact}</artifactId>\n \
<version>{version}</version>\n <packaging>jar</packaging>\n \
<dependencies>\n{deps} </dependencies>\n</project>"
)
}
/// The canonical maven layout path for a pom inside a JAR.
pub fn pom_path(group: &str, artifact: &str) -> String {
format!("META-INF/maven/{group}/{artifact}/pom.xml")
}
#[cfg(test)]
mod tests {
use super::*;
/// The standard CRC-32 check vector. A silently wrong CRC would make every
/// fixture in the workspace decode-reject in a way that looks like a codec bug.
#[test]
fn crc32_matches_the_standard_check_vector() {
assert_eq!(crc32(b"123456789"), 0xCBF4_3926);
}
/// A "stored" fixture must really be method 0 with csize == usize.
#[test]
fn stored_fixture_is_really_stored() {
let data = b"hello world";
let zip = build_zip_stored("a.txt", data);
let (method, csize, usize_) = read_local_header(&zip);
assert_eq!(method, METHOD_STORED);
assert_eq!(csize, data.len() as u32);
assert_eq!(usize_, data.len() as u32);
// The payload sits verbatim right after the name.
assert!(zip.windows(data.len()).any(|w| w == data));
}
/// THE regression this crate was extracted for: a "deflated" fixture must
/// really be method 8 AND really be smaller than the input. A builder that
/// quietly emitted a stored entry would still parse, still return the right
/// metadata, and still produce a plausible benchmark number — while never
/// running an inflate. `csize < usize` is the applied-output check that
/// cannot be satisfied by a stored entry.
#[test]
fn deflated_fixture_really_deflates() {
let pom = big_pom("com.example", "mylib", "1.2.3");
let zip = build_zip_deflated(&pom_path("com.example", "mylib"), pom.as_bytes());
let (method, csize, usize_) = read_local_header(&zip);
assert_eq!(method, METHOD_DEFLATE, "fixture must be method 8");
assert_eq!(usize_, pom.len() as u32);
assert!(
csize < usize_ / 2,
"a repetitive 200-dependency pom must compress to well under half \
({csize} vs {usize_}) — if it did not, the 'deflated' fixture is \
stored in disguise and no inflate is being exercised"
);
}
/// The corrupted-payload builder keeps a valid container but a broken stream.
#[test]
fn method8_with_payload_keeps_the_declared_uncompressed_size() {
let data = b"the real bytes";
let zip = build_zip_method8_with_payload("a.txt", data, b"\xff\xff\xff\xff");
let (method, csize, usize_) = read_local_header(&zip);
assert_eq!(method, METHOD_DEFLATE);
assert_eq!(csize, 4);
assert_eq!(usize_, data.len() as u32);
}
/// Stored and deflated differ ONLY in method + payload — the container code
/// is one writer, so the headers must line up byte-for-byte up to the method
/// field and again across the trailing EOCD shape.
#[test]
fn one_writer_produces_both_shapes() {
let data = b"payload";
let stored = build_zip_stored("n", data);
let deflated = build_zip_entry("n", data, METHOD_DEFLATE, data);
assert_eq!(stored[..8], deflated[..8], "headers share one writer");
assert_eq!(read_local_header(&stored).0, METHOD_STORED);
assert_eq!(read_local_header(&deflated).0, METHOD_DEFLATE);
// Same payload length ⇒ same total container size.
assert_eq!(stored.len(), deflated.len());
}
}