lzma_rust2/lzip/
reader.rs

1use alloc::vec::Vec;
2
3use super::{LzipHeader, LzipTrailer, CRC32, HEADER_SIZE, TRAILER_SIZE};
4use crate::{error_invalid_data, error_invalid_input, CountingReader, LzmaReader, Read, Result};
5
6/// A single-threaded LZIP decompressor.
7pub struct LzipReader<R> {
8    inner: Option<R>,
9    lzma_reader: Option<LzmaReader<CountingReader<R>>>,
10    current_header: Option<LzipHeader>,
11    finished: bool,
12    trailer_buf: Vec<u8>,
13    crc_digest: Option<crc::Digest<'static, u32, crc::Table<16>>>,
14    data_size: u64,
15}
16
17impl<R> LzipReader<R> {
18    /// Consume the LzipReader and return the inner reader.
19    pub fn into_inner(mut self) -> R {
20        if let Some(lzma_reader) = self.lzma_reader.take() {
21            return lzma_reader.into_inner().inner;
22        }
23
24        self.inner.take().expect("inner reader not set")
25    }
26
27    /// Returns a reference to the inner reader.
28    pub fn inner(&self) -> &R {
29        self.lzma_reader
30            .as_ref()
31            .map(|reader| reader.inner().inner())
32            .unwrap_or_else(|| self.inner.as_ref().expect("inner reader not set"))
33    }
34
35    /// Returns a mutable reference to the inner reader.
36    pub fn inner_mut(&mut self) -> &mut R {
37        self.lzma_reader
38            .as_mut()
39            .map(|reader| reader.inner_mut().inner_mut())
40            .unwrap_or_else(|| self.inner.as_mut().expect("inner reader not set"))
41    }
42}
43
44impl<R: Read> LzipReader<R> {
45    /// Create a new LZIP reader.
46    pub fn new(inner: R) -> Self {
47        Self {
48            inner: Some(inner),
49            lzma_reader: None,
50            current_header: None,
51            finished: false,
52            trailer_buf: Vec::with_capacity(TRAILER_SIZE),
53            crc_digest: None,
54            data_size: 0,
55        }
56    }
57
58    /// Start processing the next LZIP member.
59    /// Returns Ok(true) if a new member was started, Ok(false) if EOF was reached.
60    fn start_next_member(&mut self) -> Result<bool> {
61        let mut reader = self.inner.take().expect("inner reader not set");
62
63        let header = match LzipHeader::parse(&mut reader) {
64            Ok(header) => header,
65            Err(_) => {
66                // If header parsing fails, we've probably reached EOF:
67                // Put the reader back and indicate we're done:
68                self.inner = Some(reader);
69                return Ok(false);
70            }
71        };
72
73        if header.version != 1 {
74            return Err(error_invalid_input("unsupported LZIP version"));
75        }
76
77        let counting_reader = CountingReader::new(reader);
78
79        // Create LZMA reader with LZMA-302eos properties:
80        // - lc=3 (literal context bits)
81        // - lp=0 (literal position bits)
82        // - pb=2 (position bits)
83        // - Unlimited uncompressed size (we'll use trailer to verify)
84        let lzma_reader =
85            LzmaReader::new(counting_reader, u64::MAX, 3, 0, 2, header.dict_size, None)?;
86
87        self.current_header = Some(header);
88        self.lzma_reader = Some(lzma_reader);
89        self.trailer_buf.clear();
90        self.crc_digest = Some(CRC32.digest());
91        self.data_size = 0;
92
93        Ok(true)
94    }
95
96    fn finish_current_member(&mut self) -> Result<()> {
97        let lzma_reader = self.lzma_reader.take().expect("lzma reader not set");
98
99        let counting_reader = lzma_reader.into_inner();
100        let compressed_bytes = counting_reader.bytes_read();
101
102        let mut inner_reader = counting_reader.inner;
103        let trailer = LzipTrailer::parse(&mut inner_reader)?;
104
105        let computed_crc = self.crc_digest.take().expect("no CRC digest").finalize();
106
107        if computed_crc != trailer.crc32 {
108            self.inner = Some(inner_reader);
109            return Err(error_invalid_data("LZIP CRC32 mismatch"));
110        }
111
112        if self.data_size != trailer.data_size {
113            self.inner = Some(inner_reader);
114            return Err(error_invalid_data("LZIP data size mismatch"));
115        }
116
117        let actual_member_size = HEADER_SIZE as u64 + compressed_bytes + TRAILER_SIZE as u64;
118        if actual_member_size != trailer.member_size {
119            self.inner = Some(inner_reader);
120            return Err(error_invalid_data("LZIP member size mismatch"));
121        }
122
123        // Store the reader for potential next member.
124        self.inner = Some(inner_reader);
125
126        Ok(())
127    }
128}
129
130impl<R: Read> Read for LzipReader<R> {
131    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
132        if buf.is_empty() {
133            return Ok(0);
134        }
135
136        loop {
137            // If we have an active LZMA reader, try to read from it.
138            if let Some(ref mut lzma_reader) = self.lzma_reader {
139                match lzma_reader.read(buf) {
140                    Ok(0) => {
141                        // Current member is finished, verify trailer.
142                        self.finish_current_member()?;
143
144                        if !self.start_next_member()? {
145                            // No more members, we're done.
146                            self.finished = true;
147                            return Ok(0);
148                        }
149
150                        // Continue to read from the new member.
151                        continue;
152                    }
153                    Ok(bytes_read) => {
154                        // Update CRC with the decompressed data
155                        if let Some(ref mut crc_digest) = self.crc_digest {
156                            crc_digest.update(&buf[..bytes_read]);
157                            self.data_size += bytes_read as u64;
158                        }
159                        return Ok(bytes_read);
160                    }
161                    Err(e) => {
162                        return Err(e);
163                    }
164                }
165            } else if self.finished {
166                // Already finished, return EOF.
167                return Ok(0);
168            } else {
169                // No active LZMA reader, start the first/next member.
170                if !self.start_next_member()? {
171                    // No members found, we're done.
172                    self.finished = true;
173                    return Ok(0);
174                }
175            }
176        }
177    }
178}