use std::collections::HashMap;
use std::fs::File;
use std::os::unix::fs::FileExt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, OnceLock};
use anyhow::{anyhow, Result};
use arrow::record_batch::RecordBatch;
use arrow::ipc::reader::StreamReader;
use arrow_array::{BooleanArray, FixedSizeBinaryArray, StringArray, UInt32Array, UInt64Array};
use crate::codec;
use crate::index::{read_reserved_section_bytes, read_znippy_index, ZNIPPY_DELTA_MODULE};
use crate::views::{
build_conda_view, build_deb_view, build_gem_view, build_maven_view, build_npm_view,
build_python_view, build_rpm_view, build_rust_view, CondaView, DebView, GemView, MavenView,
NpmView, PythonView, RpmView, RustView,
};
pub trait ZnippyReader: Send + Sync {
fn list_files(&self) -> Result<Vec<String>>;
fn extract_file(&self, relative_path: &str) -> Result<Vec<u8>>;
fn contains(&self, relative_path: &str) -> bool;
fn file_size(&self, relative_path: &str) -> Option<u64>;
fn extract_files(&self, paths: &[&str]) -> Vec<Result<Vec<u8>>> {
paths.iter().map(|p| self.extract_file(p)).collect()
}
}
pub struct ReconstructCtx<'a> {
archive: &'a Arc<File>,
archive_len: u64,
resolve: Option<&'a dyn BaseResolve>,
depth: usize,
}
impl<'a> ReconstructCtx<'a> {
pub fn archive(&self) -> &Arc<File> {
self.archive
}
pub fn archive_len(&self) -> u64 {
self.archive_len
}
pub fn resolve_base(&self, path: &str, verify: bool) -> Result<Vec<u8>> {
if self.depth >= MAX_RECONSTRUCT_DEPTH {
return Err(anyhow!(
"base chain for {} is deeper than {} — refusing to recurse further \
(a cyclic or over-long index must be an error, not a crash)",
path,
MAX_RECONSTRUCT_DEPTH
));
}
self.resolve
.ok_or_else(|| anyhow!("entry needs a base but this archive supplied no resolver"))?
.resolve(path, verify, self.depth + 1)
}
}
pub const MAX_RECONSTRUCT_DEPTH: usize = 64;
pub trait BaseResolve: Send + Sync {
fn resolve(&self, path: &str, verify: bool, depth: usize) -> Result<Vec<u8>>;
}
enum ChunkSource {
Stored,
Delta { base_path: String },
}
struct ChunkInfo {
blob_offset: u64,
blob_size: u64,
fdata_offset: u64,
compressed: bool,
checksum: [u8; 32],
chunk_seq: u32,
source: ChunkSource,
}
pub struct Entry {
uncompressed_size: u64,
chunks: Vec<ChunkInfo>,
}
impl Entry {
pub fn uncompressed_size(&self) -> u64 {
self.uncompressed_size
}
pub fn reconstruct(
&self,
path: &str,
ctx: &ReconstructCtx<'_>,
verify: bool,
) -> Result<Vec<u8>> {
let mut result: Vec<u8> = Vec::new();
let mut blob = Vec::new(); let mut decomp = Vec::new(); let mut bases: HashMap<String, Vec<u8>> = HashMap::new();
for chunk in &self.chunks {
if chunk.blob_size > 0 {
let in_bounds = chunk
.blob_offset
.checked_add(chunk.blob_size)
.is_some_and(|end| end <= ctx.archive_len());
if !in_bounds {
return Err(anyhow!(
"blob for {} out of bounds (offset={}, size={}, archive_len={})",
path,
chunk.blob_offset,
chunk.blob_size,
ctx.archive_len()
));
}
}
blob.resize(chunk.blob_size as usize, 0);
ctx.archive().read_exact_at(&mut blob, chunk.blob_offset)?;
let raw: &[u8] = if chunk.compressed {
codec::decompress_into(&blob, &mut decomp)?;
&decomp
} else {
&blob
};
let applied: Vec<u8>;
let (bytes, must_check): (&[u8], bool) = match &chunk.source {
ChunkSource::Stored => (raw, false),
ChunkSource::Delta { base_path } => {
if !bases.contains_key(base_path) {
let b = ctx.resolve_base(base_path, verify)?;
bases.insert(base_path.clone(), b);
}
let base = &bases[base_path];
applied = apply_delta(base, raw)?;
(&applied, true)
}
};
if (verify || must_check) && chunk.checksum != [0u8; 32] {
let computed = blake3::hash(bytes);
if computed.as_bytes()[..] != chunk.checksum[..] {
return match &chunk.source {
ChunkSource::Stored => Err(anyhow!(
"checksum mismatch for {} at fdata_offset {}",
path,
chunk.fdata_offset
)),
ChunkSource::Delta { base_path } => Err(anyhow!(
"delta chunk of {} at fdata_offset {} produced bytes that do not \
match its result checksum (base {})",
path,
chunk.fdata_offset,
base_path
)),
};
}
}
let start = chunk.fdata_offset as usize;
if start > result.len() {
return Err(anyhow!(
"chunk of {} leaves a gap at fdata_offset {} (file reaches {})",
path,
start,
result.len()
));
}
let end = start + bytes.len();
if end > result.len() {
result.resize(end, 0);
}
result[start..end].copy_from_slice(bytes);
}
Ok(result)
}
}
fn delta_varint(buf: &[u8], at: &mut usize) -> Result<u64> {
let mut value: u64 = 0;
let mut shift = 0u32;
loop {
let byte = *buf
.get(*at)
.ok_or_else(|| anyhow!("delta header truncated at byte {}", at))?;
*at += 1;
if shift >= 64 {
return Err(anyhow!("delta size varint overflows u64"));
}
value |= u64::from(byte & 0x7f) << shift;
shift += 7;
if byte & 0x80 == 0 {
return Ok(value);
}
}
}
pub fn apply_delta(base: &[u8], delta: &[u8]) -> Result<Vec<u8>> {
let mut at = 0usize;
let declared_base = delta_varint(delta, &mut at)?;
if declared_base != base.len() as u64 {
return Err(anyhow!(
"delta expects a base of {} bytes, resolved base is {}",
declared_base,
base.len()
));
}
let result_size = delta_varint(delta, &mut at)?;
let mut out: Vec<u8> = Vec::new();
while at < delta.len() {
let op = delta[at];
at += 1;
if op & 0x80 != 0 {
let mut copy_off: u64 = 0;
let mut copy_len: u64 = 0;
for i in 0..4 {
if op & (1 << i) != 0 {
let b = *delta
.get(at)
.ok_or_else(|| anyhow!("delta copy offset truncated"))?;
at += 1;
copy_off |= u64::from(b) << (8 * i);
}
}
for i in 0..3 {
if op & (0x10 << i) != 0 {
let b = *delta
.get(at)
.ok_or_else(|| anyhow!("delta copy size truncated"))?;
at += 1;
copy_len |= u64::from(b) << (8 * i);
}
}
if copy_len == 0 {
copy_len = 0x1_0000;
}
let end = copy_off
.checked_add(copy_len)
.ok_or_else(|| anyhow!("delta copy range overflows"))?;
if end > base.len() as u64 {
return Err(anyhow!(
"delta copies [{}, {}) from a base of {} bytes",
copy_off,
end,
base.len()
));
}
out.extend_from_slice(&base[copy_off as usize..end as usize]);
} else if op != 0 {
let n = op as usize;
let end = at
.checked_add(n)
.ok_or_else(|| anyhow!("delta insert range overflows"))?;
if end > delta.len() {
return Err(anyhow!("delta insert of {} bytes runs past the stream", n));
}
out.extend_from_slice(&delta[at..end]);
at = end;
} else {
return Err(anyhow!("delta contains a 0x00 instruction"));
}
if out.len() as u64 > result_size {
return Err(anyhow!(
"delta produced {} bytes, more than the declared {}",
out.len(),
result_size
));
}
}
if out.len() as u64 != result_size {
return Err(anyhow!(
"delta produced {} bytes, declared {}",
out.len(),
result_size
));
}
Ok(out)
}
pub const MAX_DELTA_CHAIN: usize = 8;
pub const DELTA_SIZE_ALPHA: f64 = 0.7;
const MIN_COPY: usize = 16;
pub fn encode_delta_against(base: &[u8], target: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
put_size_varint(&mut out, base.len() as u64);
put_size_varint(&mut out, target.len() as u64);
let mut index: HashMap<u64, Vec<usize>> = HashMap::new();
if base.len() >= MIN_COPY {
let mut i = 0usize;
while i + MIN_COPY <= base.len() {
index.entry(hash16(&base[i..i + MIN_COPY])).or_default().push(i);
i += MIN_COPY;
}
}
let mut literal_start = 0usize;
let mut at = 0usize;
while at < target.len() {
let mut best = (0usize, 0usize); if at + MIN_COPY <= target.len() {
if let Some(cands) = index.get(&hash16(&target[at..at + MIN_COPY])) {
for &bo in cands.iter().take(8) {
if base.len() - bo < MIN_COPY || &base[bo..bo + MIN_COPY] != &target[at..at + MIN_COPY] {
continue;
}
let mut n = MIN_COPY;
while bo + n < base.len() && at + n < target.len() && base[bo + n] == target[at + n] {
n += 1;
}
if n > best.1 {
best = (bo, n);
}
}
}
}
if best.1 >= MIN_COPY {
flush_literal(&mut out, &target[literal_start..at]);
emit_copy(&mut out, best.0 as u64, best.1 as u64);
at += best.1;
literal_start = at;
} else {
at += 1;
}
}
flush_literal(&mut out, &target[literal_start..]);
out
}
fn hash16(b: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &x in b {
h ^= x as u64;
h = h.wrapping_mul(0x1000_0000_01b3);
}
h
}
fn put_size_varint(out: &mut Vec<u8>, mut v: u64) {
loop {
let mut b = (v & 0x7f) as u8;
v >>= 7;
if v != 0 {
b |= 0x80;
}
out.push(b);
if v == 0 {
return;
}
}
}
fn flush_literal(out: &mut Vec<u8>, lit: &[u8]) {
for piece in lit.chunks(0x7f) {
out.push(piece.len() as u8);
out.extend_from_slice(piece);
}
}
fn emit_copy(out: &mut Vec<u8>, mut off: u64, mut len: u64) {
while len > 0 {
let take = len.min(0xff_ffff);
let mut op: u8 = 0x80;
let mut tail = Vec::new();
for i in 0..4 {
let b = ((off >> (8 * i)) & 0xff) as u8;
if b != 0 {
op |= 1 << i;
tail.push(b);
}
}
let mut size_bytes = Vec::new();
for i in 0..3 {
let b = ((take >> (8 * i)) & 0xff) as u8;
if b != 0 {
op |= 0x10 << i;
size_bytes.push(b);
}
}
out.push(op);
out.extend_from_slice(&tail);
out.extend_from_slice(&size_bytes);
off += take;
len -= take;
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum VersionPlan {
Delta { base_path: String, chain_len: usize, delta_bytes: usize },
Chunked(ChunkedReason),
}
#[derive(Debug, PartialEq, Eq)]
pub enum ChunkedReason {
FirstVersion,
ChainTooLong,
DeltaNotSmaller,
}
pub fn plan_version(
path: &str,
previous: Option<(&str, &[u8], usize)>,
target: &[u8],
mut compressed_len: impl FnMut(&[u8]) -> usize,
) -> (VersionPlan, Option<Vec<u8>>) {
let (base_path, base_bytes, base_chain) = match previous {
None => return (VersionPlan::Chunked(ChunkedReason::FirstVersion), None),
Some(p) => p,
};
let _ = path;
if base_chain + 1 > MAX_DELTA_CHAIN {
return (VersionPlan::Chunked(ChunkedReason::ChainTooLong), None);
}
let delta = encode_delta_against(base_bytes, target);
if (delta.len() as f64) >= DELTA_SIZE_ALPHA * (base_bytes.len() as f64) {
return (VersionPlan::Chunked(ChunkedReason::DeltaNotSmaller), None);
}
if compressed_len(&delta) >= compressed_len(target) {
return (VersionPlan::Chunked(ChunkedReason::DeltaNotSmaller), None);
}
let n = delta.len();
(
VersionPlan::Delta { base_path: base_path.to_string(), chain_len: base_chain + 1, delta_bytes: n },
Some(delta),
)
}
pub struct StoredUnit {
pub fdata_offset: u64,
pub payload: UnitPayload,
}
pub enum UnitPayload {
Bytes(Vec<u8>),
Delta {
base_path: String,
delta: Vec<u8>,
expected: Vec<u8>,
},
}
pub trait Splitter: Send + Sync {
fn split(
&self,
path: &str,
bytes: &[u8],
previous: Option<(&str, &[u8], usize)>,
compressed_len: &mut dyn FnMut(&[u8]) -> usize,
) -> Vec<StoredUnit>;
}
pub struct ChunkSplitter {
pub chunk_size: usize,
}
impl Splitter for ChunkSplitter {
fn split(
&self,
_path: &str,
bytes: &[u8],
_previous: Option<(&str, &[u8], usize)>,
_compressed_len: &mut dyn FnMut(&[u8]) -> usize,
) -> Vec<StoredUnit> {
if bytes.is_empty() {
return vec![StoredUnit { fdata_offset: 0, payload: UnitPayload::Bytes(Vec::new()) }];
}
bytes
.chunks(self.chunk_size.max(1))
.enumerate()
.map(|(i, piece)| StoredUnit {
fdata_offset: (i * self.chunk_size.max(1)) as u64,
payload: UnitPayload::Bytes(piece.to_vec()),
})
.collect()
}
}
pub struct DeltaSplitter {
pub fallback: ChunkSplitter,
}
impl Splitter for DeltaSplitter {
fn split(
&self,
path: &str,
bytes: &[u8],
previous: Option<(&str, &[u8], usize)>,
compressed_len: &mut dyn FnMut(&[u8]) -> usize,
) -> Vec<StoredUnit> {
let (plan, delta) = plan_version(path, previous, bytes, |b| compressed_len(b));
match (plan, delta) {
(VersionPlan::Delta { base_path, .. }, Some(delta)) => vec![StoredUnit {
fdata_offset: 0,
payload: UnitPayload::Delta { base_path, delta, expected: bytes.to_vec() },
}],
_ => self.fallback.split(path, bytes, previous, compressed_len),
}
}
}
pub struct RegionDeltaSplitter {
pub chunk_size: usize,
}
impl Splitter for RegionDeltaSplitter {
fn split(
&self,
path: &str,
bytes: &[u8],
previous: Option<(&str, &[u8], usize)>,
compressed_len: &mut dyn FnMut(&[u8]) -> usize,
) -> Vec<StoredUnit> {
let size = self.chunk_size.max(1);
let fallback = ChunkSplitter { chunk_size: size };
let (base_path, base_bytes, base_chain) = match previous {
None => return fallback.split(path, bytes, previous, compressed_len),
Some(p) => p,
};
if base_chain + 1 > MAX_DELTA_CHAIN {
return fallback.split(path, bytes, previous, compressed_len);
}
let mut units = Vec::new();
let mut any_delta = false;
let mut off = 0usize;
while off < bytes.len() {
let end = (off + size).min(bytes.len());
let tile = &bytes[off..end];
let base_tile = base_bytes.get(off..end);
if base_tile == Some(tile) && !tile.is_empty() {
let mut delta = Vec::new();
put_size_varint(&mut delta, base_bytes.len() as u64);
put_size_varint(&mut delta, tile.len() as u64);
emit_copy(&mut delta, off as u64, tile.len() as u64);
any_delta = true;
units.push(StoredUnit {
fdata_offset: off as u64,
payload: UnitPayload::Delta {
base_path: base_path.to_string(),
delta,
expected: tile.to_vec(),
},
});
} else {
units.push(StoredUnit {
fdata_offset: off as u64,
payload: UnitPayload::Bytes(tile.to_vec()),
});
}
off = end;
}
if !any_delta {
return fallback.split(path, bytes, previous, compressed_len);
}
units
}
}
pub struct ZnippyArchive {
archive: Arc<File>,
archive_len: u64,
file_index: HashMap<String, Entry>,
path: PathBuf,
rust_view: OnceLock<Option<RustView>>,
maven_view: OnceLock<Option<MavenView>>,
python_view: OnceLock<Option<PythonView>>,
npm_view: OnceLock<Option<NpmView>>,
gem_view: OnceLock<Option<GemView>>,
conda_view: OnceLock<Option<CondaView>>,
rpm_view: OnceLock<Option<RpmView>>,
deb_view: OnceLock<Option<DebView>>,
}
impl ZnippyArchive {
pub fn open(path: &Path) -> Result<Self> {
let (_, batches) = read_znippy_index(path)?;
let mut file_index = Self::build_file_index(&batches)?;
Self::apply_delta_map(path, &mut file_index)?;
let file = File::open(path)?;
let archive_len = file.metadata()?.len();
let archive = Arc::new(file);
Ok(Self {
archive,
archive_len,
file_index,
path: path.to_path_buf(),
rust_view: OnceLock::new(),
maven_view: OnceLock::new(),
python_view: OnceLock::new(),
npm_view: OnceLock::new(),
gem_view: OnceLock::new(),
conda_view: OnceLock::new(),
rpm_view: OnceLock::new(),
deb_view: OnceLock::new(),
})
}
pub fn file_count(&self) -> usize {
self.file_index.len()
}
pub fn as_rust(&self) -> Option<&RustView> {
self.rust_view
.get_or_init(|| build_rust_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_maven(&self) -> Option<&MavenView> {
self.maven_view
.get_or_init(|| build_maven_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_python(&self) -> Option<&PythonView> {
self.python_view
.get_or_init(|| build_python_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_npm(&self) -> Option<&NpmView> {
self.npm_view
.get_or_init(|| build_npm_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_gem(&self) -> Option<&GemView> {
self.gem_view
.get_or_init(|| build_gem_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_conda(&self) -> Option<&CondaView> {
self.conda_view
.get_or_init(|| build_conda_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_rpm(&self) -> Option<&RpmView> {
self.rpm_view
.get_or_init(|| build_rpm_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
pub fn as_deb(&self) -> Option<&DebView> {
self.deb_view
.get_or_init(|| build_deb_view(&self.path, Arc::clone(&self.archive)).unwrap_or(None))
.as_ref()
}
fn build_file_index(batches: &[RecordBatch]) -> Result<HashMap<String, Entry>> {
let mut index: HashMap<String, Entry> = HashMap::new();
for batch in batches {
let paths = batch
.column_by_name("relative_path")
.ok_or_else(|| anyhow!("missing relative_path column"))?
.as_any()
.downcast_ref::<StringArray>()
.ok_or_else(|| anyhow!("relative_path not StringArray"))?;
let compressed_col = batch
.column_by_name("compressed")
.ok_or_else(|| anyhow!("missing compressed column"))?
.as_any()
.downcast_ref::<BooleanArray>()
.ok_or_else(|| anyhow!("compressed not BooleanArray"))?;
let sizes = batch
.column_by_name("uncompressed_size")
.ok_or_else(|| anyhow!("missing uncompressed_size column"))?
.as_any()
.downcast_ref::<UInt64Array>()
.ok_or_else(|| anyhow!("uncompressed_size not UInt64Array"))?;
let blob_offset_col = batch
.column_by_name("blob_offset")
.ok_or_else(|| anyhow!("missing blob_offset column"))?
.as_any()
.downcast_ref::<UInt64Array>()
.ok_or_else(|| anyhow!("blob_offset not UInt64Array"))?;
let blob_size_col = batch
.column_by_name("blob_size")
.ok_or_else(|| anyhow!("missing blob_size column"))?
.as_any()
.downcast_ref::<UInt64Array>()
.ok_or_else(|| anyhow!("blob_size not UInt64Array"))?;
let chunk_seq_col = batch
.column_by_name("chunk_seq")
.and_then(|c| c.as_any().downcast_ref::<UInt32Array>());
let fdata_offset_col = batch
.column_by_name("fdata_offset")
.ok_or_else(|| anyhow!("missing fdata_offset column"))?
.as_any()
.downcast_ref::<UInt64Array>()
.ok_or_else(|| anyhow!("fdata_offset not UInt64Array"))?;
let checksum_col = batch
.column_by_name("checksum")
.and_then(|c| c.as_any().downcast_ref::<FixedSizeBinaryArray>())
.filter(|c| c.value_length() == 32);
for row in 0..batch.num_rows() {
let path = paths.value(row).to_string();
let compressed = compressed_col.value(row);
let uncompressed_size = sizes.value(row);
let blob_offset = blob_offset_col.value(row);
let blob_size = blob_size_col.value(row);
let fdata_offset = fdata_offset_col.value(row);
let mut checksum = [0u8; 32];
if let Some(col) = checksum_col {
checksum.copy_from_slice(col.value(row));
}
let entry = index.entry(path).or_insert_with(|| Entry {
uncompressed_size: 0,
chunks: Vec::new(),
});
entry.uncompressed_size = entry
.uncompressed_size
.max(fdata_offset.saturating_add(uncompressed_size));
entry.chunks.push(ChunkInfo {
blob_offset,
blob_size,
fdata_offset,
compressed,
checksum,
chunk_seq: chunk_seq_col.map(|c| c.value(row)).unwrap_or(0),
source: ChunkSource::Stored,
});
}
}
for entry in index.values_mut() {
entry.chunks.sort_by_key(|c| c.fdata_offset);
}
Ok(index)
}
fn apply_delta_map(path: &Path, index: &mut HashMap<String, Entry>) -> Result<()> {
let Some(bytes) = read_reserved_section_bytes(path, ZNIPPY_DELTA_MODULE)? else {
return Ok(());
};
let mut reader = StreamReader::try_new(std::io::Cursor::new(bytes), None)
.map_err(|e| anyhow!("delta map: {e}"))?;
while let Some(batch) = reader.next() {
let batch = batch.map_err(|e| anyhow!("delta map batch: {e}"))?;
let paths = batch
.column_by_name("relative_path")
.and_then(|c| c.as_any().downcast_ref::<StringArray>())
.ok_or_else(|| anyhow!("delta map: missing relative_path"))?;
let seqs = batch
.column_by_name("chunk_seq")
.and_then(|c| c.as_any().downcast_ref::<UInt32Array>())
.ok_or_else(|| anyhow!("delta map: missing chunk_seq"))?;
let bases = batch
.column_by_name("base_path")
.and_then(|c| c.as_any().downcast_ref::<StringArray>())
.ok_or_else(|| anyhow!("delta map: missing base_path"))?;
for row in 0..batch.num_rows() {
let p = paths.value(row);
let seq = seqs.value(row);
let base = bases.value(row).to_string();
let entry = index
.get_mut(p)
.ok_or_else(|| anyhow!("delta map names entry {p}, which the index has not"))?;
let chunk = entry
.chunks
.iter_mut()
.find(|c| c.chunk_seq == seq)
.ok_or_else(|| anyhow!("delta map names chunk {seq} of {p}, which the index has not"))?;
chunk.source = ChunkSource::Delta { base_path: base };
}
}
Ok(())
}
fn extract_inner(&self, relative_path: &str, verify: bool) -> Result<Vec<u8>> {
self.extract_at_depth(relative_path, verify, 0)
}
fn extract_at_depth(&self, relative_path: &str, verify: bool, depth: usize) -> Result<Vec<u8>> {
let entry = self
.file_index
.get(relative_path)
.ok_or_else(|| anyhow!("file not found in archive: {}", relative_path))?;
entry.reconstruct(relative_path, &self.reconstruct_ctx(depth), verify)
}
fn reconstruct_ctx(&self, depth: usize) -> ReconstructCtx<'_> {
ReconstructCtx {
archive: &self.archive,
archive_len: self.archive_len,
resolve: Some(self),
depth,
}
}
pub fn extract_file_verified(&self, relative_path: &str) -> Result<Vec<u8>> {
self.extract_inner(relative_path, true)
}
}
impl BaseResolve for ZnippyArchive {
fn resolve(&self, path: &str, verify: bool, depth: usize) -> Result<Vec<u8>> {
self.extract_at_depth(path, verify, depth)
}
}
impl ZnippyReader for ZnippyArchive {
fn list_files(&self) -> Result<Vec<String>> {
Ok(self.file_index.keys().cloned().collect())
}
fn extract_file(&self, relative_path: &str) -> Result<Vec<u8>> {
self.extract_inner(relative_path, false)
}
fn contains(&self, relative_path: &str) -> bool {
self.file_index.contains_key(relative_path)
}
fn file_size(&self, relative_path: &str) -> Option<u64> {
self.file_index
.get(relative_path)
.map(|e| e.uncompressed_size())
}
}
#[cfg(all(test, feature = "openzl"))]
mod tests {
use super::*;
use crate::codec::CompressCtx;
use crate::index::{build_metadata_batch, lookup_schema};
use crate::meta::{BlobMeta, ChunkMeta};
use crate::meta_sink::{ArchiveMetaSink, ArrowIpcSink, GroupKey};
use std::os::unix::fs::FileExt;
use std::time::{SystemTime, UNIX_EPOCH};
fn tmp(tag: &str) -> PathBuf {
let ns = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
let d = std::env::temp_dir().join(format!("znippy_archive_{tag}_{ns}"));
std::fs::create_dir_all(&d).unwrap();
d
}
fn write_archive(
path: &Path,
files: &[(String, Vec<u8>)],
size_override: Option<u64>,
) -> u64 {
let file = Arc::new(File::create(path).unwrap());
let mut ctx = CompressCtx::new(3).unwrap();
let mut blobs = Vec::new();
let mut paths = Vec::new();
let mut cursor = 0u64;
for (fi, (rel, bytes)) in files.iter().enumerate() {
let checksum = *blake3::hash(bytes).as_bytes();
let frame = ctx.compress(bytes).unwrap();
let (on_disk, compressed): (&[u8], bool) =
if frame.len() < bytes.len() { (&frame, true) } else { (bytes, false) };
file.write_all_at(on_disk, cursor).unwrap();
let blob_offset = cursor;
cursor += on_disk.len() as u64;
paths.push(rel.clone());
blobs.push(BlobMeta {
blob_offset,
blob_size: size_override.unwrap_or(on_disk.len() as u64),
chunk_meta: ChunkMeta {
fdata_offset: 0,
file_index: fi as u64,
chunk_seq: 0,
checksum,
compressed,
uncompressed_size: bytes.len() as u64,
compressed_size: on_disk.len() as u64,
},
});
}
let resolver = { let p = paths.clone(); move |fi: u64| p[fi as usize].clone() };
let batch = build_metadata_batch(&blobs, resolver, &[], &[]).unwrap();
let schema = lookup_schema();
let mut sink = ArrowIpcSink::new(file.clone(), cursor);
sink.push_subindex(
schema.as_ref(),
&[batch],
GroupKey { pkg_type: 0, repo: String::new(), module_name: String::new() },
)
.unwrap();
Box::new(sink).finish().unwrap()
}
fn write_archive_multichunk(
path: &Path,
files: &[(String, Vec<u8>)],
chunk_size: usize,
) -> u64 {
let file = Arc::new(File::create(path).unwrap());
let mut ctx = CompressCtx::new(3).unwrap();
let mut blobs = Vec::new();
let mut paths = Vec::new();
let mut cursor = 0u64;
for (fi, (rel, bytes)) in files.iter().enumerate() {
paths.push(rel.clone());
let mut rows = Vec::new();
let mut off = 0usize;
let mut seq = 0u64;
while off < bytes.len() || (bytes.is_empty() && seq == 0) {
let end = bytes.len().min(off + chunk_size.max(1));
let piece = &bytes[off..end];
let checksum = *blake3::hash(piece).as_bytes();
let frame = ctx.compress(piece).unwrap();
let (on_disk, compressed): (&[u8], bool) =
if frame.len() < piece.len() { (&frame, true) } else { (piece, false) };
file.write_all_at(on_disk, cursor).unwrap();
rows.push(BlobMeta {
blob_offset: cursor,
blob_size: on_disk.len() as u64,
chunk_meta: ChunkMeta {
fdata_offset: off as u64,
file_index: fi as u64,
chunk_seq: seq as u32,
checksum,
compressed,
uncompressed_size: piece.len() as u64,
compressed_size: on_disk.len() as u64,
},
});
cursor += on_disk.len() as u64;
off = end;
seq += 1;
if bytes.is_empty() {
break;
}
}
rows.reverse();
blobs.extend(rows);
}
let resolver = { let p = paths.clone(); move |fi: u64| p[fi as usize].clone() };
let batch = build_metadata_batch(&blobs, resolver, &[], &[]).unwrap();
let schema = lookup_schema();
let mut sink = ArrowIpcSink::new(file.clone(), cursor);
sink.push_subindex(
schema.as_ref(),
&[batch],
GroupKey { pkg_type: 0, repo: String::new(), module_name: String::new() },
)
.unwrap();
Box::new(sink).finish().unwrap()
}
const GOLDEN_CHUNKED_DIGEST: &str =
"bc191568a85e66bd67773a75ba4fbf1438762cc562bdb190befbced15fa3e8fd";
fn corpus() -> Vec<(String, Vec<u8>)> {
let mut v: Vec<(String, Vec<u8>)> = Vec::new();
v.push(("z/last.txt".into(), b"zzz".to_vec()));
v.push(("a/empty.bin".into(), Vec::new()));
v.push(("m/one.bin".into(), vec![0x5a]));
v.push(("c/runs.txt".into(), vec![b'q'; 9000]));
let mut s = 0x1234_5678_9abc_def0u64;
let noise: Vec<u8> = (0..7777u32)
.map(|_| {
s = s.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
(z ^ (z >> 31)) as u8
})
.collect();
v.push(("n/noise.bin".into(), noise));
v.push(("b/mixed.dat".into(), {
let mut b = vec![7u8; 300];
b.extend_from_slice(&[1, 2, 3, 4, 5]);
b.extend(std::iter::repeat(0xffu8).take(1200));
b
}));
v
}
fn corpus_digest(ar: &ZnippyArchive) -> String {
let mut names = ar.list_files().unwrap();
names.sort();
let mut h = blake3::Hasher::new();
for n in &names {
h.update(n.as_bytes());
h.update(&ar.file_size(n).unwrap_or(0).to_le_bytes());
h.update(&ar.extract_file(n).unwrap());
h.update(&ar.extract_file_verified(n).unwrap());
}
h.finalize().to_hex().to_string()
}
#[test]
fn chunked_reconstruction_is_byte_identical_to_the_pre_trait_implementation() {
let dir = tmp("differential");
let path = dir.join("a.znippy");
let files = corpus();
write_archive_multichunk(&path, &files, 1000);
let ar = ZnippyArchive::open(&path).unwrap();
for (rel, bytes) in &files {
assert_eq!(&ar.extract_file(rel).unwrap(), bytes, "round-trip of {rel}");
}
assert_eq!(
corpus_digest(&ar),
GOLDEN_CHUNKED_DIGEST,
"chunked reconstruction changed. Either the refactor is not \
byte-identical, or the archive format changed and this constant \
must be re-derived from the previous commit ON PURPOSE."
);
let _ = std::fs::remove_dir_all(&dir);
}
fn put_varint(out: &mut Vec<u8>, mut v: u64) {
loop {
let mut b = (v & 0x7f) as u8;
v >>= 7;
if v != 0 {
b |= 0x80;
}
out.push(b);
if v == 0 {
return;
}
}
}
fn encode_delta(base_len: usize, copy_len: usize, tail: &[u8]) -> Vec<u8> {
let mut d = Vec::new();
put_varint(&mut d, base_len as u64);
put_varint(&mut d, (copy_len + tail.len()) as u64);
if copy_len > 0 {
d.push(0x80 | 0x01 | 0x10 | 0x20 | 0x40);
d.push(0); d.push((copy_len & 0xff) as u8);
d.push(((copy_len >> 8) & 0xff) as u8);
d.push(((copy_len >> 16) & 0xff) as u8);
}
for piece in tail.chunks(0x7f) {
d.push(piece.len() as u8);
d.extend_from_slice(piece);
}
d
}
fn write_base_and_deltas(
path: &Path,
entries: &[(String, Vec<u8>)],
deltas: &[Vec<u8>],
) -> Vec<(u64, u64, [u8; 32])> {
let file = Arc::new(File::create(path).unwrap());
let mut cursor = 0u64;
let mut rows = Vec::new();
for d in deltas {
file.write_all_at(d, cursor).unwrap();
rows.push((cursor, d.len() as u64, *blake3::hash(d).as_bytes()));
cursor += d.len() as u64;
}
let mut blobs = Vec::new();
let mut names = Vec::new();
for (fi, (rel, bytes)) in entries.iter().enumerate() {
let off = cursor;
file.write_all_at(bytes, off).unwrap();
cursor += bytes.len() as u64;
names.push(rel.clone());
blobs.push(BlobMeta {
blob_offset: off,
blob_size: bytes.len() as u64,
chunk_meta: ChunkMeta {
fdata_offset: 0,
file_index: fi as u64,
chunk_seq: 0,
checksum: *blake3::hash(bytes).as_bytes(),
compressed: false,
uncompressed_size: bytes.len() as u64,
compressed_size: bytes.len() as u64,
},
});
}
let resolver = move |fi: u64| names[fi as usize].clone();
let batch = build_metadata_batch(&blobs, resolver, &[], &[]).unwrap();
let schema = lookup_schema();
let mut sink = ArrowIpcSink::new(file.clone(), cursor);
sink.push_subindex(
schema.as_ref(),
&[batch],
GroupKey { pkg_type: 0, repo: String::new(), module_name: String::new() },
)
.unwrap();
Box::new(sink).finish().unwrap();
rows
}
#[test]
fn corrupt_delta_streams_are_refused() {
let base = b"0123456789abcdef".to_vec();
let mut bad = Vec::new();
put_varint(&mut bad, base.len() as u64);
put_varint(&mut bad, 32);
bad.extend_from_slice(&[0x80 | 0x01 | 0x10, 0, 32]);
let e = apply_delta(&base, &bad).unwrap_err().to_string();
assert!(e.contains("from a base of"), "copy overrun: {e}");
let mut zero = Vec::new();
put_varint(&mut zero, base.len() as u64);
put_varint(&mut zero, 1);
zero.push(0x00);
let e = apply_delta(&base, &zero).unwrap_err().to_string();
assert!(e.contains("0x00"), "zero opcode: {e}");
let d = encode_delta(base.len() + 1, 4, b"xy");
let e = apply_delta(&base, &d).unwrap_err().to_string();
assert!(e.contains("expects a base of"), "base size: {e}");
let mut short = Vec::new();
put_varint(&mut short, base.len() as u64);
put_varint(&mut short, 99);
short.push(2);
short.extend_from_slice(b"ab");
let e = apply_delta(&base, &short).unwrap_err().to_string();
assert!(e.contains("declared"), "short result: {e}");
}
#[test]
fn encoder_and_decoder_are_inverses() {
let body: Vec<u8> = (0..40_000u32).map(|i| (i.wrapping_mul(2654435761) >> 13) as u8).collect();
let mut cases: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
cases.push((body.clone(), body.clone())); cases.push((body.clone(), { let mut v = body.clone(); v.extend_from_slice(b"appended tail bytes"); v }));
cases.push((body.clone(), { let mut v = b"prepended header".to_vec(); v.extend_from_slice(&body); v }));
cases.push((body.clone(), { let mut v = body[..15_000].to_vec(); v.extend_from_slice(b"INSERTED IN THE MIDDLE OF IT"); v.extend_from_slice(&body[15_000..]); v }));
cases.push((body.clone(), { let mut v = body[..10_000].to_vec(); v.extend_from_slice(&body[20_000..]); v }));
cases.push((body.clone(), (0..30_000u32).map(|i| (i.wrapping_mul(40503) >> 7) as u8).collect()));
cases.push((body.clone(), Vec::new())); cases.push((Vec::new(), body.clone())); cases.push((Vec::new(), Vec::new()));
cases.push((b"short".to_vec(), b"shorter".to_vec()));
for (i, (base, target)) in cases.iter().enumerate() {
let d = encode_delta_against(base, target);
let got = apply_delta(base, &d)
.unwrap_or_else(|e| panic!("case {i}: decode failed: {e}"));
assert_eq!(&got, target, "case {i}: round-trip mismatch");
}
}
#[test]
fn a_small_edit_produces_a_small_delta() {
let body: Vec<u8> = (0..200_000u32).map(|i| (i.wrapping_mul(2654435761) >> 13) as u8).collect();
let mut edited = body.clone();
edited.extend_from_slice(b"one short appended line\n");
let d = encode_delta_against(&body, &edited);
assert_eq!(apply_delta(&body, &d).unwrap(), edited);
assert!(
d.len() < body.len() / 100,
"a 24-byte append to 200 kB must not cost {} B of delta",
d.len()
);
}
#[test]
fn the_writer_refuses_a_delta_for_stated_reasons() {
let a: Vec<u8> = {
let mut st = 0x0123_4567_89ab_cdefu64;
(0..50_000u32)
.map(|_| {
st = st.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
(z ^ (z >> 31)) as u8
})
.collect()
};
let mut b = a.clone();
b.extend_from_slice(b"tail");
let clen = |x: &[u8]| CompressCtx::new(3).unwrap().compress(x).unwrap().len();
let (plan, d) = plan_version("p", None, &a, clen);
assert_eq!(plan, VersionPlan::Chunked(ChunkedReason::FirstVersion));
assert!(d.is_none());
let (plan, d) = plan_version("p", Some(("p", &a, 0)), &b, clen);
match plan {
VersionPlan::Delta { ref base_path, chain_len, delta_bytes } => {
assert_eq!(base_path, "p");
assert_eq!(chain_len, 1);
assert!(delta_bytes < a.len() / 50, "delta should be tiny, was {delta_bytes}");
}
other => panic!("expected a delta, got {other:?}"),
}
assert_eq!(apply_delta(&a, d.as_ref().unwrap()).unwrap(), b);
let (plan, d) = plan_version("p", Some(("p", &a, MAX_DELTA_CHAIN)), &b, clen);
assert_eq!(plan, VersionPlan::Chunked(ChunkedReason::ChainTooLong));
assert!(d.is_none());
let unrelated: Vec<u8> = {
let mut st = 0xdead_beef_cafe_1234u64;
(0..50_000u32)
.map(|_| {
st = st.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = st;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
(z ^ (z >> 31)) as u8
})
.collect()
};
let (plan, d) = plan_version("p", Some(("p", &a, 0)), &unrelated, clen);
assert_eq!(plan, VersionPlan::Chunked(ChunkedReason::DeltaNotSmaller));
assert!(d.is_none());
}
#[test]
fn extract_file_verified_catches_corruption_fast_path_does_not() {
let dir = tmp("verify");
let archive = dir.join("a.znippy");
let files: Vec<(String, Vec<u8>)> = (0..8)
.map(|i| {
let mut s = 0x9e37_79b9_7f4a_7c15u64 ^ (i as u64);
let body: Vec<u8> = (0..4096u32)
.map(|_| {
s = s.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = s;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
(z ^ (z >> 31)) as u8
})
.collect();
(format!("repo/f{i:03}.bin"), body)
})
.collect();
write_archive(&archive, &files, None);
let ar = ZnippyArchive::open(&archive).unwrap();
for (p, bytes) in &files {
assert_eq!(&ar.extract_file(p).unwrap(), bytes);
assert_eq!(&ar.extract_file_verified(p).unwrap(), bytes, "clean verify for {p}");
}
drop(ar);
{
let f = std::fs::OpenOptions::new().read(true).write(true).open(&archive).unwrap();
let mut b = [0u8; 1];
f.read_exact_at(&mut b, 0).unwrap();
b[0] ^= 0xFF;
f.write_all_at(&b, 0).unwrap();
}
let ar = ZnippyArchive::open(&archive).unwrap();
let target = &files[0].0;
assert_ne!(&ar.extract_file(target).unwrap(), &files[0].1);
let err = ar.extract_file_verified(target).unwrap_err();
assert!(
err.to_string().contains("checksum mismatch"),
"expected checksum mismatch, got: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn duplicate_rows_for_one_path_do_not_double_the_extracted_bytes() {
let dir = tmp("dup");
let archive = dir.join("dup.znippy");
let payload = b"one true copy of the payload".to_vec();
let files = vec![
("repo/dup.bin".to_string(), payload.clone()),
("repo/dup.bin".to_string(), payload.clone()),
];
write_archive(&archive, &files, None);
let ar = ZnippyArchive::open(&archive).unwrap();
assert_eq!(
ar.file_size("repo/dup.bin"),
Some(payload.len() as u64),
"file_size must not be the SUM over duplicate rows"
);
assert_eq!(
ar.extract_file("repo/dup.bin").unwrap(),
payload,
"duplicate rows must not concatenate into a double-length buffer"
);
assert_eq!(ar.extract_file_verified("repo/dup.bin").unwrap(), payload);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn extract_file_rejects_out_of_bounds_blob_without_huge_alloc() {
let dir = tmp("bounds");
let archive = dir.join("b.znippy");
let files = vec![("repo/small.bin".to_string(), b"hello znippy".to_vec())];
write_archive(&archive, &files, Some(8 * 1024 * 1024 * 1024));
let ar = ZnippyArchive::open(&archive).unwrap();
let err = ar.extract_file("repo/small.bin").unwrap_err();
assert!(
err.to_string().contains("out of bounds"),
"expected out-of-bounds Err, got: {err}"
);
assert!(ar.extract_file_verified("repo/small.bin").is_err());
let _ = std::fs::remove_dir_all(&dir);
}
fn entry_from_units(units: Vec<StoredUnit>, blob_at: &mut dyn FnMut(&[u8]) -> (u64, u64)) -> Entry {
let mut chunks = Vec::new();
let mut size = 0u64;
for u in units {
let (produced, payload_bytes, source) = match u.payload {
UnitPayload::Bytes(b) => (b.clone(), b, ChunkSource::Stored),
UnitPayload::Delta { base_path, delta, expected } => {
(expected, delta, ChunkSource::Delta { base_path })
}
};
let (off, len) = blob_at(&payload_bytes);
size = size.max(u.fdata_offset + produced.len() as u64);
chunks.push(ChunkInfo {
blob_offset: off,
blob_size: len,
fdata_offset: u.fdata_offset,
compressed: false,
checksum: *blake3::hash(&produced).as_bytes(),
chunk_seq: 0,
source,
});
}
chunks.sort_by_key(|c| c.fdata_offset);
Entry { uncompressed_size: size, chunks }
}
#[test]
fn a_delta_chunk_reconstructs_through_the_one_reader() {
let dir = tmp("dchunk_ok");
let path = dir.join("d.znippy");
let base = b"the original object, stored once".to_vec();
let want1 = {
let mut v = base[..12].to_vec();
v.extend_from_slice(b" AND VERSION TWO");
v
};
let d1 = encode_delta(base.len(), 12, b" AND VERSION TWO");
let want2 = {
let mut v = want1[..5].to_vec();
v.extend_from_slice(b"third");
v
};
let d2 = encode_delta(want1.len(), 5, b"third");
let rows = write_base_and_deltas(
&path,
&[("obj/base.bin".to_string(), base.clone())],
&[d1.clone(), d2.clone()],
);
let ar = ZnippyArchive::open(&path).unwrap();
let mut at = |b: &[u8]| {
let (o, l, _) = *rows.iter().find(|(o, l, _)| {
let mut buf = vec![0u8; *l as usize];
ar.archive.read_exact_at(&mut buf, *o).unwrap();
buf == b
}).expect("payload not in blob region");
(o, l)
};
let e1 = entry_from_units(
vec![StoredUnit {
fdata_offset: 0,
payload: UnitPayload::Delta {
base_path: "obj/base.bin".into(),
delta: d1.clone(),
expected: want1.clone(),
},
}],
&mut at,
);
assert_eq!(e1.reconstruct("v1", &ar.reconstruct_ctx(0), false).unwrap(), want1);
assert_eq!(e1.reconstruct("v1", &ar.reconstruct_ctx(0), true).unwrap(), want1);
assert_eq!(e1.uncompressed_size(), want1.len() as u64);
let _ = (&d2, &want2);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_correct_delta_chunk_against_the_wrong_base_is_caught_on_the_fast_read() {
let dir = tmp("dchunk_wrongbase");
let path = dir.join("d.znippy");
let base = b"the original object, stored once".to_vec();
let decoy: Vec<u8> = {
let mut d = b"A DIFFERENT FILE".to_vec();
d.resize(base.len(), b'!');
assert_eq!(d.len(), base.len(), "decoy must match the base length");
d
};
let want = {
let mut v = base[..12].to_vec();
v.extend_from_slice(b" AND VERSION TWO");
v
};
let d1 = encode_delta(base.len(), 12, b" AND VERSION TWO");
let rows = write_base_and_deltas(
&path,
&[
("obj/base.bin".to_string(), base.clone()),
("obj/decoy.bin".to_string(), decoy.clone()),
],
&[d1.clone()],
);
let ar = ZnippyArchive::open(&path).unwrap();
let (off, len, _) = rows[0];
let unit = |base_path: &str| {
Entry {
uncompressed_size: want.len() as u64,
chunks: vec![ChunkInfo {
blob_offset: off,
blob_size: len,
fdata_offset: 0,
compressed: false,
checksum: *blake3::hash(&want).as_bytes(),
chunk_seq: 0,
source: ChunkSource::Delta { base_path: base_path.to_string() },
}],
}
};
assert_eq!(
unit("obj/base.bin").reconstruct("v", &ar.reconstruct_ctx(0), false).unwrap(),
want
);
let err = unit("obj/decoy.bin")
.reconstruct("v", &ar.reconstruct_ctx(0), false)
.unwrap_err()
.to_string();
assert!(
err.contains("result checksum") && err.contains("obj/decoy.bin"),
"the wrong base is the same LENGTH, so only the output hash can catch it, \
and the message must name the base it used — got: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_corrupted_base_of_identical_length_is_caught_by_the_chunk_checksum() {
let dir = tmp("dchunk_corruptbase");
let path = dir.join("cb.znippy");
let base: Vec<u8> = (0..8192u32).map(|i| (i.wrapping_mul(2654435761) >> 11) as u8).collect();
let mut target = base.clone();
target.extend_from_slice(b"\nthe second version\n");
let d1 = encode_delta_against(&base, &target);
let rows = write_base_and_deltas(
&path,
&[("v/base.bin".to_string(), base.clone())],
&[d1.clone()],
);
let (off, len, _) = rows[0];
let entry = || Entry {
uncompressed_size: target.len() as u64,
chunks: vec![ChunkInfo {
blob_offset: off,
blob_size: len,
fdata_offset: 0,
compressed: false,
checksum: *blake3::hash(&target).as_bytes(),
chunk_seq: 0,
source: ChunkSource::Delta { base_path: "v/base.bin".into() },
}],
};
{
let ar = ZnippyArchive::open(&path).unwrap();
assert_eq!(entry().reconstruct("v", &ar.reconstruct_ctx(0), false).unwrap(), target);
}
let base_off = rows.iter().map(|(o, s, _)| o + s).max().unwrap();
let f = File::options().read(true).write(true).open(&path).unwrap();
let mut b = [0u8; 1];
f.read_exact_at(&mut b, base_off).unwrap();
f.write_all_at(&[b[0] ^ 0xff], base_off).unwrap();
f.sync_all().unwrap();
let ar = ZnippyArchive::open(&path).unwrap();
assert_eq!(ar.file_size("v/base.bin"), Some(base.len() as u64));
let err = entry()
.reconstruct("v", &ar.reconstruct_ctx(0), false)
.unwrap_err()
.to_string();
assert!(err.contains("result checksum"), "got: {err}");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn one_entry_can_be_part_chunk_and_part_delta() {
let dir = tmp("mixed");
let path = dir.join("m.znippy");
let tile = 4096usize;
let base: Vec<u8> = (0..(tile * 4) as u32)
.map(|i| (i.wrapping_mul(2654435761) >> 9) as u8)
.collect();
let mut target = base.clone();
for b in target[tile..tile * 2].iter_mut() {
*b ^= 0x5a;
}
let split = RegionDeltaSplitter { chunk_size: tile };
let mut clen = |b: &[u8]| b.len();
let units = split.split("v2", &target, Some(("v/base.bin", &base, 0)), &mut clen);
assert_eq!(units.len(), 4, "four tiles");
let deltas = units
.iter()
.filter(|u| matches!(u.payload, UnitPayload::Delta { .. }))
.count();
let stored = units
.iter()
.filter(|u| matches!(u.payload, UnitPayload::Bytes(_)))
.count();
assert_eq!((deltas, stored), (3, 1), "three unchanged tiles, one edited");
let delta_bytes: usize = units
.iter()
.filter_map(|u| match &u.payload {
UnitPayload::Delta { delta, .. } => Some(delta.len()),
_ => None,
})
.sum();
assert!(
delta_bytes < 64,
"three whole-tile COPY instructions should be tens of bytes, not {delta_bytes}"
);
let payloads: Vec<Vec<u8>> = units
.iter()
.map(|u| match &u.payload {
UnitPayload::Bytes(b) => b.clone(),
UnitPayload::Delta { delta, .. } => delta.clone(),
})
.collect();
let rows = write_base_and_deltas(&path, &[("v/base.bin".to_string(), base.clone())], &payloads);
let mut i = 0usize;
let mut at = |_b: &[u8]| {
let (o, l, _) = rows[i];
i += 1;
(o, l)
};
let entry = entry_from_units(units, &mut at);
let ar = ZnippyArchive::open(&path).unwrap();
assert_eq!(
entry.reconstruct("v2", &ar.reconstruct_ctx(0), false).unwrap(),
target,
"a mixed entry must reconstruct exactly"
);
assert_eq!(entry.uncompressed_size(), target.len() as u64);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_self_referential_base_errors_instead_of_recursing_forever() {
let dir = tmp("cycle");
let path = dir.join("c.znippy");
let base = b"a base that will be replaced by a cycle".to_vec();
let d = encode_delta(base.len(), 4, b"xy");
let rows = write_base_and_deltas(&path, &[("v/base.bin".to_string(), base.clone())], &[d]);
let ar = ZnippyArchive::open(&path).unwrap();
let mut ar = ar;
let (off, len, _) = rows[0];
ar.file_index.insert(
"v/loop.bin".to_string(),
Entry {
uncompressed_size: 6,
chunks: vec![ChunkInfo {
blob_offset: off,
blob_size: len,
fdata_offset: 0,
compressed: false,
checksum: [0u8; 32],
chunk_seq: 0,
source: ChunkSource::Delta { base_path: "v/loop.bin".into() },
}],
},
);
let err = ar.extract_file("v/loop.bin").unwrap_err().to_string();
assert!(
err.contains("deeper than"),
"a cycle must be refused by the depth bound, got: {err}"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_mixed_entry_resolves_each_base_exactly_once() {
use std::sync::atomic::{AtomicUsize, Ordering};
struct Counting {
bytes: Vec<u8>,
calls: AtomicUsize,
}
impl BaseResolve for Counting {
fn resolve(&self, _path: &str, _verify: bool, _depth: usize) -> Result<Vec<u8>> {
self.calls.fetch_add(1, Ordering::SeqCst);
Ok(self.bytes.clone())
}
}
let dir = tmp("memo");
let path = dir.join("m.znippy");
let tile = 1024usize;
let base: Vec<u8> = (0..(tile * 4) as u32)
.map(|i| (i.wrapping_mul(2654435761) >> 9) as u8)
.collect();
let mut payloads = Vec::new();
for i in 0..4 {
let off = i * tile;
let mut d = Vec::new();
put_size_varint(&mut d, base.len() as u64);
put_size_varint(&mut d, tile as u64);
emit_copy(&mut d, off as u64, tile as u64);
payloads.push(d);
}
let rows = write_base_and_deltas(&path, &[("v/base.bin".to_string(), base.clone())], &payloads);
let file = Arc::new(File::open(&path).unwrap());
let archive_len = file.metadata().unwrap().len();
let chunks: Vec<ChunkInfo> = rows
.iter()
.enumerate()
.map(|(i, (off, len, _))| ChunkInfo {
blob_offset: *off,
blob_size: *len,
fdata_offset: (i * tile) as u64,
compressed: false,
checksum: *blake3::hash(&base[i * tile..(i + 1) * tile]).as_bytes(),
chunk_seq: 0,
source: ChunkSource::Delta { base_path: "v/base.bin".into() },
})
.collect();
let entry = Entry { uncompressed_size: base.len() as u64, chunks };
let counting = Counting { bytes: base.clone(), calls: AtomicUsize::new(0) };
let ctx = ReconstructCtx {
archive: &file,
archive_len,
resolve: Some(&counting),
depth: 0,
};
assert_eq!(entry.reconstruct("v2", &ctx, false).unwrap(), base);
assert_eq!(
counting.calls.load(Ordering::SeqCst),
1,
"four delta chunks naming ONE base must resolve it once"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
#[ignore]
fn perf_generation_delta() {
let base_p = match std::env::var("ZNIPPY_GEN_BASE") {
Ok(v) => v,
Err(_) => return,
};
let base = std::fs::read(&base_p).unwrap();
println!("target,base_b,target_b,delta_b,ratio_x,encode_ms,decode_ms,ok");
for t in std::env::var("ZNIPPY_GEN_TARGETS").unwrap().split(',') {
let target = std::fs::read(t).unwrap();
let t0 = std::time::Instant::now();
let d = encode_delta_against(&base, &target);
let enc = t0.elapsed().as_secs_f64() * 1e3;
let t1 = std::time::Instant::now();
let back = apply_delta(&base, &d).unwrap();
let dec = t1.elapsed().as_secs_f64() * 1e3;
let name = std::path::Path::new(t).file_name().unwrap().to_string_lossy();
println!(
"{name},{},{},{},{:.2},{enc:.1},{dec:.1},{}",
base.len(),
target.len(),
d.len(),
target.len() as f64 / d.len() as f64,
back == target
);
}
}
#[test]
#[ignore]
fn perf_chunk_chain_depth() {
let dir = tmp("cdepth");
let base: Vec<u8> = (0..256_000u32)
.map(|i| (i.wrapping_mul(2654435761) >> 13) as u8)
.collect();
println!("shape,depth_or_k,reconstruct_us,bytes");
for depth in [1usize, 2, 4, 8, 16, 32, 50] {
let path = dir.join(format!("chain{depth}.znippy"));
let mut versions = vec![base.clone()];
for i in 0..depth {
let mut v = versions[i].clone();
v.extend_from_slice(format!("\nedit number {i} appended here\n").as_bytes());
versions.push(v);
}
let deltas: Vec<Vec<u8>> = (0..depth)
.map(|i| encode_delta_against(&versions[i], &versions[i + 1]))
.collect();
let rows = write_base_and_deltas(
&path,
&[("v/0.bin".to_string(), base.clone())],
&deltas,
);
let mut ar = ZnippyArchive::open(&path).unwrap();
for (i, (off, len, _)) in rows.iter().enumerate() {
ar.file_index.insert(
format!("v/{}.bin", i + 1),
Entry {
uncompressed_size: versions[i + 1].len() as u64,
chunks: vec![ChunkInfo {
blob_offset: *off,
blob_size: *len,
fdata_offset: 0,
compressed: false,
checksum: *blake3::hash(&versions[i + 1]).as_bytes(),
chunk_seq: 0,
source: ChunkSource::Delta { base_path: format!("v/{i}.bin") },
}],
},
);
}
let tip = format!("v/{depth}.bin");
assert_eq!(&ar.extract_file(&tip).unwrap(), versions.last().unwrap());
let n = 20;
let t0 = std::time::Instant::now();
for _ in 0..n {
let _ = ar.extract_file(&tip).unwrap();
}
let us = t0.elapsed().as_secs_f64() * 1e6 / n as f64;
println!("chain,{depth},{us:.1},{}", versions.last().unwrap().len());
}
let tile = 4096usize;
for k in [1usize, 2, 4, 8, 16, 32, 50] {
let path = dir.join(format!("fan{k}.znippy"));
let target: Vec<u8> = base[..tile * k].to_vec();
let mut payloads = Vec::new();
let mut units = Vec::new();
for i in 0..k {
let off = i * tile;
let mut d = Vec::new();
put_size_varint(&mut d, base.len() as u64);
put_size_varint(&mut d, tile as u64);
emit_copy(&mut d, off as u64, tile as u64);
payloads.push(d.clone());
units.push(StoredUnit {
fdata_offset: off as u64,
payload: UnitPayload::Delta {
base_path: "v/base.bin".into(),
delta: d,
expected: base[off..off + tile].to_vec(),
},
});
}
let rows =
write_base_and_deltas(&path, &[("v/base.bin".to_string(), base.clone())], &payloads);
let mut idx = 0usize;
let mut at = |_b: &[u8]| {
let (o, l, _) = rows[idx];
idx += 1;
(o, l)
};
let entry = entry_from_units(units, &mut at);
let ar = ZnippyArchive::open(&path).unwrap();
assert_eq!(entry.reconstruct("fan", &ar.reconstruct_ctx(0), false).unwrap(), target);
let n = 20;
let t0 = std::time::Instant::now();
for _ in 0..n {
let _ = entry.reconstruct("fan", &ar.reconstruct_ctx(0), false).unwrap();
}
let us = t0.elapsed().as_secs_f64() * 1e6 / n as f64;
println!("fan,{k},{us:.1},{}", target.len());
}
let _ = std::fs::remove_dir_all(&dir);
}
}