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#[derive(Default, Debug, Clone)]
187pub struct XzOptions {
188 pub lzma_options: LzmaOptions,
190 pub check_type: CheckType,
192 pub block_size: Option<NonZeroU64>,
195 pub filters: Vec<FilterConfig>,
197}
198
199impl XzOptions {
200 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 pub fn set_check_sum_type(&mut self, check_type: CheckType) {
212 self.check_type = check_type;
213 }
214
215 pub fn set_block_size(&mut self, block_size: Option<NonZeroU64>) {
217 self.block_size = block_size;
218 }
219
220 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
232pub 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 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 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 pub fn auto_finish(self) -> AutoFinisher<Self> {
290 AutoFinisher(Some(self))
291 }
292
293 pub fn into_inner(self) -> W {
295 self.writer.into_inner()
296 }
297
298 pub fn inner(&self) -> &W {
300 self.writer.inner()
301 }
302
303 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 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 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 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 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 if self.should_finish_block() {
453 self.finish_current_block()?;
454 }
455
456 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 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}