use alloc::vec::Vec;
use super::{HEADER_SIZE, LZIP_MAGIC, LZIP_VERSION, TRAILER_SIZE, decode_dict_size};
use crate::{
Action, LzmaStream, Result, Status, StreamResult, crc::Crc32, error_eof, error_invalid_data,
error_out_of_memory, lzma_reader::get_memory_usage,
};
#[derive(Clone, Copy, PartialEq, Eq)]
enum LzipState {
Header,
LzmaData,
Trailer,
Finished,
}
fn source<'a>(pending: &'a [u8], input: &'a [u8]) -> (&'a [u8], bool) {
if pending.is_empty() {
(input, true)
} else {
(pending, false)
}
}
fn parse_header(header: &[u8]) -> Result<u32> {
if header[..4] != LZIP_MAGIC {
return Err(error_invalid_data("invalid LZIP magic bytes"));
}
if header[4] != LZIP_VERSION {
return Err(error_invalid_data("unsupported LZIP version"));
}
decode_dict_size(header[5])
}
pub struct LzipStream {
state: LzipState,
lzma: Option<LzmaStream>,
accum: Vec<u8>,
accum_needed: usize,
pending: Vec<u8>,
pending_pos: usize,
crc: Crc32,
data_size: u64,
member_compressed: u64,
members: usize,
mem_limit_kb: u32,
leftover: Vec<u8>,
total_in: u64,
total_out: u64,
}
impl Default for LzipStream {
fn default() -> Self {
Self::new()
}
}
impl LzipStream {
pub fn new() -> Self {
Self::new_mem_limit(u32::MAX)
}
pub fn new_mem_limit(mem_limit_kb: u32) -> Self {
Self {
state: LzipState::Header,
lzma: None,
accum: Vec::with_capacity(TRAILER_SIZE),
accum_needed: HEADER_SIZE,
pending: Vec::new(),
pending_pos: 0,
crc: Crc32::new(),
data_size: 0,
member_compressed: 0,
members: 0,
mem_limit_kb,
leftover: Vec::new(),
total_in: 0,
total_out: 0,
}
}
pub fn total_in(&self) -> u64 {
self.total_in
}
pub fn total_out(&self) -> u64 {
self.total_out
}
pub fn is_finished(&self) -> bool {
self.state == LzipState::Finished
}
pub fn has_output(&self) -> bool {
self.lzma.as_ref().is_some_and(|lzma| lzma.has_output())
}
pub fn member_count(&self) -> usize {
self.members
}
pub fn unused_input(&self) -> &[u8] {
if self.state == LzipState::Finished {
&self.leftover
} else {
&[]
}
}
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 {
LzipState::Finished => {
return Ok(StreamResult {
bytes_consumed: in_pos,
bytes_produced: out_pos,
status: Status::StreamEnd,
});
}
LzipState::Header => {
if let Some(result) = self.accumulate(input, action, &mut in_pos, out_pos)? {
return Ok(result);
}
let dict_size = match parse_header(&self.accum) {
Ok(dict_size) => dict_size,
Err(error) => {
if self.members == 0 {
return Err(error);
}
self.finish();
return Ok(StreamResult {
bytes_consumed: in_pos,
bytes_produced: out_pos,
status: Status::StreamEnd,
});
}
};
self.start_member(dict_size)?;
}
LzipState::LzmaData => {
if let Some(result) =
self.decode_payload(input, output, action, &mut in_pos, &mut out_pos)?
{
return Ok(result);
}
}
LzipState::Trailer => {
if let Some(result) = self.accumulate(input, action, &mut in_pos, out_pos)? {
return Ok(result);
}
self.verify_trailer()?;
}
}
}
}
fn accumulate(
&mut self,
input: &[u8],
action: Action,
in_pos: &mut usize,
out_pos: usize,
) -> Result<Option<StreamResult>> {
while self.accum.len() < self.accum_needed {
let (buf, from_caller) = source(&self.pending[self.pending_pos..], &input[*in_pos..]);
if buf.is_empty() {
if action != Action::Finish {
return Ok(Some(StreamResult {
bytes_consumed: *in_pos,
bytes_produced: out_pos,
status: Status::Ok,
}));
}
if self.state != LzipState::Header || self.members == 0 {
return Err(error_eof("unexpected end of LZIP stream"));
}
self.finish();
return Ok(Some(StreamResult {
bytes_consumed: *in_pos,
bytes_produced: out_pos,
status: Status::StreamEnd,
}));
}
let to_copy = (self.accum_needed - self.accum.len()).min(buf.len());
self.accum.extend_from_slice(&buf[..to_copy]);
if from_caller {
*in_pos += to_copy;
self.total_in += to_copy as u64;
} else {
self.pending_pos += to_copy;
}
}
Ok(None)
}
fn start_member(&mut self, dict_size: u32) -> Result<()> {
let need_mem = get_memory_usage(dict_size, 3, 0)?;
if self.mem_limit_kb < need_mem {
return Err(error_out_of_memory(
"needed memory too big for mem_limit_kb",
));
}
self.lzma = Some(LzmaStream::new(u64::MAX, 3, 0, 2, dict_size, None)?);
self.crc = Crc32::new();
self.data_size = 0;
self.member_compressed = 0;
self.accum.clear();
self.accum_needed = 0;
self.state = LzipState::LzmaData;
Ok(())
}
fn decode_payload(
&mut self,
input: &[u8],
output: &mut [u8],
action: Action,
in_pos: &mut usize,
out_pos: &mut usize,
) -> Result<Option<StreamResult>> {
let (buf, from_caller) = source(&self.pending[self.pending_pos..], &input[*in_pos..]);
let action = if from_caller { action } else { Action::Run };
let lzma = self
.lzma
.as_mut()
.ok_or_else(|| error_invalid_data("payload decoder not set"))?;
let result = lzma.process(buf, &mut output[*out_pos..], action)?;
if from_caller {
*in_pos += result.bytes_consumed;
self.total_in += result.bytes_consumed as u64;
} else {
self.pending_pos += result.bytes_consumed;
}
if result.bytes_produced > 0 {
self.crc
.update(&output[*out_pos..*out_pos + result.bytes_produced]);
self.data_size += result.bytes_produced as u64;
*out_pos += result.bytes_produced;
self.total_out += result.bytes_produced as u64;
}
if result.status == Status::StreamEnd {
self.finish_payload();
return Ok(None);
}
if (from_caller && *in_pos >= input.len())
|| *out_pos >= output.len()
|| (result.bytes_consumed == 0 && result.bytes_produced == 0)
{
return Ok(Some(StreamResult {
bytes_consumed: *in_pos,
bytes_produced: *out_pos,
status: Status::Ok,
}));
}
Ok(None)
}
fn finish_payload(&mut self) {
let lzma = self.lzma.take().expect("payload decoder not set");
let unused = lzma.unused_input();
self.member_compressed = lzma.total_in() - unused.len() as u64;
self.pending.drain(..self.pending_pos);
self.pending_pos = 0;
self.pending.splice(..0, unused.iter().copied());
self.accum.clear();
self.accum_needed = TRAILER_SIZE;
self.state = LzipState::Trailer;
}
fn verify_trailer(&mut self) -> Result<()> {
let crc32 =
u32::from_le_bytes([self.accum[0], self.accum[1], self.accum[2], self.accum[3]]);
let data_size = u64::from_le_bytes([
self.accum[4],
self.accum[5],
self.accum[6],
self.accum[7],
self.accum[8],
self.accum[9],
self.accum[10],
self.accum[11],
]);
let member_size = u64::from_le_bytes([
self.accum[12],
self.accum[13],
self.accum[14],
self.accum[15],
self.accum[16],
self.accum[17],
self.accum[18],
self.accum[19],
]);
if self.crc.finalize() != crc32 {
return Err(error_invalid_data("LZIP CRC32 mismatch"));
}
if self.data_size != data_size {
return Err(error_invalid_data("LZIP data size mismatch"));
}
let actual_member_size = HEADER_SIZE as u64 + self.member_compressed + TRAILER_SIZE as u64;
if actual_member_size != member_size {
return Err(error_invalid_data("LZIP member size mismatch"));
}
self.members += 1;
self.accum.clear();
self.accum_needed = HEADER_SIZE;
self.state = LzipState::Header;
Ok(())
}
fn finish(&mut self) {
self.leftover.clear();
self.leftover.extend_from_slice(&self.accum);
self.leftover
.extend_from_slice(&self.pending[self.pending_pos..]);
self.accum.clear();
self.pending.clear();
self.pending_pos = 0;
self.state = LzipState::Finished;
}
}