Skip to main content

lzma_rust2/
lzma2_reader.rs

1use alloc::vec::Vec;
2
3use super::{
4    Read,
5    decoder::LzmaDecoder,
6    error_invalid_data, error_invalid_input,
7    lz::LzDecoder,
8    range_dec::{RangeDecoder, RangeDecoderBuffer},
9};
10use crate::{ByteReader, DICT_SIZE_MIN};
11
12pub const COMPRESSED_SIZE_MAX: u32 = 1 << 16;
13
14/// A single-threaded LZMA2 decompressor.
15///
16/// # Examples
17/// ```
18/// use std::io::Read;
19///
20/// use lzma_rust2::{Lzma2Reader, LzmaOptions};
21///
22/// let compressed: Vec<u8> = vec![
23///     1, 0, 12, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 0,
24/// ];
25/// let mut reader = Lzma2Reader::new(compressed.as_slice(), LzmaOptions::DICT_SIZE_DEFAULT, None);
26/// let mut decompressed = Vec::new();
27/// reader.read_to_end(&mut decompressed).unwrap();
28/// assert_eq!(&decompressed[..], b"Hello, world!");
29/// ```
30pub struct Lzma2Reader<R> {
31    inner: R,
32    lz: LzDecoder,
33    rc: RangeDecoder<RangeDecoderBuffer>,
34    lzma: Option<LzmaDecoder>,
35    uncompressed_size: usize,
36    is_lzma_chunk: bool,
37    need_dict_reset: bool,
38    need_props: bool,
39    end_reached: bool,
40}
41
42/// Calculates the memory usage in KiB required for LZMA2 decompression.
43#[inline]
44pub fn get_memory_usage(dict_size: u32) -> u32 {
45    40 + COMPRESSED_SIZE_MAX / 1024 + get_dict_size(dict_size) / 1024
46}
47
48#[inline]
49fn get_dict_size(dict_size: u32) -> u32 {
50    if dict_size >= (u32::MAX - 15) {
51        return u32::MAX;
52    }
53
54    (dict_size + 15) & !15
55}
56
57fn decode_lzma2_props(props: u8) -> crate::Result<LzmaDecoder> {
58    if props > (4 * 5 + 4) * 9 + 8 {
59        return Err(error_invalid_input("corrupted input data (LZMA2:3)"));
60    }
61    let pb = props / (9 * 5);
62    let remainder = props - pb * 9 * 5;
63    let lp = remainder / 9;
64    let lc = remainder - lp * 9;
65    if lc + lp > 4 {
66        return Err(error_invalid_input("corrupted input data (LZMA2:4)"));
67    }
68    Ok(LzmaDecoder::new(lc as _, lp as _, pb as _))
69}
70
71impl<R> Lzma2Reader<R> {
72    /// Unwraps the reader, returning the underlying reader.
73    pub fn into_inner(self) -> R {
74        self.inner
75    }
76
77    /// Returns a reference to the inner reader.
78    pub fn inner(&self) -> &R {
79        &self.inner
80    }
81
82    /// Returns a mutable reference to the inner reader.
83    pub fn inner_mut(&mut self) -> &mut R {
84        &mut self.inner
85    }
86}
87
88impl<R: Read> Lzma2Reader<R> {
89    /// Create a new LZMA2 reader.
90    /// `inner` is the reader to read compressed data from.
91    /// `dict_size` is the dictionary size in bytes.
92    pub fn new(inner: R, dict_size: u32, preset_dict: Option<&[u8]>) -> Self {
93        let has_preset = preset_dict.as_ref().map(|a| !a.is_empty()).unwrap_or(false);
94        let lz = LzDecoder::new(get_dict_size(dict_size) as _, preset_dict);
95        let rc = RangeDecoder::new_buffer(COMPRESSED_SIZE_MAX as _);
96        Self {
97            inner,
98            lz,
99            rc,
100            lzma: None,
101            uncompressed_size: 0,
102            is_lzma_chunk: false,
103            need_dict_reset: !has_preset,
104            need_props: true,
105            end_reached: false,
106        }
107    }
108
109    // ### LZMA2 Control Byte Meaning
110    //
111    //  Control Byte    | Chunk Type      | Formal Action
112    //  --------------- | --------------- | ----------------------------
113    //  0x00            | End of Stream   | Terminates the LZMA2 stream.
114    //  0x01            | Uncompressed    | Resets Dictionary.
115    //  0x02            | Uncompressed    | Preserves Dictionary.
116    //  0x03 – 0x7F     | Reserved        | Invalid stream.
117    //  0x80 – 0xFF     | LZMA Compressed | Varies based on bits 6 and 5
118    //
119    // ### Detailed Breakdown of LZMA Compressed Chunks (0x80 - 0xFF)
120    //
121    //  Bits | Control Byte | Reset Action            | Suitable for Parallel Start? |
122    //  ---- | ------------ | ----------------------- | ---------------------------- |
123    //  00   | 0x80 – 0x9F  | None                    | No
124    //  01   | 0xA0 – 0xBF  | Reset State             | No
125    //  10   | 0xC0 – 0xDF  | Reset State & Props     | No
126    //  11   | 0xE0 – 0xFF  | Reset Everything        | Yes
127    fn decode_chunk_header(&mut self) -> crate::Result<()> {
128        let control = self.inner.read_u8()?;
129
130        if control == 0x00 {
131            self.end_reached = true;
132            return Ok(());
133        }
134
135        if control >= 0xE0 || control == 0x01 {
136            self.need_props = true;
137            self.need_dict_reset = false;
138            // Reset dictionary
139            self.lz.reset();
140        } else if self.need_dict_reset {
141            return Err(error_invalid_input("corrupted input data (LZMA2:0)"));
142        }
143        if control >= 0x80 {
144            self.is_lzma_chunk = true;
145            self.uncompressed_size = ((control & 0x1F) as usize) << 16;
146            self.uncompressed_size += self.inner.read_u16_be()? as usize + 1;
147            let compressed_size = self.inner.read_u16_be()? as usize + 1;
148
149            if control >= 0xC0 {
150                // Reset props and state (by re-creating it)
151                self.need_props = false;
152                self.decode_props()?;
153            } else if self.need_props {
154                return Err(error_invalid_input("corrupted input data (LZMA2:1)"));
155            } else if control >= 0xA0 {
156                // Reset state
157                if let Some(l) = self.lzma.as_mut() {
158                    l.reset()
159                }
160            }
161
162            self.rc.prepare(&mut self.inner, compressed_size)?;
163        } else if control > 0x02 {
164            return Err(error_invalid_input("corrupted input data (LZMA2:2)"));
165        } else {
166            self.is_lzma_chunk = false;
167            self.uncompressed_size = (self.inner.read_u16_be()? as usize) + 1;
168        }
169        Ok(())
170    }
171
172    fn decode_props(&mut self) -> crate::Result<()> {
173        let props = self.inner.read_u8()?;
174        self.lzma = Some(decode_lzma2_props(props)?);
175        Ok(())
176    }
177}
178
179impl<R: Read> Read for Lzma2Reader<R> {
180    fn read(&mut self, buf: &mut [u8]) -> crate::Result<usize> {
181        if buf.is_empty() {
182            return Ok(0);
183        }
184
185        if self.end_reached {
186            return Ok(0);
187        }
188
189        self.lz.ensure_capacity()?;
190
191        let mut size = 0;
192        let mut len = buf.len();
193        let mut off = 0;
194        while len > 0 {
195            if self.uncompressed_size == 0 {
196                self.decode_chunk_header()?;
197                if self.end_reached {
198                    return Ok(size);
199                }
200            }
201
202            let copy_size_max = self.uncompressed_size.min(len);
203            if !self.is_lzma_chunk {
204                self.lz.copy_uncompressed(&mut self.inner, copy_size_max)?;
205            } else {
206                self.lz.set_limit(copy_size_max);
207                if let Some(lzma) = self.lzma.as_mut() {
208                    lzma.decode(&mut self.lz, &mut self.rc)?;
209                }
210            }
211
212            {
213                let copied_size = self.lz.flush(buf, off)?;
214                off = off.saturating_add(copied_size);
215                len = len.saturating_sub(copied_size);
216                size = size.saturating_add(copied_size);
217                self.uncompressed_size = self.uncompressed_size.saturating_sub(copied_size);
218                if self.uncompressed_size == 0 && (!self.rc.is_finished() || self.lz.has_pending())
219                {
220                    return Err(error_invalid_input("rc not finished or lz has pending"));
221                }
222            }
223        }
224
225        Ok(size)
226    }
227}
228
229// ── Sans-I/O stream types ───────────────────────────────────────────────────
230
231/// Action to perform during stream processing.
232#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum Action {
234    /// Process available data without flushing.
235    Run,
236    /// Signal that no more input will be provided.
237    Finish,
238}
239
240/// Status returned by stream processing.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum Status {
243    /// More input or output space needed to continue.
244    Ok,
245    /// The stream has been fully processed.
246    StreamEnd,
247}
248
249/// Result of a single `process()` call.
250#[derive(Debug, Clone, Copy)]
251pub struct StreamResult {
252    /// Number of bytes consumed from the input buffer.
253    pub bytes_consumed: usize,
254    /// Number of bytes written to the output buffer.
255    pub bytes_produced: usize,
256    /// Current stream status.
257    pub status: Status,
258}
259
260#[derive(Clone, Copy)]
261enum Lzma2State {
262    ChunkHeader,
263    CompressedData { remaining: usize },
264    UncompressedData { remaining: usize },
265    DrainUncompressed { remaining: usize },
266    Decode,
267    DrainOutput,
268    Finished,
269}
270
271/// Sans-I/O LZMA2 stream decoder.
272///
273/// Decodes a raw LZMA2 byte stream (no XZ container). Call `process()` repeatedly
274/// with input/output buffers until `Status::StreamEnd` is returned.
275pub struct Lzma2Stream {
276    state: Lzma2State,
277    accum: Vec<u8>,
278    accum_needed: usize,
279    lz: LzDecoder,
280    rc: RangeDecoder<RangeDecoderBuffer>,
281    lzma: Option<LzmaDecoder>,
282    compressed_buf: Vec<u8>,
283    uncompressed_size: usize,
284    need_dict_reset: bool,
285    need_props: bool,
286    total_in: u64,
287    total_out: u64,
288}
289
290impl Lzma2Stream {
291    /// Create a new LZMA2 stream decoder with the given dictionary size.
292    pub fn new(dict_size: u32) -> Self {
293        let dict_size = get_dict_size(dict_size.max(DICT_SIZE_MIN)) as usize;
294        Self {
295            state: Lzma2State::ChunkHeader,
296            accum: Vec::with_capacity(8),
297            accum_needed: 1,
298            lz: LzDecoder::new(dict_size, None),
299            rc: RangeDecoder::new_buffer(65536),
300            lzma: None,
301            compressed_buf: Vec::new(),
302            uncompressed_size: 0,
303            need_dict_reset: true,
304            need_props: true,
305            total_in: 0,
306            total_out: 0,
307        }
308    }
309
310    /// Total bytes consumed from input across all `process()` calls.
311    pub fn total_in(&self) -> u64 {
312        self.total_in
313    }
314
315    /// Total bytes produced to output across all `process()` calls.
316    pub fn total_out(&self) -> u64 {
317        self.total_out
318    }
319
320    /// Returns true if the LZMA2 stream has been fully decoded.
321    pub fn is_finished(&self) -> bool {
322        matches!(self.state, Lzma2State::Finished)
323    }
324
325    /// Returns true if there is decoded output waiting to be flushed.
326    pub fn has_output(&self) -> bool {
327        self.lz.has_output()
328    }
329
330    /// Process available LZMA2 data from `input` into `output`.
331    pub fn process(
332        &mut self,
333        input: &[u8],
334        output: &mut [u8],
335        action: Action,
336    ) -> crate::Result<StreamResult> {
337        self.lz.ensure_capacity()?;
338
339        let mut in_pos = 0;
340        let mut out_pos = 0;
341
342        loop {
343            match self.state {
344                Lzma2State::Finished => {
345                    return Ok(StreamResult {
346                        bytes_consumed: in_pos,
347                        bytes_produced: out_pos,
348                        status: Status::StreamEnd,
349                    });
350                }
351
352                Lzma2State::DrainOutput | Lzma2State::DrainUncompressed { .. } => {
353                    if out_pos >= output.len() {
354                        return Ok(StreamResult {
355                            bytes_consumed: in_pos,
356                            bytes_produced: out_pos,
357                            status: Status::Ok,
358                        });
359                    }
360                    if !self.flush_output(output, &mut out_pos) {
361                        return Ok(StreamResult {
362                            bytes_consumed: in_pos,
363                            bytes_produced: out_pos,
364                            status: Status::Ok,
365                        });
366                    }
367                }
368
369                Lzma2State::Decode => {
370                    self.decode_lzma()?;
371                }
372
373                Lzma2State::CompressedData { remaining } => {
374                    if let Some(result) = self.process_compressed_data(
375                        input,
376                        action,
377                        &mut in_pos,
378                        out_pos,
379                        remaining,
380                    )? {
381                        return Ok(result);
382                    }
383                }
384
385                Lzma2State::UncompressedData { remaining } => {
386                    if let Some(result) = self.process_uncompressed_data(
387                        input,
388                        action,
389                        &mut in_pos,
390                        out_pos,
391                        remaining,
392                    )? {
393                        return Ok(result);
394                    }
395                }
396
397                Lzma2State::ChunkHeader => {
398                    if let Some(result) =
399                        self.accumulate_chunk_header(input, action, &mut in_pos, out_pos)?
400                    {
401                        return Ok(result);
402                    }
403                }
404            }
405        }
406    }
407
408    fn flush_output(&mut self, output: &mut [u8], out_pos: &mut usize) -> bool {
409        let n = self.lz.flush_partial(&mut output[*out_pos..]);
410        if n > 0 {
411            *out_pos += n;
412            self.total_out += n as u64;
413        }
414        if self.lz.has_output() {
415            return false;
416        }
417        self.finish_drain();
418        true
419    }
420
421    fn decode_lzma(&mut self) -> crate::Result<()> {
422        let pos_before = self.lz.get_pos();
423        self.lz.set_limit(self.uncompressed_size);
424        self.lzma
425            .as_mut()
426            .ok_or_else(|| error_invalid_input("corrupted input data (LZMA2:1)"))?
427            .decode(&mut self.lz, &mut self.rc)?;
428        let decoded = self.lz.get_pos() - pos_before;
429        self.uncompressed_size -= decoded;
430
431        if self.uncompressed_size == 0 && (!self.rc.is_finished() || self.lz.has_pending()) {
432            return Err(error_invalid_input("rc not finished or lz has pending"));
433        }
434
435        self.state = Lzma2State::DrainOutput;
436        Ok(())
437    }
438
439    fn process_compressed_data(
440        &mut self,
441        input: &[u8],
442        action: Action,
443        in_pos: &mut usize,
444        out_pos: usize,
445        remaining: usize,
446    ) -> crate::Result<Option<StreamResult>> {
447        if *in_pos >= input.len() {
448            if action == Action::Finish {
449                return Err(error_invalid_data("unexpected end of LZMA2 stream"));
450            }
451            return Ok(Some(StreamResult {
452                bytes_consumed: *in_pos,
453                bytes_produced: out_pos,
454                status: Status::Ok,
455            }));
456        }
457        let available = &input[*in_pos..];
458        let to_copy = remaining.min(available.len());
459        self.compressed_buf.extend_from_slice(&available[..to_copy]);
460        *in_pos += to_copy;
461        self.total_in += to_copy as u64;
462        let new_remaining = remaining - to_copy;
463        if new_remaining == 0 {
464            self.rc.prepare_from_slice(&self.compressed_buf)?;
465            self.compressed_buf.clear();
466            self.state = Lzma2State::Decode;
467        } else {
468            self.state = Lzma2State::CompressedData {
469                remaining: new_remaining,
470            };
471        }
472        Ok(None)
473    }
474
475    fn process_uncompressed_data(
476        &mut self,
477        input: &[u8],
478        action: Action,
479        in_pos: &mut usize,
480        out_pos: usize,
481        remaining: usize,
482    ) -> crate::Result<Option<StreamResult>> {
483        let lz_space = self.lz.available_space();
484        if lz_space == 0 {
485            self.state = Lzma2State::DrainUncompressed { remaining };
486            return Ok(None);
487        }
488        if *in_pos >= input.len() {
489            if action == Action::Finish {
490                return Err(error_invalid_data("unexpected end of LZMA2 stream"));
491            }
492            return Ok(Some(StreamResult {
493                bytes_consumed: *in_pos,
494                bytes_produced: out_pos,
495                status: Status::Ok,
496            }));
497        }
498        let available = &input[*in_pos..];
499        let to_copy = remaining.min(available.len()).min(lz_space);
500        self.lz
501            .copy_uncompressed_from_slice(&available[..to_copy])?;
502        *in_pos += to_copy;
503        self.total_in += to_copy as u64;
504        self.uncompressed_size -= to_copy;
505        let new_remaining = remaining - to_copy;
506        if new_remaining == 0 {
507            self.state = Lzma2State::DrainOutput;
508        } else if self.lz.available_space() == 0 {
509            self.state = Lzma2State::DrainUncompressed {
510                remaining: new_remaining,
511            };
512        } else {
513            self.state = Lzma2State::UncompressedData {
514                remaining: new_remaining,
515            };
516        }
517        Ok(None)
518    }
519
520    fn accumulate_chunk_header(
521        &mut self,
522        input: &[u8],
523        action: Action,
524        in_pos: &mut usize,
525        out_pos: usize,
526    ) -> crate::Result<Option<StreamResult>> {
527        if self.accum.len() < self.accum_needed {
528            if *in_pos >= input.len() {
529                if action == Action::Finish {
530                    return Err(error_invalid_data("unexpected end of LZMA2 stream"));
531                }
532                return Ok(Some(StreamResult {
533                    bytes_consumed: *in_pos,
534                    bytes_produced: out_pos,
535                    status: Status::Ok,
536                }));
537            }
538            let available = &input[*in_pos..];
539            let need = self.accum_needed - self.accum.len();
540            let to_copy = need.min(available.len());
541            self.accum.extend_from_slice(&available[..to_copy]);
542            *in_pos += to_copy;
543            self.total_in += to_copy as u64;
544            if self.accum.len() < self.accum_needed {
545                return Ok(Some(StreamResult {
546                    bytes_consumed: *in_pos,
547                    bytes_produced: out_pos,
548                    status: Status::Ok,
549                }));
550            }
551        }
552        self.process_chunk_header()?;
553        Ok(None)
554    }
555
556    pub(crate) fn is_draining(&self) -> bool {
557        matches!(
558            self.state,
559            Lzma2State::DrainOutput | Lzma2State::DrainUncompressed { .. }
560        )
561    }
562
563    pub(crate) fn drain_with_filter(&mut self, output: &mut [u8], out_pos: &mut usize) -> usize {
564        if *out_pos >= output.len() {
565            return 0;
566        }
567        let n = self.lz.flush_partial(&mut output[*out_pos..]);
568        if n > 0 {
569            *out_pos += n;
570            self.total_out += n as u64;
571        }
572        if !self.lz.has_output() {
573            self.finish_drain();
574        }
575        n
576    }
577
578    pub(crate) fn drain_to_buf(&mut self, buf: &mut Vec<u8>, limit: usize) -> usize {
579        let mut tmp = [0u8; 4096];
580        let cap = limit.min(tmp.len());
581        let n = self.lz.flush_partial(&mut tmp[..cap]);
582        if n > 0 {
583            buf.extend_from_slice(&tmp[..n]);
584            self.total_out += n as u64;
585        }
586        if !self.lz.has_output() {
587            self.finish_drain();
588        }
589        n
590    }
591
592    fn finish_drain(&mut self) {
593        match self.state {
594            Lzma2State::DrainUncompressed { remaining } => {
595                self.state = Lzma2State::UncompressedData { remaining };
596            }
597            _ if self.uncompressed_size > 0 => {
598                self.state = Lzma2State::Decode;
599            }
600            _ => {
601                self.state = Lzma2State::ChunkHeader;
602                self.accum.clear();
603                self.accum_needed = 1;
604            }
605        }
606    }
607
608    fn process_chunk_header(&mut self) -> crate::Result<()> {
609        let control = self.accum[0];
610        if control == 0x00 {
611            self.state = Lzma2State::Finished;
612            Ok(())
613        } else if control >= 0x80 {
614            self.process_compressed_chunk_header(control)
615        } else if control <= 0x02 {
616            self.process_uncompressed_chunk_header(control)
617        } else {
618            Err(error_invalid_input("corrupted input data (LZMA2:2)"))
619        }
620    }
621
622    fn process_compressed_chunk_header(&mut self, control: u8) -> crate::Result<()> {
623        let needed = if control >= 0xC0 { 6 } else { 5 };
624        if self.accum.len() < needed {
625            self.accum_needed = needed;
626            return Ok(());
627        }
628
629        if control >= 0xE0 {
630            self.need_props = true;
631            self.need_dict_reset = false;
632            self.lz.reset();
633        } else if self.need_dict_reset {
634            return Err(error_invalid_input("corrupted input data (LZMA2:0)"));
635        }
636
637        self.uncompressed_size = ((control & 0x1F) as usize) << 16;
638        let uncompressed_hi = u16::from_be_bytes([self.accum[1], self.accum[2]]);
639        self.uncompressed_size += uncompressed_hi as usize + 1;
640        let compressed_size = u16::from_be_bytes([self.accum[3], self.accum[4]]) as usize + 1;
641
642        if control >= 0xC0 {
643            self.need_props = false;
644            self.lzma = Some(decode_lzma2_props(self.accum[5])?);
645        } else if self.need_props {
646            return Err(error_invalid_input("corrupted input data (LZMA2:1)"));
647        } else if control >= 0xA0 {
648            if let Some(l) = self.lzma.as_mut() {
649                l.reset();
650            }
651        }
652
653        self.compressed_buf.clear();
654        self.compressed_buf.reserve(compressed_size);
655        self.state = Lzma2State::CompressedData {
656            remaining: compressed_size,
657        };
658        self.accum.clear();
659        Ok(())
660    }
661
662    fn process_uncompressed_chunk_header(&mut self, control: u8) -> crate::Result<()> {
663        if self.accum.len() < 3 {
664            self.accum_needed = 3;
665            return Ok(());
666        }
667
668        if control == 0x01 {
669            self.need_props = true;
670            self.need_dict_reset = false;
671            self.lz.reset();
672        } else if self.need_dict_reset {
673            return Err(error_invalid_input("corrupted input data (LZMA2:0)"));
674        }
675
676        self.uncompressed_size = u16::from_be_bytes([self.accum[1], self.accum[2]]) as usize + 1;
677
678        self.state = Lzma2State::UncompressedData {
679            remaining: self.uncompressed_size,
680        };
681        self.accum.clear();
682        Ok(())
683    }
684}