lzma_rust2/lzip/
writer.rs

1use core::num::NonZeroU64;
2
3use super::{
4    encode_dict_size, CRC32, HEADER_SIZE, LZIP_MAGIC, LZIP_VERSION, MAX_DICT_SIZE, MIN_DICT_SIZE,
5    TRAILER_SIZE,
6};
7use crate::{
8    enc::{LzmaOptions, LzmaWriter},
9    error_invalid_data, AutoFinish, AutoFinisher, ByteWriter, CountingWriter, Result, Write,
10};
11
12/// Options for LZIP compression.
13#[derive(Default, Debug, Clone)]
14pub struct LzipOptions {
15    /// LZMA compression options (will be overridden partially to use LZMA-302eos defaults).
16    pub lzma_options: LzmaOptions,
17    /// The maximal size of a member. If not set, the whole data will be written in one member.
18    /// Will get clamped to be at least the dict size to not waste memory.
19    pub member_size: Option<NonZeroU64>,
20}
21
22impl LzipOptions {
23    /// Create options with specific preset.
24    pub fn with_preset(preset: u32) -> Self {
25        Self {
26            lzma_options: LzmaOptions::with_preset(preset),
27            member_size: None,
28        }
29    }
30
31    /// Set the maximum member size (None means a single member, which is the default).
32    pub fn set_member_size(&mut self, member_size: Option<NonZeroU64>) {
33        self.member_size = member_size;
34    }
35}
36
37/// A single-threaded LZIP compressor.
38pub struct LzipWriter<W: Write> {
39    inner: Option<W>,
40    lzma_writer: Option<LzmaWriter<CountingWriter<W>>>,
41    options: LzipOptions,
42    header_written: bool,
43    finished: bool,
44    crc_digest: crc::Digest<'static, u32, crc::Table<16>>,
45    uncompressed_size: u64,
46    member_start_pos: u64,
47    current_member_uncompressed_size: u64,
48}
49
50impl<W: Write> LzipWriter<W> {
51    /// Create a new LZIP writer with the given options.
52    pub fn new(inner: W, options: LzipOptions) -> Self {
53        let mut options = options;
54
55        // Overwrite with LZMA-302eos defaults.
56        options.lzma_options.lc = 3;
57        options.lzma_options.lp = 0;
58        options.lzma_options.pb = 2;
59        options.lzma_options.dict_size = options
60            .lzma_options
61            .dict_size
62            .clamp(MIN_DICT_SIZE, MAX_DICT_SIZE);
63
64        if let Some(member_size) = options.member_size.as_mut() {
65            *member_size =
66                NonZeroU64::new(member_size.get().max(options.lzma_options.dict_size as u64))
67                    .expect("member size is zero");
68        }
69
70        Self {
71            inner: Some(inner),
72            lzma_writer: None,
73            options,
74            header_written: false,
75            finished: false,
76            crc_digest: CRC32.digest(),
77            uncompressed_size: 0,
78            member_start_pos: 0,
79            current_member_uncompressed_size: 0,
80        }
81    }
82
83    /// Returns a wrapper around `self` that will finish the stream on drop.
84    pub fn auto_finish(self) -> AutoFinisher<Self> {
85        AutoFinisher(Some(self))
86    }
87
88    /// Consume the writer and return the inner writer.
89    pub fn into_inner(mut self) -> W {
90        if let Some(lzma_writer) = self.lzma_writer.take() {
91            return lzma_writer.into_inner().into_inner();
92        }
93
94        self.inner.take().expect("inner writer not set")
95    }
96
97    /// Returns a reference to the inner writer.
98    pub fn inner(&self) -> &W {
99        self.lzma_writer
100            .as_ref()
101            .map(|reader| reader.inner().inner())
102            .unwrap_or_else(|| self.inner.as_ref().expect("inner writer not set"))
103    }
104
105    /// Returns a mutable reference to the inner writer.
106    pub fn inner_mut(&mut self) -> &mut W {
107        self.lzma_writer
108            .as_mut()
109            .map(|reader| reader.inner_mut().inner_mut())
110            .unwrap_or_else(|| self.inner.as_mut().expect("inner writer not set"))
111    }
112
113    /// Check if we should finish the current member and start a new one.
114    fn should_finish_member(&self) -> bool {
115        if let Some(member_size) = self.options.member_size {
116            self.current_member_uncompressed_size >= member_size.get()
117        } else {
118            false
119        }
120    }
121
122    /// Start a new LZIP member.
123    fn start_new_member(&mut self) -> Result<()> {
124        let mut writer = self.inner.take().expect("inner writer not set");
125
126        self.member_start_pos = 0;
127
128        writer.write_all(&LZIP_MAGIC)?;
129        writer.write_all(&[LZIP_VERSION])?;
130
131        let dict_size_byte = encode_dict_size(self.options.lzma_options.dict_size)?;
132        writer.write_u8(dict_size_byte)?;
133
134        let counting_writer = CountingWriter::new(writer);
135
136        let lzma_writer =
137            LzmaWriter::new_no_header(counting_writer, &self.options.lzma_options, true)?;
138
139        self.lzma_writer = Some(lzma_writer);
140        self.header_written = true;
141        self.current_member_uncompressed_size = 0;
142        self.crc_digest = CRC32.digest();
143        self.uncompressed_size = 0;
144
145        Ok(())
146    }
147
148    fn write_header(&mut self) -> Result<()> {
149        if self.header_written {
150            return Ok(());
151        }
152
153        self.start_new_member()
154    }
155
156    /// Finish the current member by writing its trailer.
157    fn finish_current_member(&mut self) -> Result<()> {
158        let lzma_writer = self.lzma_writer.take().expect("lzma writer not set");
159
160        let counting_writer = lzma_writer.finish()?;
161        let compressed_size = counting_writer.bytes_written();
162        let mut writer = counting_writer.into_inner();
163
164        // Calculate member size: header + compressed data + trailer.
165        let member_size = HEADER_SIZE as u64 + compressed_size + TRAILER_SIZE as u64;
166
167        let crc_digest = core::mem::replace(&mut self.crc_digest, CRC32.digest());
168        let computed_crc = crc_digest.finalize();
169        writer.write_u32(computed_crc)?;
170        writer.write_u64(self.uncompressed_size)?;
171        writer.write_u64(member_size)?;
172
173        self.inner = Some(writer);
174        self.header_written = false;
175
176        Ok(())
177    }
178
179    /// Finish writing the LZIP stream and return the inner writer.
180    pub fn finish(mut self) -> Result<W> {
181        if self.finished {
182            return Ok(self.into_inner());
183        }
184
185        if !self.header_written {
186            self.write_header()?;
187        }
188
189        self.finish_current_member()?;
190        self.finished = true;
191
192        Ok(self.into_inner())
193    }
194}
195
196impl<W: Write> Write for LzipWriter<W> {
197    fn write(&mut self, buf: &[u8]) -> Result<usize> {
198        if self.finished {
199            return Err(error_invalid_data("LZIP writer already finished"));
200        }
201
202        if buf.is_empty() {
203            return Ok(0);
204        }
205
206        let mut total_written = 0;
207        let mut remaining = buf;
208
209        while !remaining.is_empty() {
210            if self.should_finish_member() && self.header_written {
211                self.finish_current_member()?;
212            }
213
214            if !self.header_written {
215                self.start_new_member()?;
216            }
217
218            let lzma_writer = self.lzma_writer.as_mut().expect("lzma writer not set");
219
220            let bytes_to_write = if let Some(member_size) = self.options.member_size {
221                let remaining_in_member = member_size
222                    .get()
223                    .saturating_sub(self.current_member_uncompressed_size);
224                (remaining.len() as u64).min(remaining_in_member) as usize
225            } else {
226                remaining.len()
227            };
228
229            if bytes_to_write == 0 {
230                self.finish_current_member()?;
231                continue;
232            }
233
234            let bytes_written = lzma_writer.write(&remaining[..bytes_to_write])?;
235
236            if bytes_written > 0 {
237                self.crc_digest.update(&remaining[..bytes_written]);
238                self.uncompressed_size += bytes_written as u64;
239                self.current_member_uncompressed_size += bytes_written as u64;
240                total_written += bytes_written;
241                remaining = &remaining[bytes_written..];
242            } else {
243                break;
244            }
245        }
246
247        Ok(total_written)
248    }
249
250    fn flush(&mut self) -> Result<()> {
251        if let Some(ref mut lzma_writer) = self.lzma_writer {
252            lzma_writer.flush()?;
253        }
254        Ok(())
255    }
256}
257
258impl<W: Write> AutoFinish for LzipWriter<W> {
259    fn finish_ignore_error(self) {
260        let _ = self.finish();
261    }
262}