use alloc::{boxed::Box, vec::Vec};
use super::{
BlockHeader, CheckType, ChecksumCalculator, FilterType, Index, IndexRecord, StreamFooter,
StreamHeader, XZ_FOOTER_MAGIC, XZ_MAGIC, count_multibyte_integer_size, parse_multibyte_integer,
};
use crate::{
CountingReader, Lzma2Reader, Read, Result,
crc::Crc32,
error_invalid_data,
filter::{
bcj::{BcjFilter, BcjReader},
delta::{Delta, DeltaReader},
},
lzma2_reader::{Action, Lzma2Stream, Status, StreamResult},
};
#[allow(clippy::large_enum_variant)]
enum FilterReader<R: Read> {
Counting(CountingReader<R>),
Lzma2(Lzma2Reader<Box<FilterReader<R>>>),
Delta(DeltaReader<Box<FilterReader<R>>>),
Bcj(BcjReader<Box<FilterReader<R>>>),
Dummy,
}
impl<R: Read> Read for FilterReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
match self {
FilterReader::Counting(reader) => reader.read(buf),
FilterReader::Lzma2(reader) => reader.read(buf),
FilterReader::Delta(reader) => reader.read(buf),
FilterReader::Bcj(reader) => reader.read(buf),
FilterReader::Dummy => unimplemented!(),
}
}
}
impl<R: Read> FilterReader<R> {
fn create_filter_chain(inner: R, filters: &[Option<FilterType>], properties: &[u32]) -> Self {
let mut chain_reader = FilterReader::Counting(CountingReader::new(inner));
for (filter, property) in filters
.iter()
.copied()
.zip(properties)
.filter_map(|(filter, property)| filter.map(|filter| (filter, *property)))
.rev()
{
chain_reader = match filter {
FilterType::Delta => {
let distance = property as usize;
FilterReader::Delta(DeltaReader::new(Box::new(chain_reader), distance))
}
FilterType::BcjX86 => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_x86(Box::new(chain_reader), start_offset))
}
FilterType::BcjPpc => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_ppc(Box::new(chain_reader), start_offset))
}
FilterType::BcjIa64 => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_ia64(Box::new(chain_reader), start_offset))
}
FilterType::BcjArm => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_arm(Box::new(chain_reader), start_offset))
}
FilterType::BcjArmThumb => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_arm_thumb(
Box::new(chain_reader),
start_offset,
))
}
FilterType::BcjSparc => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_sparc(Box::new(chain_reader), start_offset))
}
FilterType::BcjArm64 => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_arm64(Box::new(chain_reader), start_offset))
}
FilterType::BcjRiscv => {
let start_offset = property as usize;
FilterReader::Bcj(BcjReader::new_riscv(Box::new(chain_reader), start_offset))
}
FilterType::Lzma2 => {
let dict_size = property;
FilterReader::Lzma2(Lzma2Reader::new(Box::new(chain_reader), dict_size, None))
}
};
}
chain_reader
}
fn bytes_read(&self) -> u64 {
match self {
FilterReader::Counting(reader) => reader.bytes_read(),
FilterReader::Lzma2(reader) => reader.inner().bytes_read(),
FilterReader::Delta(reader) => reader.inner().bytes_read(),
FilterReader::Bcj(reader) => reader.inner().bytes_read(),
FilterReader::Dummy => unimplemented!(),
}
}
fn into_inner(self) -> R {
match self {
FilterReader::Counting(reader) => reader.inner,
FilterReader::Lzma2(reader) => {
let filter_reader = reader.into_inner();
filter_reader.into_inner()
}
FilterReader::Delta(reader) => {
let filter_reader = reader.into_inner();
filter_reader.into_inner()
}
FilterReader::Bcj(reader) => {
let filter_reader = reader.into_inner();
filter_reader.into_inner()
}
FilterReader::Dummy => unimplemented!(),
}
}
fn inner(&self) -> &R {
match self {
FilterReader::Counting(reader) => &reader.inner,
FilterReader::Lzma2(reader) => {
let filter_reader = reader.inner();
filter_reader.inner()
}
FilterReader::Delta(reader) => {
let filter_reader = reader.inner();
filter_reader.inner()
}
FilterReader::Bcj(reader) => {
let filter_reader = reader.inner();
filter_reader.inner()
}
FilterReader::Dummy => unimplemented!(),
}
}
fn inner_mut(&mut self) -> &mut R {
match self {
FilterReader::Counting(reader) => &mut reader.inner,
FilterReader::Lzma2(reader) => {
let filter_reader = reader.inner_mut();
filter_reader.inner_mut()
}
FilterReader::Delta(reader) => {
let filter_reader = reader.inner_mut();
filter_reader.inner_mut()
}
FilterReader::Bcj(reader) => {
let filter_reader = reader.inner_mut();
filter_reader.inner_mut()
}
FilterReader::Dummy => unimplemented!(),
}
}
}
pub struct XzReader<R: Read> {
reader: FilterReader<R>,
stream_header: Option<StreamHeader>,
checksum_calculator: Option<ChecksumCalculator>,
finished: bool,
allow_multiple_streams: bool,
blocks_processed: u64,
}
impl<R: Read> XzReader<R> {
pub fn new(inner: R, allow_multiple_streams: bool) -> Self {
let reader = FilterReader::Counting(CountingReader::new(inner));
Self {
reader,
stream_header: None,
checksum_calculator: None,
finished: false,
allow_multiple_streams,
blocks_processed: 0,
}
}
pub fn into_inner(self) -> R {
self.reader.into_inner()
}
pub fn inner(&self) -> &R {
self.reader.inner()
}
pub fn inner_mut(&mut self) -> &mut R {
self.reader.inner_mut()
}
}
impl<R: Read> XzReader<R> {
fn ensure_stream_header(&mut self) -> Result<()> {
if self.stream_header.is_none() {
let header = StreamHeader::parse(&mut self.reader)?;
self.stream_header = Some(header);
}
Ok(())
}
fn prepare_next_block(&mut self) -> Result<bool> {
match BlockHeader::parse(&mut self.reader)? {
Some(block_header) => {
let base_reader: FilterReader<R> =
core::mem::replace(&mut self.reader, FilterReader::Dummy);
self.reader = FilterReader::create_filter_chain(
base_reader.into_inner(),
&block_header.filters,
&block_header.properties,
);
match self.stream_header.as_ref() {
Some(header) => {
self.checksum_calculator = Some(ChecksumCalculator::new(header.check_type));
}
None => {
panic!("stream_header not set");
}
}
self.blocks_processed += 1;
Ok(true)
}
None => {
self.parse_index_and_footer()?;
if self.allow_multiple_streams && self.try_start_next_stream()? {
return self.prepare_next_block();
}
self.finished = true;
Ok(false)
}
}
}
fn consume_padding(&mut self, compressed_bytes: u64) -> Result<()> {
let padding_needed = match (4 - (compressed_bytes % 4)) % 4 {
0 => return Ok(()),
n => n as usize,
};
let mut padding_buf = [0u8; 3];
let bytes_read = self.reader.read(&mut padding_buf[..padding_needed])?;
if bytes_read != padding_needed {
return Err(error_invalid_data("incomplete XZ block padding"));
}
if !padding_buf[..bytes_read].iter().all(|&byte| byte == 0) {
return Err(error_invalid_data("invalid XZ block padding"));
}
Ok(())
}
fn verify_block_checksum(&mut self) -> Result<()> {
let checksum_calculator = self
.checksum_calculator
.take()
.expect("checksum_calculator not set");
match checksum_calculator {
ChecksumCalculator::None => { }
ChecksumCalculator::Crc32(_) => {
let mut checksum = [0u8; 4];
self.reader.read_exact(&mut checksum)?;
if !checksum_calculator.verify(&checksum) {
return Err(error_invalid_data("invalid block checksum"));
}
}
ChecksumCalculator::Crc64(_) => {
let mut checksum = [0u8; 8];
self.reader.read_exact(&mut checksum)?;
if !checksum_calculator.verify(&checksum) {
return Err(error_invalid_data("invalid block checksum"));
}
}
ChecksumCalculator::Sha256(_) => {
let mut checksum = [0u8; 32];
self.reader.read_exact(&mut checksum)?;
if !checksum_calculator.verify(&checksum) {
return Err(error_invalid_data("invalid block checksum"));
}
}
}
Ok(())
}
fn try_start_next_stream(&mut self) -> Result<bool> {
let mut padding_bytes = 0;
let mut buffer = [0u8; 6];
loop {
let mut byte_buffer = [0u8; 1];
let read = self.reader.read(&mut byte_buffer)?;
if read == 0 {
return Ok(false);
}
let byte = byte_buffer[0];
if byte == 0 {
padding_bytes += 1;
continue;
}
if byte != XZ_MAGIC[0] {
return Err(error_invalid_data("invalid data after stream"));
}
buffer[0] = byte;
let mut buffer_pos = 1;
while buffer_pos < 6 {
match self.reader.read(&mut byte_buffer)? {
0 => {
return Err(error_invalid_data("incomplete XZ magic bytes"));
}
1 => {
buffer[buffer_pos] = byte_buffer[0];
buffer_pos += 1;
}
_ => unreachable!(),
}
}
if buffer != XZ_MAGIC {
return Err(error_invalid_data("invalid data after stream padding"));
}
if padding_bytes % 4 != 0 {
return Err(error_invalid_data("stream padding size not multiple of 4"));
}
let stream_header = StreamHeader::parse_stream_header_flags_and_crc(&mut self.reader)?;
self.stream_header = Some(stream_header);
self.blocks_processed = 0;
return Ok(true);
}
}
fn parse_index_and_footer(&mut self) -> Result<()> {
let index = Index::parse(&mut self.reader)?;
if index.number_of_records != self.blocks_processed {
return Err(error_invalid_data(
"number of blocks processed doesn't match index records",
));
}
let stream_footer = StreamFooter::parse(&mut self.reader)?;
let header = self.stream_header.as_ref().expect("stream_header not set");
let header_flags = [0, header.check_type as u8];
if stream_footer.stream_flags != header_flags {
return Err(error_invalid_data(
"stream header and footer flags mismatch",
));
}
Ok(())
}
}
impl<R: Read> Read for XzReader<R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
if self.finished {
return Ok(0);
}
self.ensure_stream_header()?;
loop {
if self.checksum_calculator.is_some() {
let bytes_read = self.reader.read(buf)?;
if bytes_read > 0 {
if let Some(ref mut calc) = self.checksum_calculator {
calc.update(&buf[..bytes_read]);
}
return Ok(bytes_read);
} else {
let reader = core::mem::replace(&mut self.reader, FilterReader::Dummy);
let compressed_bytes = reader.bytes_read();
self.reader = FilterReader::Counting(CountingReader::with_count(
reader.into_inner(),
compressed_bytes,
));
self.consume_padding(compressed_bytes)?;
self.verify_block_checksum()?;
}
} else {
if !self.prepare_next_block()? {
return Ok(0);
}
}
}
}
}
enum StreamFilter {
Delta(Box<Delta>),
Bcj(BcjFilter),
}
impl StreamFilter {
fn from_filter_type(ft: FilterType, property: u32) -> Option<Self> {
match ft {
FilterType::Delta => Some(StreamFilter::Delta(Box::new(Delta::new(property as usize)))),
FilterType::BcjX86 => Some(StreamFilter::Bcj(BcjFilter::new_x86(
property as usize,
false,
))),
FilterType::BcjArm => Some(StreamFilter::Bcj(BcjFilter::new_arm(
property as usize,
false,
))),
FilterType::BcjArm64 => Some(StreamFilter::Bcj(BcjFilter::new_arm64(
property as usize,
false,
))),
FilterType::BcjArmThumb => Some(StreamFilter::Bcj(BcjFilter::new_arm_thumb(
property as usize,
false,
))),
FilterType::BcjPpc => Some(StreamFilter::Bcj(BcjFilter::new_power_pc(
property as usize,
false,
))),
FilterType::BcjSparc => Some(StreamFilter::Bcj(BcjFilter::new_sparc(
property as usize,
false,
))),
FilterType::BcjIa64 => Some(StreamFilter::Bcj(BcjFilter::new_ia64(
property as usize,
false,
))),
FilterType::BcjRiscv => Some(StreamFilter::Bcj(BcjFilter::new_riscv(
property as usize,
false,
))),
FilterType::Lzma2 => None,
}
}
fn apply_decode(&mut self, buf: &mut [u8]) -> usize {
match self {
StreamFilter::Delta(d) => {
d.decode(buf);
buf.len()
}
StreamFilter::Bcj(b) => b.code(buf),
}
}
}
#[derive(Clone, Copy)]
enum XzStreamState {
StreamHeader,
BlockHeaderSize,
BlockHeaderBody { header_size: usize },
Lzma2Data,
BlockPadding,
BlockChecksum { remaining: usize },
IndexCount,
IndexRecordUnpadded { remaining: u64 },
IndexRecordUncompressed { remaining: u64 },
IndexPaddingCrc,
StreamFooter,
InterStreamPadding,
Finished,
}
pub struct XzStream {
state: XzStreamState,
accum: Vec<u8>,
accum_needed: usize,
lzma2: Option<Lzma2Stream>,
checksum: Option<ChecksumCalculator>,
check_type: CheckType,
block_count: usize,
block_header_size: u64,
block_compressed_size: u64,
block_uncompressed_size: u64,
index_records: Vec<IndexRecord>,
index_crc: Crc32,
index_size: usize,
allow_multiple_streams: bool,
total_in: u64,
total_out: u64,
filter: Option<StreamFilter>,
filter_buf: Vec<u8>,
filter_pos: usize,
filter_unfiltered: usize,
}
impl XzStream {
pub fn new(allow_multiple_streams: bool) -> Self {
Self {
state: XzStreamState::StreamHeader,
accum: Vec::with_capacity(1024),
accum_needed: 12,
lzma2: None,
checksum: None,
check_type: CheckType::None,
block_count: 0,
block_header_size: 0,
block_compressed_size: 0,
block_uncompressed_size: 0,
index_records: Vec::new(),
index_crc: Crc32::new(),
index_size: 0,
allow_multiple_streams,
total_in: 0,
total_out: 0,
filter: None,
filter_buf: Vec::new(),
filter_pos: 0,
filter_unfiltered: 0,
}
}
pub fn total_in(&self) -> u64 {
self.total_in
}
pub fn total_out(&self) -> u64 {
self.total_out
}
pub fn process(
&mut self,
input: &[u8],
output: &mut [u8],
action: Action,
) -> Result<StreamResult> {
let mut in_pos = 0;
let mut out_pos = 0;
loop {
match &self.state {
XzStreamState::Finished => {
return Ok(StreamResult {
bytes_consumed: in_pos,
bytes_produced: out_pos,
status: Status::StreamEnd,
});
}
XzStreamState::Lzma2Data => {
if self.filter.is_some() {
if self.process_lzma2_filtered(
input,
output,
action,
&mut in_pos,
&mut out_pos,
)? == 0
{
return Ok(StreamResult {
bytes_consumed: in_pos,
bytes_produced: out_pos,
status: Status::Ok,
});
}
} else if let Some(result) = self.process_lzma2_unfiltered(
input,
output,
action,
&mut in_pos,
&mut out_pos,
)? {
return Ok(result);
}
}
_ => {
if self.accum.len() < self.accum_needed {
if in_pos >= input.len() {
if action == Action::Finish {
if matches!(self.state, XzStreamState::InterStreamPadding) {
if !self.accum.is_empty() {
return Err(error_invalid_data(
"inter-stream padding not a multiple of 4 bytes",
));
}
self.state = XzStreamState::Finished;
continue;
}
return Err(error_invalid_data("unexpected end of XZ stream"));
}
return Ok(StreamResult {
bytes_consumed: in_pos,
bytes_produced: out_pos,
status: Status::Ok,
});
}
let available = &input[in_pos..];
let need = self.accum_needed - self.accum.len();
let to_copy = need.min(available.len());
self.accum.extend_from_slice(&available[..to_copy]);
in_pos += to_copy;
self.total_in += to_copy as u64;
if self.accum.len() < self.accum_needed {
return Ok(StreamResult {
bytes_consumed: in_pos,
bytes_produced: out_pos,
status: Status::Ok,
});
}
}
self.process_accumulated()?;
}
}
}
}
fn process_lzma2_unfiltered(
&mut self,
input: &[u8],
output: &mut [u8],
action: Action,
in_pos: &mut usize,
out_pos: &mut usize,
) -> Result<Option<StreamResult>> {
let lzma2 = self.lzma2.as_mut().unwrap();
if lzma2.is_draining() {
if *out_pos >= output.len() {
return Ok(Some(StreamResult {
bytes_consumed: *in_pos,
bytes_produced: *out_pos,
status: Status::Ok,
}));
}
let prev_out = *out_pos;
lzma2.drain_with_filter(output, out_pos);
let drained = *out_pos - prev_out;
if drained > 0 {
self.total_out += drained as u64;
if let Some(cs) = self.checksum.as_mut() {
cs.update(&output[prev_out..*out_pos]);
}
}
if lzma2.has_output() {
return Ok(Some(StreamResult {
bytes_consumed: *in_pos,
bytes_produced: *out_pos,
status: Status::Ok,
}));
}
if lzma2.is_finished() {
self.finish_lzma2_block()?;
}
return Ok(None);
}
let result = lzma2.process(&input[*in_pos..], &mut output[*out_pos..], action)?;
*in_pos += result.bytes_consumed;
self.total_in += result.bytes_consumed as u64;
if result.bytes_produced > 0 {
if let Some(cs) = self.checksum.as_mut() {
cs.update(&output[*out_pos..*out_pos + result.bytes_produced]);
}
*out_pos += result.bytes_produced;
self.total_out += result.bytes_produced as u64;
}
if result.status == Status::StreamEnd {
self.finish_lzma2_block()?;
} else if *in_pos >= input.len() || *out_pos >= output.len() {
return Ok(Some(StreamResult {
bytes_consumed: *in_pos,
bytes_produced: *out_pos,
status: Status::Ok,
}));
}
Ok(None)
}
fn process_lzma2_filtered(
&mut self,
input: &[u8],
output: &mut [u8],
action: Action,
in_pos: &mut usize,
out_pos: &mut usize,
) -> Result<usize> {
if *out_pos >= output.len() {
return Ok(0);
}
if self.filter_pos < self.filter_buf.len() - self.filter_unfiltered {
return self.emit_filtered_output(output, out_pos);
}
if self.lzma2.as_ref().unwrap().is_draining() {
self.drain_and_filter_lzma2();
let lzma2 = self.lzma2.as_ref().unwrap();
if !lzma2.has_output() && lzma2.is_finished() {
self.flush_filter_pending();
}
return Ok(1);
}
let result = self
.lzma2
.as_mut()
.unwrap()
.process(&input[*in_pos..], &mut [], action)?;
*in_pos += result.bytes_consumed;
self.total_in += result.bytes_consumed as u64;
if result.status == Status::StreamEnd {
return self.try_complete_filtered_block();
}
if *in_pos >= input.len() && !self.lzma2.as_ref().unwrap().is_draining() {
return Ok(0);
}
Ok(1)
}
fn emit_filtered_output(&mut self, output: &mut [u8], out_pos: &mut usize) -> Result<usize> {
let ready_end = self.filter_buf.len() - self.filter_unfiltered;
let available = ready_end - self.filter_pos;
let space = output.len() - *out_pos;
let n = available.min(space);
output[*out_pos..*out_pos + n]
.copy_from_slice(&self.filter_buf[self.filter_pos..self.filter_pos + n]);
if let Some(cs) = self.checksum.as_mut() {
cs.update(&output[*out_pos..*out_pos + n]);
}
*out_pos += n;
self.total_out += n as u64;
self.filter_pos += n;
if self.filter_pos < ready_end {
return Ok(0);
}
self.compact_filter_buf();
let is_finished = {
let lzma2 = self.lzma2.as_ref().unwrap();
!lzma2.has_output() && lzma2.is_finished()
};
if is_finished {
return self.try_complete_filtered_block();
}
Ok(1)
}
fn drain_and_filter_lzma2(&mut self) -> usize {
let lzma2 = self.lzma2.as_mut().unwrap();
let prev_len = self.filter_buf.len();
lzma2.drain_to_buf(&mut self.filter_buf, 4096);
let new_bytes = self.filter_buf.len() - prev_len;
if new_bytes > 0 {
let filter_start = prev_len - self.filter_unfiltered;
let filter_slice = &mut self.filter_buf[filter_start..];
let filtered = self.filter.as_mut().unwrap().apply_decode(filter_slice);
self.filter_unfiltered = filter_slice.len() - filtered;
}
new_bytes
}
fn compact_filter_buf(&mut self) {
if self.filter_unfiltered > 0 {
let tail_start = self.filter_buf.len() - self.filter_unfiltered;
let pending: Vec<u8> = self.filter_buf[tail_start..].to_vec();
self.filter_buf.clear();
self.filter_buf.extend_from_slice(&pending);
} else {
self.filter_buf.clear();
}
self.filter_pos = 0;
self.filter_unfiltered = self.filter_buf.len();
}
fn try_complete_filtered_block(&mut self) -> Result<usize> {
self.flush_filter_pending();
if self.filter_pos < self.filter_buf.len() - self.filter_unfiltered {
return Ok(1);
}
self.filter.take();
self.finish_lzma2_block()?;
Ok(1)
}
fn flush_filter_pending(&mut self) {
self.filter_unfiltered = 0;
}
fn finish_lzma2_block(&mut self) -> Result<()> {
let lzma2 = self.lzma2.as_ref().unwrap();
self.block_compressed_size = lzma2.total_in();
self.block_uncompressed_size = lzma2.total_out();
let pad_needed = ((4 - (self.block_compressed_size % 4)) % 4) as usize;
if pad_needed > 0 {
self.state = XzStreamState::BlockPadding;
self.accum.clear();
self.accum_needed = pad_needed;
} else {
let check_size = self.check_type.checksum_size() as usize;
if check_size > 0 {
self.state = XzStreamState::BlockChecksum {
remaining: check_size,
};
self.accum.clear();
self.accum_needed = check_size;
} else {
self.push_index_record();
self.state = XzStreamState::BlockHeaderSize;
self.accum.clear();
self.accum_needed = 1;
}
}
Ok(())
}
fn push_index_record(&mut self) {
let check_size = self.check_type.checksum_size();
self.checksum.take();
self.index_records.push(IndexRecord {
unpadded_size: self.block_header_size + self.block_compressed_size + check_size,
uncompressed_size: self.block_uncompressed_size,
});
}
fn process_accumulated(&mut self) -> Result<()> {
match self.state {
XzStreamState::StreamHeader => self.process_stream_header(),
XzStreamState::BlockHeaderSize => self.process_block_header_size(),
XzStreamState::BlockHeaderBody { header_size } => {
self.process_block_header_body(header_size)
}
XzStreamState::BlockPadding => self.process_block_padding(),
XzStreamState::BlockChecksum { remaining } => self.process_block_checksum(remaining),
XzStreamState::IndexCount => self.process_index_count(),
XzStreamState::IndexRecordUnpadded { remaining } => {
self.process_index_record_unpadded(remaining)
}
XzStreamState::IndexRecordUncompressed { remaining } => {
self.process_index_record_uncompressed(remaining)
}
XzStreamState::IndexPaddingCrc => self.process_index_padding_crc(),
XzStreamState::StreamFooter => self.process_stream_footer(),
XzStreamState::InterStreamPadding => self.process_inter_stream_padding(),
_ => Ok(()),
}
}
fn process_stream_header(&mut self) -> Result<()> {
let data = &self.accum;
if data[..6] != XZ_MAGIC {
return Err(error_invalid_data("invalid XZ magic bytes"));
}
if data[6] != 0 {
return Err(error_invalid_data("invalid XZ stream flags"));
}
let check_type = CheckType::from_byte(data[7])?;
let expected_crc = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
if expected_crc != Crc32::checksum(&data[6..8]) {
return Err(error_invalid_data("XZ stream header CRC32 mismatch"));
}
self.check_type = check_type;
self.index_records.clear();
self.block_count = 0;
self.state = XzStreamState::BlockHeaderSize;
self.accum.clear();
self.accum_needed = 1;
Ok(())
}
fn process_block_header_size(&mut self) -> Result<()> {
let byte = self.accum[0];
if byte == 0x00 {
self.state = XzStreamState::IndexCount;
self.index_crc = Crc32::new();
self.index_crc.update(&[0x00]);
self.index_size = 0;
self.accum.clear();
self.accum_needed = 1;
} else {
let header_size = (byte as usize + 1) * 4;
self.state = XzStreamState::BlockHeaderBody { header_size };
self.accum_needed = header_size;
}
Ok(())
}
fn process_block_header_body(&mut self, header_size: usize) -> Result<()> {
let data = &self.accum[..header_size];
let crc_offset = header_size - 4;
let expected_crc = u32::from_le_bytes([
data[crc_offset],
data[crc_offset + 1],
data[crc_offset + 2],
data[crc_offset + 3],
]);
let actual_crc = Crc32::checksum(&data[..crc_offset]);
if expected_crc != actual_crc {
return Err(error_invalid_data("block header CRC32 mismatch"));
}
let (filters, properties, _) = BlockHeader::parse_from_slice(data)?;
let mut lzma2_dict_size = 0u32;
let mut found_lzma2 = false;
let mut pre_filter: Option<StreamFilter> = None;
for i in 0..4 {
if let Some(ft) = filters[i] {
if ft == FilterType::Lzma2 {
lzma2_dict_size = properties[i];
found_lzma2 = true;
} else if let Some(f) = StreamFilter::from_filter_type(ft, properties[i]) {
if pre_filter.is_some() {
return Err(error_invalid_data(
"multiple non-LZMA2 filters not supported yet for stream API",
));
}
pre_filter = Some(f);
}
}
}
if !found_lzma2 {
return Err(error_invalid_data("no LZMA2 filter in block"));
}
self.lzma2 = Some(Lzma2Stream::new(lzma2_dict_size));
self.checksum = Some(ChecksumCalculator::new(self.check_type));
self.filter = pre_filter;
self.filter_buf.clear();
self.filter_pos = 0;
self.filter_unfiltered = 0;
self.block_count += 1;
self.block_header_size = header_size as u64;
self.block_compressed_size = 0;
self.block_uncompressed_size = 0;
self.state = XzStreamState::Lzma2Data;
self.accum.clear();
Ok(())
}
fn process_block_padding(&mut self) -> Result<()> {
for &b in self.accum.iter() {
if b != 0 {
return Err(error_invalid_data("non-zero block padding"));
}
}
let check_size = self.check_type.checksum_size() as usize;
if check_size > 0 {
self.state = XzStreamState::BlockChecksum {
remaining: check_size,
};
self.accum.clear();
self.accum_needed = check_size;
} else {
self.push_index_record();
self.state = XzStreamState::BlockHeaderSize;
self.accum.clear();
self.accum_needed = 1;
}
Ok(())
}
fn process_block_checksum(&mut self, remaining: usize) -> Result<()> {
if self.accum.len() < remaining {
self.accum_needed = remaining;
return Ok(());
}
if let Some(checksum) = self.checksum.take() {
if !checksum.verify(&self.accum[..remaining]) {
return Err(error_invalid_data("block checksum mismatch"));
}
}
self.push_index_record();
self.state = XzStreamState::BlockHeaderSize;
self.accum.clear();
self.accum_needed = 1;
Ok(())
}
fn process_index_count(&mut self) -> Result<()> {
if !has_complete_vli(&self.accum)? {
self.accum_needed = self.accum.len() + 1;
return Ok(());
}
let num_records = parse_multibyte_integer(&self.accum)?;
let vli_size = count_multibyte_integer_size(&self.accum);
self.index_crc.update(&self.accum[..vli_size]);
self.index_size += vli_size;
if num_records != self.block_count as u64 {
return Err(error_invalid_data(
"index record count does not match number of blocks",
));
}
self.accum.clear();
if num_records > 0 {
self.state = XzStreamState::IndexRecordUnpadded {
remaining: num_records,
};
self.accum_needed = 1;
} else {
self.state = XzStreamState::IndexPaddingCrc;
let pad_needed = (4 - ((1 + self.index_size) % 4)) % 4;
self.accum_needed = pad_needed + 4;
}
Ok(())
}
fn process_index_record_unpadded(&mut self, remaining: u64) -> Result<()> {
if !has_complete_vli(&self.accum)? {
self.accum_needed = self.accum.len() + 1;
return Ok(());
}
let unpadded = parse_multibyte_integer(&self.accum)?;
let vli_size = count_multibyte_integer_size(&self.accum);
self.index_crc.update(&self.accum[..vli_size]);
self.index_size += vli_size;
let idx = self.block_count - remaining as usize;
if self.index_records[idx].unpadded_size != unpadded {
return Err(error_invalid_data("index unpadded size mismatch"));
}
self.accum.clear();
self.state = XzStreamState::IndexRecordUncompressed { remaining };
self.accum_needed = 1;
Ok(())
}
fn process_index_record_uncompressed(&mut self, remaining: u64) -> Result<()> {
if !has_complete_vli(&self.accum)? {
self.accum_needed = self.accum.len() + 1;
return Ok(());
}
let uncompressed = parse_multibyte_integer(&self.accum)?;
let vli_size = count_multibyte_integer_size(&self.accum);
self.index_crc.update(&self.accum[..vli_size]);
self.index_size += vli_size;
let idx = self.block_count - remaining as usize;
if self.index_records[idx].uncompressed_size != uncompressed {
return Err(error_invalid_data("index uncompressed size mismatch"));
}
self.accum.clear();
let remaining = remaining - 1;
if remaining > 0 {
self.state = XzStreamState::IndexRecordUnpadded { remaining };
self.accum_needed = 1;
} else {
self.state = XzStreamState::IndexPaddingCrc;
let pad_needed = (4 - ((1 + self.index_size) % 4)) % 4;
self.accum_needed = pad_needed + 4;
}
Ok(())
}
fn process_index_padding_crc(&mut self) -> Result<()> {
let pad_needed = (4 - ((1 + self.index_size) % 4)) % 4;
let total_needed = pad_needed + 4;
if self.accum.len() < total_needed {
self.accum_needed = total_needed;
return Ok(());
}
for &b in &self.accum[..pad_needed] {
if b != 0 {
return Err(error_invalid_data("non-zero index padding"));
}
}
self.index_crc.update(&self.accum[..pad_needed]);
let expected_crc = u32::from_le_bytes([
self.accum[pad_needed],
self.accum[pad_needed + 1],
self.accum[pad_needed + 2],
self.accum[pad_needed + 3],
]);
let actual_crc = core::mem::replace(&mut self.index_crc, Crc32::new()).finalize();
if actual_crc != expected_crc {
return Err(error_invalid_data("index CRC32 mismatch"));
}
self.accum.clear();
self.accum_needed = 12;
self.state = XzStreamState::StreamFooter;
Ok(())
}
fn process_stream_footer(&mut self) -> Result<()> {
let data = &self.accum;
if data.len() < 12 {
self.accum_needed = 12;
return Ok(());
}
let expected_crc = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let actual_crc = Crc32::checksum(&data[4..10]);
if expected_crc != actual_crc {
return Err(error_invalid_data("stream footer CRC32 mismatch"));
}
if data[10..12] != XZ_FOOTER_MAGIC {
return Err(error_invalid_data("invalid XZ footer magic"));
}
if data[8] != 0 {
return Err(error_invalid_data(
"reserved stream footer flags byte is non-zero",
));
}
let footer_check_type = CheckType::from_byte(data[9])?;
if footer_check_type != self.check_type {
return Err(error_invalid_data("stream footer flags don't match header"));
}
if self.allow_multiple_streams {
self.state = XzStreamState::InterStreamPadding;
self.accum.clear();
self.accum_needed = 4;
} else {
self.state = XzStreamState::Finished;
}
Ok(())
}
fn process_inter_stream_padding(&mut self) -> Result<()> {
if self.accum.len() < 4 {
self.accum_needed = 4;
return Ok(());
}
if self.accum[..4] == [0, 0, 0, 0] {
self.accum.clear();
self.accum_needed = 4;
} else if self.accum[..6.min(self.accum.len())] == XZ_MAGIC[..self.accum.len().min(6)] {
if self.accum.len() >= 6 && self.accum[..6] == XZ_MAGIC {
self.state = XzStreamState::StreamHeader;
self.accum_needed = 12;
} else {
self.accum_needed = 12;
}
} else {
return Err(error_invalid_data("invalid inter-stream padding"));
}
Ok(())
}
}
fn has_complete_vli(data: &[u8]) -> Result<bool> {
if data.len() > 9 {
return Err(error_invalid_data("XZ multibyte integer too long"));
}
for &byte in data {
if (byte & 0x80) == 0 {
return Ok(true);
}
}
Ok(false)
}