Skip to main content

lzma_rust2/xz/
writer.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::num::NonZeroU64;
3
4use super::{
5    CheckType, ChecksumCalculator, FilterConfig, FilterType, IndexRecord, add_padding,
6    write_xz_block_header, write_xz_index, write_xz_stream_footer, write_xz_stream_header,
7};
8use crate::{
9    AutoFinish, AutoFinisher, CountingWriter, Lzma2Options, Result, Write,
10    enc::{Lzma2Writer, LzmaOptions},
11    error_invalid_data, error_invalid_input,
12    filter::{bcj::BcjWriter, delta::DeltaWriter},
13};
14
15#[allow(clippy::large_enum_variant)]
16enum FilterWriter<W: Write> {
17    Counting(CountingWriter<W>),
18    Lzma2(Lzma2Writer<Box<FilterWriter<W>>>),
19    Delta(DeltaWriter<Box<FilterWriter<W>>>),
20    Bcj(BcjWriter<Box<FilterWriter<W>>>),
21    Dummy,
22}
23
24impl<W: Write> Write for FilterWriter<W> {
25    fn write(&mut self, buf: &[u8]) -> Result<usize> {
26        match self {
27            FilterWriter::Counting(writer) => writer.write(buf),
28            FilterWriter::Lzma2(writer) => writer.write(buf),
29            FilterWriter::Delta(writer) => writer.write(buf),
30            FilterWriter::Bcj(writer) => writer.write(buf),
31            FilterWriter::Dummy => unimplemented!(),
32        }
33    }
34
35    fn flush(&mut self) -> Result<()> {
36        match self {
37            FilterWriter::Counting(writer) => writer.flush(),
38            FilterWriter::Lzma2(writer) => writer.flush(),
39            FilterWriter::Delta(writer) => writer.flush(),
40            FilterWriter::Bcj(writer) => writer.flush(),
41            FilterWriter::Dummy => unimplemented!(),
42        }
43    }
44}
45
46impl<W: Write> FilterWriter<W> {
47    fn create_filter_chain(
48        inner: CountingWriter<W>,
49        filters: &[FilterConfig],
50        lzma_options: &LzmaOptions,
51    ) -> Result<Self> {
52        let mut chain_writer = FilterWriter::Counting(inner);
53
54        for filter_config in filters.iter().rev() {
55            chain_writer = match filter_config.filter_type {
56                FilterType::Delta => {
57                    let distance = filter_config.property as usize;
58                    FilterWriter::Delta(DeltaWriter::new(Box::new(chain_writer), distance))
59                }
60                FilterType::BcjX86 => {
61                    let start_offset = filter_config.property as usize;
62                    FilterWriter::Bcj(BcjWriter::new_x86(Box::new(chain_writer), start_offset))
63                }
64                FilterType::BcjPpc => {
65                    let start_offset = filter_config.property as usize;
66                    FilterWriter::Bcj(BcjWriter::new_ppc(Box::new(chain_writer), start_offset))
67                }
68                FilterType::BcjIa64 => {
69                    let start_offset = filter_config.property as usize;
70                    FilterWriter::Bcj(BcjWriter::new_ia64(Box::new(chain_writer), start_offset))
71                }
72                FilterType::BcjArm => {
73                    let start_offset = filter_config.property as usize;
74                    FilterWriter::Bcj(BcjWriter::new_arm(Box::new(chain_writer), start_offset))
75                }
76                FilterType::BcjArmThumb => {
77                    let start_offset = filter_config.property as usize;
78                    FilterWriter::Bcj(BcjWriter::new_arm_thumb(
79                        Box::new(chain_writer),
80                        start_offset,
81                    ))
82                }
83                FilterType::BcjSparc => {
84                    let start_offset = filter_config.property as usize;
85                    FilterWriter::Bcj(BcjWriter::new_sparc(Box::new(chain_writer), start_offset))
86                }
87                FilterType::BcjArm64 => {
88                    let start_offset = filter_config.property as usize;
89                    FilterWriter::Bcj(BcjWriter::new_arm64(Box::new(chain_writer), start_offset))
90                }
91                FilterType::BcjRiscv => {
92                    let start_offset = filter_config.property as usize;
93                    FilterWriter::Bcj(BcjWriter::new_riscv(Box::new(chain_writer), start_offset))
94                }
95                FilterType::Lzma2 => {
96                    let options = Lzma2Options {
97                        lzma_options: lzma_options.clone(),
98                        ..Default::default()
99                    };
100                    FilterWriter::Lzma2(Lzma2Writer::new(Box::new(chain_writer), options))
101                }
102            };
103        }
104
105        Ok(chain_writer)
106    }
107
108    fn into_inner(self) -> W {
109        match self {
110            FilterWriter::Counting(writer) => writer.inner,
111            FilterWriter::Lzma2(writer) => {
112                let filter_writer = writer.into_inner();
113                filter_writer.into_inner()
114            }
115            FilterWriter::Delta(writer) => {
116                let filter_writer = writer.into_inner();
117                filter_writer.into_inner()
118            }
119            FilterWriter::Bcj(writer) => {
120                let filter_writer = writer.into_inner();
121                filter_writer.into_inner()
122            }
123            FilterWriter::Dummy => unimplemented!(),
124        }
125    }
126
127    fn inner(&self) -> &W {
128        match self {
129            FilterWriter::Counting(writer) => &writer.inner,
130            FilterWriter::Lzma2(writer) => {
131                let filter_writer = writer.inner();
132                filter_writer.inner()
133            }
134            FilterWriter::Delta(writer) => {
135                let filter_writer = writer.inner();
136                filter_writer.inner()
137            }
138            FilterWriter::Bcj(writer) => {
139                let filter_writer = writer.inner();
140                filter_writer.inner()
141            }
142            FilterWriter::Dummy => unimplemented!(),
143        }
144    }
145
146    fn inner_mut(&mut self) -> &mut W {
147        match self {
148            FilterWriter::Counting(writer) => &mut writer.inner,
149            FilterWriter::Lzma2(writer) => {
150                let filter_writer = writer.inner_mut();
151                filter_writer.inner_mut()
152            }
153            FilterWriter::Delta(writer) => {
154                let filter_writer = writer.inner_mut();
155                filter_writer.inner_mut()
156            }
157            FilterWriter::Bcj(writer) => {
158                let filter_writer = writer.inner_mut();
159                filter_writer.inner_mut()
160            }
161            FilterWriter::Dummy => unimplemented!(),
162        }
163    }
164
165    fn finish(self) -> Result<CountingWriter<W>> {
166        match self {
167            FilterWriter::Counting(writer) => Ok(writer),
168            FilterWriter::Lzma2(writer) => {
169                let inner_writer = writer.finish()?;
170                inner_writer.finish()
171            }
172            FilterWriter::Delta(writer) => {
173                let inner_writer = writer.into_inner();
174                inner_writer.finish()
175            }
176            FilterWriter::Bcj(writer) => {
177                let inner_writer = writer.finish()?;
178                inner_writer.finish()
179            }
180            FilterWriter::Dummy => unimplemented!(),
181        }
182    }
183}
184
185/// Configuration options for XZ compression.
186#[derive(Default, Debug, Clone)]
187pub struct XzOptions {
188    /// LZMA compression options.
189    pub lzma_options: LzmaOptions,
190    /// Checksum type to use.
191    pub check_type: CheckType,
192    /// Maximum uncompressed size for each block (None = single block).
193    /// Will get clamped to be at least the dict size to not waste memory.
194    pub block_size: Option<NonZeroU64>,
195    /// Pre-filter to use (at most 3).
196    pub filters: Vec<FilterConfig>,
197}
198
199impl XzOptions {
200    /// Create options with specific preset and checksum type.
201    pub fn with_preset(preset: u32) -> Self {
202        Self {
203            lzma_options: LzmaOptions::with_preset(preset),
204            check_type: CheckType::Crc64,
205            block_size: None,
206            filters: Vec::new(),
207        }
208    }
209
210    /// Set the checksum type to use (Default is CRC64).
211    pub fn set_check_sum_type(&mut self, check_type: CheckType) {
212        self.check_type = check_type;
213    }
214
215    /// Set the maximum block size (None means a single block, which is the default).
216    pub fn set_block_size(&mut self, block_size: Option<NonZeroU64>) {
217        self.block_size = block_size;
218    }
219
220    /// Prepend a filter to the chain. You can prepend at most 3 additional filter.
221    pub fn prepend_pre_filter(&mut self, filter_type: FilterType, property: u32) {
222        self.filters.insert(
223            0,
224            FilterConfig {
225                filter_type,
226                property,
227            },
228        );
229    }
230}
231
232/// A single-threaded XZ compressor.
233pub struct XzWriter<W: Write> {
234    writer: FilterWriter<W>,
235    options: XzOptions,
236    index_records: Vec<IndexRecord>,
237    block_uncompressed_size: u64,
238    checksum_calculator: ChecksumCalculator,
239    header_written: bool,
240    finished: bool,
241    total_uncompressed_pos: u64,
242    current_block_start_pos: u64,
243    current_block_header_size: u64,
244    current_block_open: bool,
245}
246
247impl<W: Write> XzWriter<W> {
248    /// Create a new XZ writer with the given options.
249    pub fn new(inner: W, options: XzOptions) -> Result<Self> {
250        let mut options = options;
251
252        if options.filters.len() > 3 {
253            return Err(error_invalid_input(
254                "XZ allows only at most 3 pre-filters plus LZMA2",
255            ));
256        }
257
258        if let Some(block_size) = options.block_size.as_mut() {
259            *block_size =
260                NonZeroU64::new(block_size.get().max(options.lzma_options.dict_size as u64))
261                    .expect("block size is zero");
262        }
263
264        // Last filter is always LZMA2.
265        options.filters.push(FilterConfig {
266            filter_type: FilterType::Lzma2,
267            property: 0,
268        });
269
270        let checksum_calculator = ChecksumCalculator::new(options.check_type);
271        let writer = FilterWriter::Counting(CountingWriter::new(inner));
272
273        Ok(Self {
274            writer,
275            options,
276            index_records: Vec::new(),
277            block_uncompressed_size: 0,
278            checksum_calculator,
279            header_written: false,
280            finished: false,
281            total_uncompressed_pos: 0,
282            current_block_start_pos: 0,
283            current_block_header_size: 0,
284            current_block_open: false,
285        })
286    }
287
288    /// Returns a wrapper around `self` that will finish the stream on drop.
289    pub fn auto_finish(self) -> AutoFinisher<Self> {
290        AutoFinisher(Some(self))
291    }
292
293    /// Consume the XzWriter and return the inner writer.
294    pub fn into_inner(self) -> W {
295        self.writer.into_inner()
296    }
297
298    /// Returns a reference to the inner writer.
299    pub fn inner(&self) -> &W {
300        self.writer.inner()
301    }
302
303    /// Returns a mutable reference to the inner writer.
304    pub fn inner_mut(&mut self) -> &mut W {
305        self.writer.inner_mut()
306    }
307
308    fn write_stream_header(&mut self) -> Result<()> {
309        if self.header_written {
310            return Ok(());
311        }
312
313        write_xz_stream_header(&mut self.writer, self.options.check_type)?;
314
315        self.header_written = true;
316
317        Ok(())
318    }
319
320    fn prepare_next_block(&mut self) -> Result<()> {
321        let writer = core::mem::replace(&mut self.writer, FilterWriter::Dummy);
322        let counting_writer = writer.finish()?;
323        self.writer = FilterWriter::Counting(counting_writer);
324
325        self.current_block_header_size = write_xz_block_header(
326            &mut self.writer,
327            &self.options.filters,
328            self.options.lzma_options.dict_size,
329        )?;
330
331        let writer = core::mem::replace(&mut self.writer, FilterWriter::Dummy);
332        let counting_writer = writer.finish()?;
333        let bytes_written = counting_writer.bytes_written();
334        self.current_block_start_pos = bytes_written;
335
336        self.writer = FilterWriter::create_filter_chain(
337            counting_writer,
338            &self.options.filters,
339            &self.options.lzma_options,
340        )?;
341
342        self.block_uncompressed_size = 0;
343        self.current_block_open = true;
344
345        Ok(())
346    }
347
348    fn should_finish_block(&self) -> bool {
349        if let Some(block_size) = self.options.block_size {
350            self.block_uncompressed_size >= block_size.get()
351        } else {
352            false
353        }
354    }
355
356    fn finish_current_block(&mut self) -> Result<()> {
357        if !self.current_block_open {
358            return Ok(());
359        }
360
361        // Finish the filter chain and get back to the counting writer.
362        let writer = core::mem::replace(&mut self.writer, FilterWriter::Dummy);
363        let counting_writer = writer.finish()?;
364        let bytes_written = counting_writer.bytes_written();
365        self.writer = FilterWriter::Counting(counting_writer);
366
367        let block_compressed_size = bytes_written - self.current_block_start_pos;
368
369        let data_size = block_compressed_size;
370        let padding_needed = (4 - (data_size % 4)) % 4;
371
372        add_padding(&mut self.writer, padding_needed as usize)?;
373
374        self.write_block_checksum()?;
375
376        let unpadded_size = self.current_block_header_size
377            + block_compressed_size
378            + self.options.check_type.checksum_size();
379
380        self.index_records.push(IndexRecord {
381            unpadded_size,
382            uncompressed_size: self.block_uncompressed_size,
383        });
384
385        self.block_uncompressed_size = 0;
386        self.current_block_start_pos = 0;
387        self.current_block_header_size = 0;
388        self.current_block_open = false;
389
390        Ok(())
391    }
392
393    fn get_block_header_size(&self, _compressed_size: u64, _uncompressed_size: u64) -> u64 {
394        // Block header: size_byte(1) + flags(1) + filter_id(1) + props_size(1)
395        // + dict_prop(1) + padding + crc32(4)
396        let base_size: u64 = 9;
397        base_size.div_ceil(4) * 4
398    }
399
400    fn write_block_checksum(&mut self) -> Result<()> {
401        let checksum = self.take_checksum();
402        self.writer.write_all(&checksum)?;
403
404        // Reset checksum calculator for next block.
405        self.checksum_calculator = ChecksumCalculator::new(self.options.check_type);
406
407        Ok(())
408    }
409
410    fn take_checksum(&mut self) -> Vec<u8> {
411        let calculator = core::mem::replace(
412            &mut self.checksum_calculator,
413            ChecksumCalculator::new(self.options.check_type),
414        );
415        calculator.finalize_to_bytes()
416    }
417
418    /// Finish writing the XZ stream and return the inner writer.
419    pub fn finish(mut self) -> Result<W> {
420        if self.finished {
421            return Ok(self.into_inner());
422        }
423
424        self.write_stream_header()?;
425        self.finish_current_block()?;
426
427        write_xz_index(&mut self.writer, &self.index_records)?;
428
429        write_xz_stream_footer(
430            &mut self.writer,
431            &self.index_records,
432            self.options.check_type,
433        )?;
434
435        Ok(self.into_inner())
436    }
437}
438
439impl<W: Write> Write for XzWriter<W> {
440    fn write(&mut self, buf: &[u8]) -> Result<usize> {
441        if self.finished {
442            return Err(error_invalid_data("XzWriter already finished"));
443        }
444
445        self.write_stream_header()?;
446
447        let mut total_written = 0;
448        let mut remaining = buf;
449
450        while !remaining.is_empty() {
451            // Check if we need to start a new block.
452            if self.should_finish_block() {
453                self.finish_current_block()?;
454            }
455
456            // Check if we need to prepare the next block (either first block or after finishing one).
457            if self.block_uncompressed_size == 0 {
458                self.prepare_next_block()?;
459            }
460
461            let max_write_size = match self.options.block_size {
462                Some(block_size) => {
463                    let remaining_capacity = block_size
464                        .get()
465                        .saturating_sub(self.block_uncompressed_size);
466                    remaining.len().min(remaining_capacity as usize)
467                }
468                None => remaining.len(),
469            };
470
471            if max_write_size == 0 {
472                // Block is full, finish it and continue.
473                continue;
474            }
475
476            let chunk_to_write = &remaining[..max_write_size];
477            let written = self.writer.write(chunk_to_write)?;
478
479            self.checksum_calculator.update(&remaining[..written]);
480
481            remaining = &remaining[written..];
482            total_written += written;
483            self.block_uncompressed_size += written as u64;
484            self.total_uncompressed_pos += written as u64;
485        }
486
487        Ok(total_written)
488    }
489
490    fn flush(&mut self) -> Result<()> {
491        self.writer.flush()
492    }
493}
494
495impl<W: Write> AutoFinish for XzWriter<W> {
496    fn finish_ignore_error(self) {
497        let _ = self.finish();
498    }
499}