Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/decode/delta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::decode::lzbuffer;
use crate::decode::lzbuffer::LzBuffer;
use crate::error;
use byteorder::ReadBytesExt;
use std::io;

#[derive(Debug)]
/// Decoder for XZ delta-encoded blocks (filter 3).
pub struct DeltaDecoder {
distance: usize,
pos: u8,
delta: [u8; 256],
}

impl DeltaDecoder {
/// Creates a new object ready for transforming data that it's given.
pub fn new(property_distance: u8) -> Self {
DeltaDecoder {
distance: property_distance as usize + 1,
pos: 0,
delta: [0u8; 256],
}
}

/// Performs the equivalent of replacing this decompression state with a
/// freshly allocated copy.
///
/// This function may not allocate memory and will attempt to reuse any
/// previously allocated resources.
#[cfg(feature = "raw_decoder")]
pub fn reset(&mut self) {
self.pos = 0;
self.delta = [0u8; 256];
}

/// Decompresses the input data into the output, consuming only as much
/// input as needed and writing as much output as possible.
pub fn decompress<W: io::Write, R: io::BufRead>(
&mut self,
input: &mut R,
output: &mut W,
) -> error::Result<()> {
let mut accum = lzbuffer::LzAccumBuffer::from_stream(output, usize::MAX);

// See xz-file-format.txt for the C pseudocode this is implementing.
loop {
let byte = if let Ok(byte) = input.read_u8() {
byte
} else {
lzma_info!("Delta end of input");
break;
};

let tmp = self.delta[(self.distance + self.pos as usize) as u8 as usize];
let tmp = byte.wrapping_add(tmp);
self.delta[self.pos as usize] = tmp;

accum.append_literal(tmp)?;
self.pos = self.pos.wrapping_sub(1);
}

accum.finish()?;
Ok(())
}
}
1 change: 1 addition & 0 deletions src/decode/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Decoding logic.

pub mod delta;
pub mod lzbuffer;
pub mod lzma;
pub mod lzma2;
Expand Down
51 changes: 28 additions & 23 deletions src/decode/xz.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Decoder for the `.xz` file format.

use crate::decode::delta::DeltaDecoder;
use crate::decode::lzma2::Lzma2Decoder;
use crate::decode::util;
use crate::error;
Expand Down Expand Up @@ -173,15 +174,18 @@ where
#[derive(Debug)]
enum FilterId {
Lzma2,
Delta,
}

fn get_filter_id(id: u64) -> error::Result<FilterId> {
match id {
0x21 => Ok(FilterId::Lzma2),
0x03 => Ok(FilterId::Delta),
_ => Err(error::Error::XzError(format!("Unknown filter id {}", id))),
}
}

#[derive(Debug)]
struct Filter {
filter_id: FilterId,
props: Vec<u8>,
Expand Down Expand Up @@ -223,31 +227,22 @@ where
)));
}

let mut tmpbuf: Vec<u8> = Vec::new();
let filters = block_header.filters;
for (i, filter) in filters.iter().enumerate() {
if i == 0 {
// TODO: use SubBufRead on input if packed_size is known?
let packed_size = decode_filter(count_input, &mut tmpbuf, filter)?;
if let Some(expected_packed_size) = block_header.packed_size {
if (packed_size as u64) != expected_packed_size {
return Err(error::Error::XzError(format!(
"Invalid compressed size: expected {} but got {}",
expected_packed_size, packed_size
)));
}
}
} else {
let mut newbuf: Vec<u8> = Vec::new();
decode_filter(
&mut io::BufReader::new(tmpbuf.as_slice()),
&mut newbuf,
filter,
)?;
// TODO: does this move or copy?
tmpbuf = newbuf;
let decompress_filters = block_header.filters.iter().rev().collect::<Vec<_>>();
let mut tmpbuf = Vec::with_capacity(block_header.unpacked_size.unwrap_or(1 << 12) as usize);
let packed_size = decode_filter(count_input, &mut tmpbuf, decompress_filters[0])?;
if let Some(expected_packed_size) = block_header.packed_size {
if (packed_size as u64) != expected_packed_size {
return Err(error::Error::XzError(format!(
"Invalid compressed size: expected {} but got {}",
expected_packed_size, packed_size
)));
}
}
for filter in &decompress_filters[1..] {
let mut succ = Vec::with_capacity(block_header.unpacked_size.unwrap_or(1 << 12) as usize);
decode_filter(&mut (tmpbuf.as_slice()), &mut succ, filter)?;
tmpbuf = succ;
}

let unpacked_size = tmpbuf.len();
lzma_info!("XZ block decompressed to {} byte(s)", tmpbuf.len());
Expand Down Expand Up @@ -350,6 +345,16 @@ where
Lzma2Decoder::new().decompress(&mut count_input, output)?;
Ok(count_input.count())
}
FilterId::Delta => {
if filter.props.len() != 1 {
return Err(error::Error::XzError(format!(
"Invalid properties for filter {:?}",
filter.filter_id
)));
}
DeltaDecoder::new(filter.props[0]).decompress(&mut count_input, output)?;
Ok(count_input.count())
}
}
}

Expand Down
Binary file added tests/files/delta-filter-3.dat
Binary file not shown.
Binary file added tests/files/delta-filter-3.dat.xz
Binary file not shown.
11 changes: 11 additions & 0 deletions tests/xz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,14 @@ fn test_xz_block_check_crc32_invalid() {
"xz error: Invalid footer CRC32: expected 0x01234567 but got 0x8b0d303e"
)
}

#[test]
fn test_xz_delta_filter() {
#[cfg(feature = "enable_logging")]
let _ = env_logger::try_init();

decomp_big_file(
"tests/files/delta-filter-3.dat.xz",
"tests/files/delta-filter-3.dat",
);
}