#![allow(clippy::unwrap_used, clippy::expect_used, clippy::doc_markdown)]
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use flate2::write::DeflateEncoder;
use flate2::Compression;
fn make_stored_block_zip(dir: &Path, name: &str, payload: &[u8]) -> PathBuf {
let mut enc = DeflateEncoder::new(Vec::new(), Compression::none());
enc.write_all(payload).unwrap();
let deflated = enc.finish().unwrap();
let crc = crc32(payload);
let path = dir.join(name);
let mut out = Vec::new();
let fname = b"image.bin";
out.extend_from_slice(&[0x50, 0x4b, 0x03, 0x04]);
out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&8u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(deflated.len() as u32).to_le_bytes()); out.extend_from_slice(&(payload.len() as u32).to_le_bytes()); out.extend_from_slice(&(fname.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); let lfh_start = 0usize;
out.extend_from_slice(fname);
let data_offset = out.len();
out.extend_from_slice(&deflated);
let cd_start = out.len();
out.extend_from_slice(&[0x50, 0x4b, 0x01, 0x02]);
out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&20u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&8u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(deflated.len() as u32).to_le_bytes());
out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
out.extend_from_slice(&(fname.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&(lfh_start as u32).to_le_bytes()); out.extend_from_slice(fname);
let cd_size = out.len() - cd_start;
out.extend_from_slice(&[0x50, 0x4b, 0x05, 0x06]);
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&(cd_size as u32).to_le_bytes());
out.extend_from_slice(&(cd_start as u32).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
let _ = data_offset;
std::fs::write(&path, &out).unwrap();
path
}
fn make_compressed_zip(dir: &Path, name: &str, payload: &[u8]) -> PathBuf {
let mut enc = DeflateEncoder::new(Vec::new(), Compression::best());
enc.write_all(payload).unwrap();
let deflated = enc.finish().unwrap();
let crc = crc32(payload);
let path = dir.join(name);
let mut out = Vec::new();
let fname = b"compressed.bin";
out.extend_from_slice(&[0x50, 0x4b, 0x03, 0x04]);
out.extend_from_slice(&20u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&8u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(deflated.len() as u32).to_le_bytes());
out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
out.extend_from_slice(&(fname.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
let lfh_start = 0usize;
out.extend_from_slice(fname);
out.extend_from_slice(&deflated);
let cd_start = out.len();
out.extend_from_slice(&[0x50, 0x4b, 0x01, 0x02]);
out.extend_from_slice(&20u16.to_le_bytes());
out.extend_from_slice(&20u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&8u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(deflated.len() as u32).to_le_bytes());
out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
out.extend_from_slice(&(fname.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u32.to_le_bytes());
out.extend_from_slice(&(lfh_start as u32).to_le_bytes());
out.extend_from_slice(fname);
let cd_size = out.len() - cd_start;
out.extend_from_slice(&[0x50, 0x4b, 0x05, 0x06]);
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&(cd_size as u32).to_le_bytes());
out.extend_from_slice(&(cd_start as u32).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
std::fs::write(&path, &out).unwrap();
path
}
#[test]
fn read_at_matches_full_decompress_on_compressed_entry() {
let dir = tempfile::tempdir().unwrap();
let payload: Vec<u8> = (0..80_000u32).map(|i| (i / 53) as u8).collect();
let path = make_compressed_zip(dir.path(), "fixture.zip", &payload);
let entry = zip_core::open_entry(&path, "compressed.bin").unwrap();
assert_eq!(entry.len(), payload.len() as u64);
assert!(
!entry.is_stored_block_indexed(),
"best-compression => fallback path"
);
let cases = [
(0usize, 16usize),
(1, 100),
(40_000, 999),
(79_990, 50),
(0, payload.len()),
];
for (off, len) in cases {
let mut buf = vec![0u8; len];
let n = entry.read_at(&mut buf, off as u64).unwrap();
let expected = oracle_read(&path, "compressed.bin", off, len);
assert_eq!(
&buf[..n],
&expected[..],
"mismatch at offset={off} len={len}"
);
}
}
fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for &b in data {
crc ^= u32::from(b);
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
}
fn oracle_read(path: &Path, name: &str, offset: usize, len: usize) -> Vec<u8> {
let mut archive = zip::ZipArchive::new(std::fs::File::open(path).unwrap()).unwrap();
let mut entry = archive.by_name(name).unwrap();
let mut all = Vec::new();
entry.read_to_end(&mut all).unwrap();
let start = offset.min(all.len());
let end = (offset + len).min(all.len());
all[start..end].to_vec()
}
#[test]
fn read_at_matches_full_decompress_on_stored_block_entry() {
let dir = tempfile::tempdir().unwrap();
let payload: Vec<u8> = (0..200_000u32)
.map(|i| (i.wrapping_mul(2_654_435_761) >> 13) as u8)
.collect();
let path = make_stored_block_zip(dir.path(), "fixture.zip", &payload);
let entry = zip_core::open_entry(&path, "image.bin").unwrap();
assert_eq!(entry.len(), payload.len() as u64);
let cases = [
(0usize, 16usize),
(1, 100),
(65_530, 20), (65_535, 10),
(131_070, 64), (199_990, 50), (0, payload.len()),
];
for (off, len) in cases {
let mut buf = vec![0xAAu8; len];
let n = entry.read_at(&mut buf, off as u64).unwrap();
let expected = oracle_read(&path, "image.bin", off, len);
assert_eq!(
&buf[..n],
&expected[..],
"mismatch at offset={off} len={len}"
);
}
}
#[test]
fn read_at_matches_real_e01_zip() {
let Ok(zip_path) = std::env::var("ZIP_CORE_REAL_E01_ZIP") else {
eprintln!("skipping: ZIP_CORE_REAL_E01_ZIP not set");
return;
};
let entry_name = std::env::var("ZIP_CORE_REAL_E01_ENTRY")
.unwrap_or_else(|_| "E01-DC01/20200918_0347_CDrive.E01".to_string());
let path = PathBuf::from(zip_path);
let entry = zip_core::open_entry(&path, &entry_name).unwrap();
let size = entry.len();
let offsets = [0u64, 4096, 1_000_003, size / 3, size / 2, size - 8192];
for off in offsets {
let len = 8192usize.min((size - off) as usize);
let mut buf = vec![0u8; len];
let n = entry.read_at(&mut buf, off).unwrap();
let expected = oracle_read(&path, &entry_name, off as usize, len);
assert_eq!(
&buf[..n],
&expected[..],
"real-E01 mismatch at offset={off}"
);
}
}
#[test]
fn native_decode_matches_extracted_ground_truth() {
let (Ok(zip_path), Ok(extracted)) = (
std::env::var("ZIP_CORE_REAL_E01_ZIP"),
std::env::var("ZIP_CORE_REAL_E01_EXTRACTED"),
) else {
eprintln!("skipping: ZIP_CORE_REAL_E01_ZIP / ZIP_CORE_REAL_E01_EXTRACTED not set");
return;
};
let entry_name = std::env::var("ZIP_CORE_REAL_E01_ENTRY")
.unwrap_or_else(|_| "E01-DC01/20200918_0347_CDrive.E01".to_string());
let zip = std::fs::File::open(&zip_path).unwrap();
let mut archive = zip_core::ZipArchive::new(zip).unwrap();
let mut decoded = archive.by_name(&entry_name).unwrap();
let total = decoded.size();
assert_eq!(
std::fs::metadata(&extracted).unwrap().len(),
total,
"entry size vs extracted ground truth"
);
let mut gt = std::io::BufReader::new(std::fs::File::open(&extracted).unwrap());
let mut got = vec![0u8; 1 << 20];
let mut want = vec![0u8; 1 << 20];
let mut pos = 0u64;
loop {
let mut n = 0usize;
while n < got.len() {
let r = decoded.read(&mut got[n..]).unwrap();
if r == 0 {
break;
}
n += r;
}
if n == 0 {
break;
}
gt.read_exact(&mut want[..n]).unwrap();
assert_eq!(&got[..n], &want[..n], "decode mismatch near byte {pos}");
pos += n as u64;
}
assert_eq!(pos, total, "decoded length vs ground truth");
}