use crate::error::{Error, Result};
const MAGIC: &[u8] = b"\xfd7zXZ\x00";
pub fn decode(data: &[u8], raw_size: usize, path: &str, offset: u64) -> Result<Vec<u8>> {
if !data.starts_with(MAGIC) {
return Err(Error::corrupt(
path,
offset,
"an lzma block that does not begin with the xz stream signature",
));
}
let mut out = Bounded {
out: Vec::with_capacity(raw_size.min(1 << 20)),
limit: raw_size.min(super::MAX_BLOCK_RAW_SIZE),
};
lzma_rs::xz_decompress(&mut std::io::Cursor::new(data), &mut out).map_err(|e| {
Error::corrupt(
path,
offset,
format!("could not inflate an lzma block: {e}"),
)
})?;
Ok(out.out)
}
struct Bounded {
out: Vec<u8>,
limit: usize,
}
impl std::io::Write for Bounded {
fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
if self.out.len() + data.len() > self.limit {
return Err(std::io::Error::other(format!(
"inflating past the {} bytes it declared",
self.limit
)));
}
self.out.write(data)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compress(data: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
lzma_rs::xz_compress(&mut std::io::Cursor::new(data), &mut out).expect("compress");
out
}
#[test]
fn an_xz_stream_round_trips() {
let data = b"ACGTACGTNNNN".repeat(500);
let compressed = compress(&data);
assert!(compressed.starts_with(MAGIC));
assert_eq!(
decode(&compressed, data.len(), "test", 0).expect("decode"),
data
);
}
#[test]
fn an_empty_stream_round_trips() {
let compressed = compress(b"");
assert_eq!(decode(&compressed, 0, "test", 0).expect("decode"), b"");
}
#[test]
fn a_stream_without_the_signature_is_refused() {
let error = decode(b"not xz at all!!!", 16, "test", 0).expect_err("no signature");
assert!(error.to_string().contains("xz stream signature"), "{error}");
}
#[test]
fn a_bare_lzma_stream_is_refused_rather_than_half_read() {
let mut bare = Vec::new();
lzma_rs::lzma_compress(
&mut std::io::Cursor::new(&b"ACGT".repeat(100)[..]),
&mut bare,
)
.expect("compress");
assert!(decode(&bare, 400, "test", 0).is_err(), "a bare lzma stream");
}
#[test]
fn a_truncated_stream_is_refused_rather_than_returning_what_it_had() {
let compressed = compress(&b"ACGT".repeat(1000));
let error = decode(&compressed[..compressed.len() / 2], 4000, "test", 0);
assert!(error.is_err(), "half a stream decoded");
}
#[test]
fn a_stream_longer_than_its_declared_size_is_refused() {
let data = b"ACGT".repeat(1000);
let compressed = compress(&data);
let error = decode(&compressed, 100, "test", 0).expect_err("past its declared size");
assert!(error.to_string().contains("lzma block"), "{error}");
}
#[test]
fn every_prefix_fails_without_panicking() {
let compressed = compress(&b"ACGTNNNN".repeat(100));
for cut in 0..compressed.len() {
let _ = decode(&compressed[..cut], 800, "test", 0);
}
}
}