use std::io::Read;
use flate2::read::ZlibDecoder;
use crate::error::{Error, Result};
use crate::unpack_objects::decompress_zlib_at;
use crate::zlib_inflate::ZlibInflateScratch;
const SKIP_BUF_LEN: usize = 64 * 1024;
pub(crate) fn skip_zlib_at(pack: &[u8], offset: usize, expected_size: usize) -> Result<usize> {
let slice = pack.get(offset..).ok_or_else(|| {
Error::CorruptObject(format!(
"pack stream truncated: need zlib at offset {offset}"
))
})?;
if expected_size == 0 {
let mut decoder = ZlibDecoder::new(slice);
let mut out = Vec::new();
decoder
.read_to_end(&mut out)
.map_err(|e| Error::Zlib(e.to_string()))?;
if !out.is_empty() {
return Err(Error::CorruptObject(
"0-byte packed object inflated to non-empty output".to_owned(),
));
}
return Ok(decoder.total_in() as usize);
}
let mut decoder = ZlibDecoder::new(slice);
let mut scratch = [0u8; SKIP_BUF_LEN];
let mut produced = 0usize;
while produced < expected_size {
let n = decoder
.read(&mut scratch)
.map_err(|e| Error::Zlib(e.to_string()))?;
if n == 0 {
break;
}
produced += n;
}
if produced != expected_size {
return Err(Error::CorruptObject(format!(
"decompressed {produced} bytes but expected {expected_size}"
)));
}
loop {
let n = decoder
.read(&mut scratch)
.map_err(|e| Error::Zlib(e.to_string()))?;
if n == 0 {
break;
}
}
Ok(decoder.total_in() as usize)
}
pub(crate) fn skip_zlib_at_with_scratch(
scratch: &mut ZlibInflateScratch,
pack: &[u8],
offset: usize,
expected_size: usize,
) -> Result<usize> {
let mut pos = offset;
scratch.skip_zlib_stream(pack, &mut pos, expected_size as u64)?;
Ok(pos - offset)
}
pub(crate) fn decompress_zlib_at_index(
pack: &[u8],
offset: usize,
expected_size: usize,
) -> Result<Vec<u8>> {
decompress_zlib_at(pack, offset, expected_size).map(|(data, _)| data)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::unpack_objects::decompress_zlib_at;
#[test]
fn skip_zlib_at_with_scratch_matches_skip_zlib_at() {
let plain = b"hello pack zlib skip scratch";
let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
use std::io::Write;
enc.write_all(plain).expect("zlib");
let zlib = enc.finish().expect("finish");
let mut pack = Vec::new();
pack.extend_from_slice(b"PACK");
pack.extend_from_slice(&2u32.to_be_bytes());
pack.extend_from_slice(&1u32.to_be_bytes());
pack.push(0x32);
let offset = pack.len();
pack.extend_from_slice(&zlib);
let mut scratch = ZlibInflateScratch::default();
let skipped = skip_zlib_at_with_scratch(&mut scratch, &pack, offset, plain.len())
.expect("scratch skip");
let direct = skip_zlib_at(&pack, offset, plain.len()).expect("direct skip");
assert_eq!(skipped, direct);
}
#[test]
fn skip_zlib_at_matches_decompress_for_zero_byte_object() {
let mut pack = Vec::new();
pack.extend_from_slice(b"PACK");
pack.extend_from_slice(&2u32.to_be_bytes());
pack.extend_from_slice(&1u32.to_be_bytes());
pack.push(0x30);
pack.extend_from_slice(&[0x78, 0x9c, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01]);
let offset = pack.len() - 8;
let skipped = skip_zlib_at(&pack, offset, 0).expect("skip");
let (_, consumed) = decompress_zlib_at(&pack, offset, 0).expect("decompress");
assert_eq!(skipped, consumed);
assert!(
consumed > 0,
"zlib wrapper must be consumed for 0-byte object"
);
}
}