Skip to main content

lzma_rust2/
xz.rs

1//! XZ format decoder and encoder implementation.
2
3mod reader;
4#[cfg(feature = "std")]
5mod reader_mt;
6#[cfg(feature = "encoder")]
7mod writer;
8#[cfg(all(feature = "encoder", feature = "std"))]
9mod writer_mt;
10
11use alloc::{boxed::Box, vec, vec::Vec};
12#[cfg(feature = "std")]
13use std::io::{self, Seek, SeekFrom};
14
15pub use reader::{XzReader, XzStream};
16#[cfg(feature = "std")]
17pub use reader_mt::XzReaderMt;
18use sha2::Digest;
19#[cfg(feature = "encoder")]
20pub use writer::{XzOptions, XzWriter};
21#[cfg(all(feature = "encoder", feature = "std"))]
22pub use writer_mt::XzWriterMt;
23
24use crate::{
25    ByteReader, Read,
26    crc::{Crc32, Crc64},
27    error_invalid_data, error_invalid_input,
28};
29#[cfg(feature = "encoder")]
30use crate::{ByteWriter, Write};
31#[cfg(feature = "std")]
32use crate::{
33    Lzma2Reader,
34    filter::{bcj::BcjReader, delta::DeltaReader},
35};
36
37const XZ_MAGIC: [u8; 6] = [0xFD, b'7', b'z', b'X', b'Z', 0x00];
38
39const XZ_FOOTER_MAGIC: [u8; 2] = *b"YZ";
40
41#[derive(Debug, Clone)]
42struct IndexRecord {
43    unpadded_size: u64,
44    uncompressed_size: u64,
45}
46
47#[derive(Debug)]
48struct Index {
49    pub number_of_records: u64,
50    pub records: Vec<IndexRecord>,
51}
52
53#[derive(Debug)]
54struct StreamHeader {
55    pub check_type: CheckType,
56}
57
58#[derive(Debug)]
59struct StreamFooter {
60    pub backward_size: u32,
61    pub stream_flags: [u8; 2],
62}
63
64#[derive(Debug)]
65struct BlockHeader {
66    header_size: usize,
67    compressed_size: Option<u64>,
68    uncompressed_size: Option<u64>,
69    filters: [Option<FilterType>; 4],
70    properties: [u32; 4],
71}
72
73#[derive(Debug, Clone)]
74struct Block {
75    start_pos: u64,
76    unpadded_size: u64,
77    uncompressed_size: u64,
78}
79
80/// Configuration for a filter in the XZ filter chain.
81#[derive(Debug, Clone)]
82pub struct FilterConfig {
83    /// Filter type to use.
84    pub filter_type: FilterType,
85    /// Property to use.
86    pub property: u32,
87}
88
89impl FilterConfig {
90    /// Creates a new delta filter configuration.
91    pub fn new_delta(distance: u32) -> Self {
92        Self {
93            filter_type: FilterType::Delta,
94            property: distance,
95        }
96    }
97
98    /// Creates a new BCJ x86 filter configuration.
99    pub fn new_bcj_x86(start_pos: u32) -> Self {
100        Self {
101            filter_type: FilterType::BcjX86,
102            property: start_pos,
103        }
104    }
105
106    /// Creates a new BCJ ARM filter configuration.
107    pub fn new_bcj_arm(start_pos: u32) -> Self {
108        Self {
109            filter_type: FilterType::BcjArm,
110            property: start_pos,
111        }
112    }
113
114    /// Creates a new BCJ ARM Thumb filter configuration.
115    pub fn new_bcj_arm_thumb(start_pos: u32) -> Self {
116        Self {
117            filter_type: FilterType::BcjArmThumb,
118            property: start_pos,
119        }
120    }
121
122    /// Creates a new BCJ ARM64 filter configuration.
123    pub fn new_bcj_arm64(start_pos: u32) -> Self {
124        Self {
125            filter_type: FilterType::BcjArm64,
126            property: start_pos,
127        }
128    }
129
130    /// Creates a new BCJ IA64 filter configuration.
131    pub fn new_bcj_ia64(start_pos: u32) -> Self {
132        Self {
133            filter_type: FilterType::BcjIa64,
134            property: start_pos,
135        }
136    }
137
138    /// Creates a new BCJ PPC filter configuration.
139    pub fn new_bcj_ppc(start_pos: u32) -> Self {
140        Self {
141            filter_type: FilterType::BcjPpc,
142            property: start_pos,
143        }
144    }
145
146    /// Creates a new BCJ SPARC filter configuration.
147    pub fn new_bcj_sparc(start_pos: u32) -> Self {
148        Self {
149            filter_type: FilterType::BcjSparc,
150            property: start_pos,
151        }
152    }
153
154    /// Creates a new BCJ RISC-V filter configuration.
155    pub fn new_bcj_risc_v(start_pos: u32) -> Self {
156        Self {
157            filter_type: FilterType::BcjRiscv,
158            property: start_pos,
159        }
160    }
161}
162
163/// Supported checksum types in XZ format.
164#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
165pub enum CheckType {
166    /// No checksum
167    None = 0x00,
168    /// CRC32
169    Crc32 = 0x01,
170    /// CRC64
171    #[default]
172    Crc64 = 0x04,
173    /// SHA-256
174    Sha256 = 0x0A,
175}
176
177impl CheckType {
178    fn from_byte(byte: u8) -> crate::Result<Self> {
179        match byte {
180            0x00 => Ok(CheckType::None),
181            0x01 => Ok(CheckType::Crc32),
182            0x04 => Ok(CheckType::Crc64),
183            0x0A => Ok(CheckType::Sha256),
184            _ => Err(error_invalid_data("unsupported XZ check type")),
185        }
186    }
187
188    #[cfg(any(feature = "encoder", feature = "xz"))]
189    fn checksum_size(self) -> u64 {
190        match self {
191            CheckType::None => 0,
192            CheckType::Crc32 => 4,
193            CheckType::Crc64 => 8,
194            CheckType::Sha256 => 32,
195        }
196    }
197}
198
199/// Supported filter types in XZ format.
200#[derive(Debug, Copy, Clone, Eq, PartialEq)]
201pub enum FilterType {
202    /// Delta filter
203    Delta,
204    /// BCJ x86 filter
205    BcjX86,
206    /// BCJ PowerPC filter
207    BcjPpc,
208    /// BCJ IA64 filter
209    BcjIa64,
210    /// BCJ ARM filter
211    BcjArm,
212    /// BCJ ARM Thumb
213    BcjArmThumb,
214    /// BCJ SPARC filter
215    BcjSparc,
216    /// BCJ ARM64 filter
217    BcjArm64,
218    /// BCJ RISC-V filter
219    BcjRiscv,
220    /// LZMA2 filter
221    Lzma2,
222}
223
224impl TryFrom<u64> for FilterType {
225    type Error = ();
226
227    fn try_from(value: u64) -> Result<Self, Self::Error> {
228        match value {
229            0x03 => Ok(FilterType::Delta),
230            0x04 => Ok(FilterType::BcjX86),
231            0x05 => Ok(FilterType::BcjPpc),
232            0x06 => Ok(FilterType::BcjIa64),
233            0x07 => Ok(FilterType::BcjArm),
234            0x08 => Ok(FilterType::BcjArmThumb),
235            0x09 => Ok(FilterType::BcjSparc),
236            0x0A => Ok(FilterType::BcjArm64),
237            0x0B => Ok(FilterType::BcjRiscv),
238            0x21 => Ok(FilterType::Lzma2),
239            _ => Err(()),
240        }
241    }
242}
243
244/// Parse XZ multibyte integer (variable length encoding).
245fn parse_multibyte_integer(data: &[u8]) -> crate::Result<u64> {
246    let mut result = 0u64;
247    let mut shift = 0;
248
249    for &byte in data {
250        if shift >= 63 {
251            return Err(error_invalid_data("XZ multibyte integer too large"));
252        }
253
254        result |= ((byte & 0x7F) as u64) << shift;
255        shift += 7;
256
257        if (byte & 0x80) == 0 {
258            return Ok(result);
259        }
260    }
261
262    Err(error_invalid_data("incomplete XZ multibyte integer"))
263}
264
265/// Count the number of bytes used by a multibyte integer.
266fn count_multibyte_integer_size(data: &[u8]) -> usize {
267    for (i, &byte) in data.iter().enumerate() {
268        if (byte & 0x80) == 0 {
269            return i + 1;
270        }
271    }
272    data.len()
273}
274
275fn parse_multibyte_integer_from_reader<R: Read>(reader: &mut R) -> crate::Result<u64> {
276    let mut result = 0u64;
277    let mut shift = 0;
278
279    for _ in 0..9 {
280        // Max 9 bytes for 63-bit value
281        let byte = reader.read_u8()?;
282
283        if shift >= 63 {
284            return Err(error_invalid_data("XZ multibyte integer too large"));
285        }
286
287        result |= ((byte & 0x7F) as u64) << shift;
288        shift += 7;
289
290        if (byte & 0x80) == 0 {
291            return Ok(result);
292        }
293    }
294
295    Err(error_invalid_data("XZ multibyte integer too long"))
296}
297
298fn count_multibyte_integer_size_for_value(mut value: u64) -> usize {
299    if value == 0 {
300        return 1;
301    }
302
303    let mut count = 0;
304    while value > 0 {
305        count += 1;
306        value >>= 7;
307    }
308    count
309}
310
311fn encode_multibyte_integer(mut value: u64, buf: &mut [u8]) -> crate::Result<usize> {
312    if value > (u64::MAX / 2) {
313        return Err(error_invalid_data("value too big to encode"));
314    }
315
316    let mut i = 0;
317    while value >= 0x80 && i < buf.len() {
318        buf[i] = (value as u8) | 0x80;
319        value >>= 7;
320        i += 1;
321    }
322
323    if i < buf.len() {
324        buf[i] = value as u8;
325        i += 1;
326    }
327
328    Ok(i)
329}
330
331impl BlockHeader {
332    fn parse<R: Read>(reader: &mut R) -> crate::Result<Option<Self>> {
333        let header_size_encoded = reader.read_u8()?;
334
335        if header_size_encoded == 0 {
336            // If header size is 0, this indicates end of blocks (index follows).
337            return Ok(None);
338        }
339
340        let header_size = (header_size_encoded as usize + 1) * 4;
341        if !(8..=1024).contains(&header_size) {
342            return Err(error_invalid_data("invalid XZ block header size"));
343        }
344
345        // -1 because we already read the size byte.
346        let mut header_data = vec![0u8; header_size - 1];
347        reader.read_exact(&mut header_data)?;
348
349        let block_flags = header_data[0];
350        let num_filters = ((block_flags & 0x03) + 1) as usize;
351        let has_compressed_size = (block_flags & 0x40) != 0;
352        let has_uncompressed_size = (block_flags & 0x80) != 0;
353
354        let mut offset = 1;
355        let mut compressed_size = None;
356        let mut uncompressed_size = None;
357
358        // Parse optional compressed size.
359        if has_compressed_size {
360            if offset + 8 > header_data.len() {
361                return Err(error_invalid_data(
362                    "XZ block header too short for compressed size",
363                ));
364            }
365            compressed_size = Some(parse_multibyte_integer(&header_data[offset..])?);
366            offset += count_multibyte_integer_size(&header_data[offset..]);
367        }
368
369        if has_uncompressed_size {
370            if offset >= header_data.len() {
371                return Err(error_invalid_data(
372                    "XZ block header too short for uncompressed size",
373                ));
374            }
375            uncompressed_size = Some(parse_multibyte_integer(&header_data[offset..])?);
376            offset += count_multibyte_integer_size(&header_data[offset..]);
377        }
378
379        let mut filters = [None; 4];
380        let mut properties = [0; 4];
381
382        for i in 0..num_filters {
383            if offset >= header_data.len() {
384                return Err(error_invalid_data("XZ block header too short for filters"));
385            }
386
387            let filter_type =
388                FilterType::try_from(parse_multibyte_integer(&header_data[offset..])?)
389                    .map_err(|_| error_invalid_input("unsupported filter type found"))?;
390
391            offset += count_multibyte_integer_size(&header_data[offset..]);
392
393            let property = match filter_type {
394                FilterType::Delta => {
395                    if offset >= header_data.len() {
396                        return Err(error_invalid_data(
397                            "XZ block header too short for Delta properties",
398                        ));
399                    }
400
401                    let props_size = parse_multibyte_integer(&header_data[offset..])?;
402                    offset += count_multibyte_integer_size(&header_data[offset..]);
403
404                    if props_size != 1 {
405                        return Err(error_invalid_data("invalid Delta properties size"));
406                    }
407
408                    if offset >= header_data.len() {
409                        return Err(error_invalid_data(
410                            "XZ block header too short for Delta properties",
411                        ));
412                    }
413
414                    let distance_prop = header_data[offset];
415                    offset += 1;
416
417                    // Distance is encoded as byte value + 1, range [1, 256].
418                    (distance_prop as u32) + 1
419                }
420                FilterType::BcjX86
421                | FilterType::BcjPpc
422                | FilterType::BcjIa64
423                | FilterType::BcjArm
424                | FilterType::BcjArmThumb
425                | FilterType::BcjSparc
426                | FilterType::BcjArm64
427                | FilterType::BcjRiscv => {
428                    if offset >= header_data.len() {
429                        return Err(error_invalid_data(
430                            "XZ block header too short for BCJ properties",
431                        ));
432                    }
433
434                    let props_size = parse_multibyte_integer(&header_data[offset..])?;
435                    offset += count_multibyte_integer_size(&header_data[offset..]);
436
437                    match props_size {
438                        0 => {
439                            // No start offset specified, use default (0).
440                            0
441                        }
442                        4 => {
443                            // 4-byte start offset specified.
444                            if offset + 4 > header_data.len() {
445                                return Err(error_invalid_data(
446                                    "XZ block header too short for BCJ start offset",
447                                ));
448                            }
449
450                            let start_offset_value = u32::from_le_bytes([
451                                header_data[offset],
452                                header_data[offset + 1],
453                                header_data[offset + 2],
454                                header_data[offset + 3],
455                            ]);
456                            offset += 4;
457
458                            // Validate alignment based on filter type.
459                            let bcj_alignment = match filter_type {
460                                FilterType::BcjX86 => 1,
461                                FilterType::BcjPpc => 4,
462                                FilterType::BcjIa64 => 16,
463                                FilterType::BcjArm => 4,
464                                FilterType::BcjArmThumb => 2,
465                                FilterType::BcjSparc => 4,
466                                FilterType::BcjArm64 => 4,
467                                FilterType::BcjRiscv => 2,
468                                _ => unreachable!(),
469                            };
470
471                            if start_offset_value % bcj_alignment != 0 {
472                                return Err(error_invalid_data(
473                                    "BCJ start offset not aligned to filter requirements",
474                                ));
475                            }
476
477                            start_offset_value
478                        }
479                        _ => {
480                            return Err(error_invalid_data("invalid BCJ properties size"));
481                        }
482                    }
483                }
484                FilterType::Lzma2 => {
485                    if offset >= header_data.len() {
486                        return Err(error_invalid_data(
487                            "XZ block header too short for LZMA2 properties",
488                        ));
489                    }
490
491                    let props_size = parse_multibyte_integer(&header_data[offset..])?;
492                    offset += count_multibyte_integer_size(&header_data[offset..]);
493
494                    if props_size != 1 {
495                        return Err(error_invalid_data("invalid LZMA2 properties size"));
496                    }
497
498                    if offset >= header_data.len() {
499                        return Err(error_invalid_data(
500                            "XZ block header too short for LZMA2 properties",
501                        ));
502                    }
503
504                    let dict_size_prop = header_data[offset];
505                    offset += 1;
506
507                    if dict_size_prop > 40 {
508                        return Err(error_invalid_data("invalid LZMA2 dictionary size"));
509                    }
510
511                    if dict_size_prop == 40 {
512                        0xFFFFFFFF
513                    } else {
514                        let base = 2 | ((dict_size_prop & 1) as u32);
515                        base << (dict_size_prop / 2 + 11)
516                    }
517                }
518            };
519
520            filters[i] = Some(filter_type);
521            properties[i] = property;
522        }
523
524        if filters.iter().filter_map(|x| *x).next_back() != Some(FilterType::Lzma2) {
525            return Err(error_invalid_input(
526                "XZ block's last filter must be a LZMA2 filter",
527            ));
528        }
529
530        // Header must be padded so that the total header size matches the declared size.
531        // We need to pad until: 1 (size byte) + offset + 4 (CRC32) == header_size
532        let expected_offset = header_size - 1 - 4; // header_size - size_byte - crc32_size
533        while offset < expected_offset {
534            if offset >= header_data.len() || header_data[offset] != 0 {
535                return Err(error_invalid_data("invalid XZ block header padding"));
536            }
537            offset += 1;
538        }
539
540        // Last 4 bytes should be CRC32 of the header (excluding the CRC32 itself).
541        if offset + 4 != header_data.len() {
542            return Err(error_invalid_data("invalid XZ block header CRC32 position"));
543        }
544
545        let expected_crc = u32::from_le_bytes([
546            header_data[offset],
547            header_data[offset + 1],
548            header_data[offset + 2],
549            header_data[offset + 3],
550        ]);
551
552        // Calculate CRC32 of header size byte + header data (excluding CRC32).
553        let mut crc = Crc32::new();
554        crc.update(&[header_size_encoded]);
555        crc.update(&header_data[..offset]);
556
557        if expected_crc != crc.finalize() {
558            return Err(error_invalid_data("XZ block header CRC32 mismatch"));
559        }
560
561        Ok(Some(BlockHeader {
562            header_size,
563            compressed_size,
564            uncompressed_size,
565            filters,
566            properties,
567        }))
568    }
569
570    pub fn parse_from_slice(
571        block_data: &[u8],
572    ) -> crate::Result<([Option<FilterType>; 4], [u32; 4], usize)> {
573        if block_data.is_empty() {
574            return Err(error_invalid_data("Empty block data"));
575        }
576
577        let header_size_encoded = block_data[0];
578        if header_size_encoded == 0 {
579            return Err(error_invalid_data("Invalid block header size"));
580        }
581
582        let header_size = (header_size_encoded as usize + 1) * 4;
583        if header_size > block_data.len() {
584            return Err(error_invalid_data("Block data too short for header"));
585        }
586
587        let header_data = &block_data[1..header_size];
588        let block_flags = header_data[0];
589        let num_filters = ((block_flags & 0x03) + 1) as usize;
590        let has_compressed_size = (block_flags & 0x40) != 0;
591        let has_uncompressed_size = (block_flags & 0x80) != 0;
592
593        let mut offset = 1;
594
595        // Skip optional compressed size.
596        if has_compressed_size {
597            if offset >= header_data.len() {
598                return Err(error_invalid_data(
599                    "Block header too short for compressed size",
600                ));
601            }
602            offset += count_multibyte_integer_size(&header_data[offset..]);
603        }
604
605        // Skip optional uncompressed size.
606        if has_uncompressed_size {
607            if offset >= header_data.len() {
608                return Err(error_invalid_data(
609                    "Block header too short for uncompressed size",
610                ));
611            }
612            offset += count_multibyte_integer_size(&header_data[offset..]);
613        }
614
615        let mut filters = [None; 4];
616        let mut properties = [0; 4];
617
618        // Parse filters.
619        for i in 0..num_filters {
620            if offset >= header_data.len() {
621                return Err(error_invalid_data("Block header too short for filters"));
622            }
623
624            let filter_id = parse_multibyte_integer(&header_data[offset..])?;
625            let filter_type = FilterType::try_from(filter_id)
626                .map_err(|_| error_invalid_data("Unsupported filter type"))?;
627
628            offset += count_multibyte_integer_size(&header_data[offset..]);
629
630            let property = match filter_type {
631                FilterType::Delta => {
632                    if offset >= header_data.len() {
633                        return Err(error_invalid_data(
634                            "Block header too short for Delta properties",
635                        ));
636                    }
637
638                    let props_size = parse_multibyte_integer(&header_data[offset..])?;
639                    offset += count_multibyte_integer_size(&header_data[offset..]);
640
641                    if props_size != 1 {
642                        return Err(error_invalid_data("Invalid Delta properties size"));
643                    }
644
645                    if offset >= header_data.len() {
646                        return Err(error_invalid_data(
647                            "Block header too short for Delta properties",
648                        ));
649                    }
650
651                    let distance_prop = header_data[offset];
652                    offset += 1;
653                    (distance_prop as u32) + 1
654                }
655                FilterType::BcjX86
656                | FilterType::BcjPpc
657                | FilterType::BcjIa64
658                | FilterType::BcjArm
659                | FilterType::BcjArmThumb
660                | FilterType::BcjSparc
661                | FilterType::BcjArm64
662                | FilterType::BcjRiscv => {
663                    if offset >= header_data.len() {
664                        return Err(error_invalid_data(
665                            "Block header too short for BCJ properties",
666                        ));
667                    }
668
669                    let props_size = parse_multibyte_integer(&header_data[offset..])?;
670                    offset += count_multibyte_integer_size(&header_data[offset..]);
671
672                    match props_size {
673                        0 => 0,
674                        4 => {
675                            if offset + 4 > header_data.len() {
676                                return Err(error_invalid_data(
677                                    "Block header too short for BCJ start offset",
678                                ));
679                            }
680
681                            let start_offset = u32::from_le_bytes([
682                                header_data[offset],
683                                header_data[offset + 1],
684                                header_data[offset + 2],
685                                header_data[offset + 3],
686                            ]);
687                            offset += 4;
688                            start_offset
689                        }
690                        _ => return Err(error_invalid_data("Invalid BCJ properties size")),
691                    }
692                }
693                FilterType::Lzma2 => {
694                    if offset >= header_data.len() {
695                        return Err(error_invalid_data(
696                            "Block header too short for LZMA2 properties",
697                        ));
698                    }
699
700                    let props_size = parse_multibyte_integer(&header_data[offset..])?;
701                    offset += count_multibyte_integer_size(&header_data[offset..]);
702
703                    if props_size != 1 {
704                        return Err(error_invalid_data("Invalid LZMA2 properties size"));
705                    }
706
707                    if offset >= header_data.len() {
708                        return Err(error_invalid_data(
709                            "Block header too short for LZMA2 properties",
710                        ));
711                    }
712
713                    let dict_size_prop = header_data[offset];
714                    offset += 1;
715
716                    if dict_size_prop > 40 {
717                        return Err(error_invalid_data("Invalid LZMA2 dictionary size"));
718                    }
719
720                    if dict_size_prop == 40 {
721                        0xFFFFFFFF
722                    } else {
723                        let base = 2 | ((dict_size_prop & 1) as u32);
724                        base << (dict_size_prop / 2 + 11)
725                    }
726                }
727            };
728
729            filters[i] = Some(filter_type);
730            properties[i] = property;
731        }
732
733        if filters.iter().filter_map(|x| *x).next_back() != Some(FilterType::Lzma2) {
734            return Err(error_invalid_data(
735                "XZ block's last filter must be a LZMA2 filter",
736            ));
737        }
738
739        Ok((filters, properties, header_size))
740    }
741}
742
743/// Handles checksum calculation for different XZ check types.
744enum ChecksumCalculator {
745    None,
746    Crc32(Crc32),
747    Crc64(Crc64),
748    Sha256(sha2::Sha256),
749}
750
751impl ChecksumCalculator {
752    fn new(check_type: CheckType) -> Self {
753        match check_type {
754            CheckType::None => Self::None,
755            CheckType::Crc32 => Self::Crc32(Crc32::new()),
756            CheckType::Crc64 => Self::Crc64(Crc64::new()),
757            CheckType::Sha256 => Self::Sha256(sha2::Sha256::new()),
758        }
759    }
760
761    fn update(&mut self, data: &[u8]) {
762        match self {
763            ChecksumCalculator::None => {}
764            ChecksumCalculator::Crc32(crc) => {
765                crc.update(data);
766            }
767            ChecksumCalculator::Crc64(crc) => {
768                crc.update(data);
769            }
770            ChecksumCalculator::Sha256(sha) => {
771                sha.update(data);
772            }
773        }
774    }
775
776    fn verify(self, expected: &[u8]) -> bool {
777        match self {
778            ChecksumCalculator::None => true,
779            ChecksumCalculator::Crc32(crc) => {
780                if expected.len() != 4 {
781                    return false;
782                }
783
784                let expected_crc =
785                    u32::from_le_bytes([expected[0], expected[1], expected[2], expected[3]]);
786
787                let final_crc = crc.finalize();
788
789                final_crc == expected_crc
790            }
791            ChecksumCalculator::Crc64(crc) => {
792                if expected.len() != 8 {
793                    return false;
794                }
795
796                let expected_crc = u64::from_le_bytes([
797                    expected[0],
798                    expected[1],
799                    expected[2],
800                    expected[3],
801                    expected[4],
802                    expected[5],
803                    expected[6],
804                    expected[7],
805                ]);
806
807                let final_crc = crc.finalize();
808
809                final_crc == expected_crc
810            }
811            ChecksumCalculator::Sha256(sha) => {
812                if expected.len() != 32 {
813                    return false;
814                }
815
816                let final_sha = sha.finalize();
817
818                &final_sha[..32] == expected
819            }
820        }
821    }
822
823    #[cfg(feature = "encoder")]
824    fn finalize_to_bytes(self) -> Vec<u8> {
825        match self {
826            ChecksumCalculator::None => Vec::new(),
827            ChecksumCalculator::Crc32(crc) => crc.finalize().to_le_bytes().to_vec(),
828            ChecksumCalculator::Crc64(crc) => crc.finalize().to_le_bytes().to_vec(),
829            ChecksumCalculator::Sha256(sha) => sha.finalize().to_vec(),
830        }
831    }
832}
833
834impl StreamHeader {
835    fn parse<R: Read>(reader: &mut R) -> crate::Result<Self> {
836        let mut magic = [0u8; 6];
837        reader.read_exact(&mut magic)?;
838        if magic != XZ_MAGIC {
839            return Err(error_invalid_data("invalid XZ magic bytes"));
840        }
841
842        Self::parse_stream_header_flags_and_crc(reader)
843    }
844
845    pub(crate) fn parse_stream_header_flags_and_crc<R: Read>(
846        reader: &mut R,
847    ) -> crate::Result<Self> {
848        let mut flags = [0u8; 2];
849        reader.read_exact(&mut flags)?;
850
851        if flags[0] != 0 {
852            return Err(error_invalid_data("invalid XZ stream flags"));
853        }
854
855        let check_type = CheckType::from_byte(flags[1])?;
856
857        let expected_crc = reader.read_u32()?;
858
859        if expected_crc != Crc32::checksum(&flags) {
860            return Err(error_invalid_data("XZ stream header CRC32 mismatch"));
861        }
862
863        Ok(StreamHeader { check_type })
864    }
865}
866
867impl StreamFooter {
868    pub(crate) fn parse<R: Read>(reader: &mut R) -> crate::Result<Self> {
869        let expected_crc = reader.read_u32()?;
870
871        let backward_size = reader.read_u32()?;
872
873        let mut stream_flags = [0u8; 2];
874        reader.read_exact(&mut stream_flags)?;
875
876        // Verify CRC32 of backward size + stream flags.
877        let mut crc = Crc32::new();
878        crc.update(&backward_size.to_le_bytes());
879        crc.update(&stream_flags);
880
881        if expected_crc != crc.finalize() {
882            return Err(error_invalid_data("stream footer CRC32 mismatch"));
883        }
884
885        let mut footer_magic = [0u8; 2];
886        reader.read_exact(&mut footer_magic)?;
887        if footer_magic != XZ_FOOTER_MAGIC {
888            return Err(error_invalid_data("invalid XZ footer magic bytes"));
889        }
890
891        Ok(StreamFooter {
892            backward_size,
893            stream_flags,
894        })
895    }
896}
897
898impl Index {
899    pub(crate) fn parse<R: Read>(reader: &mut R) -> crate::Result<Index> {
900        // sic! index indicator already consumed
901        let number_of_records = parse_multibyte_integer_from_reader(reader)?;
902        let mut records = Vec::new();
903        records.try_reserve_exact(number_of_records as usize)?;
904
905        for _ in 0..number_of_records {
906            let unpadded_size = parse_multibyte_integer_from_reader(reader)?;
907            let uncompressed_size = parse_multibyte_integer_from_reader(reader)?;
908
909            if unpadded_size == 0 {
910                return Err(error_invalid_data("invalid index record unpadded size"));
911            }
912
913            records.push(IndexRecord {
914                unpadded_size,
915                uncompressed_size,
916            });
917        }
918
919        // Skip index padding (0-3 null bytes to make multiple of 4).
920        let mut bytes_read = 1;
921        bytes_read += count_multibyte_integer_size_for_value(number_of_records);
922        for record in &records {
923            bytes_read += count_multibyte_integer_size_for_value(record.unpadded_size);
924            bytes_read += count_multibyte_integer_size_for_value(record.uncompressed_size);
925        }
926
927        let padding_needed = (4 - (bytes_read % 4)) % 4;
928
929        if padding_needed > 0 {
930            let mut padding_buf = [0u8; 3];
931            reader.read_exact(&mut padding_buf[..padding_needed])?;
932
933            if !padding_buf[..padding_needed].iter().all(|&b| b == 0) {
934                return Err(error_invalid_data("invalid index padding"));
935            }
936        }
937
938        let expected_crc = reader.read_u32()?;
939
940        // Calculate CRC32 over index data (excluding CRC32 itself).
941        let mut crc = Crc32::new();
942        crc.update(&[0]);
943
944        // Add number of records.
945        let mut temp_buf = [0u8; 10];
946        let size = encode_multibyte_integer(number_of_records, &mut temp_buf)?;
947        crc.update(&temp_buf[..size]);
948
949        // Add all records.
950        for record in &records {
951            let size = encode_multibyte_integer(record.unpadded_size, &mut temp_buf)?;
952            crc.update(&temp_buf[..size]);
953            let size = encode_multibyte_integer(record.uncompressed_size, &mut temp_buf)?;
954            crc.update(&temp_buf[..size]);
955        }
956
957        update_crc_with_padding(&mut crc, padding_needed);
958
959        if expected_crc != crc.finalize() {
960            return Err(error_invalid_data("index CRC32 mismatch"));
961        }
962
963        Ok(Index {
964            number_of_records,
965            records,
966        })
967    }
968}
969
970#[cfg(feature = "encoder")]
971fn write_xz_stream_header<W: Write>(writer: &mut W, check_type: CheckType) -> crate::Result<()> {
972    writer.write_all(&XZ_MAGIC)?;
973
974    let stream_flags = [0u8, check_type as u8];
975    writer.write_all(&stream_flags)?;
976
977    let crc = Crc32::checksum(&stream_flags);
978    writer.write_u32(crc)?;
979
980    Ok(())
981}
982
983#[cfg(feature = "encoder")]
984fn encode_lzma2_dict_size(dict_size: u32) -> crate::Result<u8> {
985    if dict_size < 4096 {
986        return Err(error_invalid_input("LZMA2 dictionary size too small"));
987    }
988
989    if dict_size == 0xFFFFFFFF {
990        return Ok(40);
991    }
992
993    // Find the appropriate property value.
994    for prop in 0u8..40 {
995        let base = 2 | ((prop & 1) as u32);
996        let size = base << (prop / 2 + 11);
997
998        if size >= dict_size {
999            return Ok(prop);
1000        }
1001    }
1002
1003    Err(error_invalid_input("LZMA2 dictionary size too large"))
1004}
1005
1006fn update_crc_with_padding(crc: &mut Crc32, padding_needed: usize) {
1007    match padding_needed {
1008        1 => crc.update(&[0]),
1009        2 => crc.update(&[0, 0]),
1010        3 => crc.update(&[0, 0, 0]),
1011        _ => {}
1012    }
1013}
1014
1015/// Scan the XZ file to collect information about all blocks.
1016/// This reads the index at the end of the file to efficiently locate block boundaries.
1017#[cfg(feature = "std")]
1018fn scan_blocks<R: Read + Seek>(mut reader: R) -> io::Result<(R, Vec<Block>, CheckType)> {
1019    let stream_header = StreamHeader::parse(&mut reader)?;
1020    let check_type = stream_header.check_type;
1021
1022    let header_end_pos = reader.stream_position()?;
1023
1024    let file_size = reader.seek(SeekFrom::End(0))?;
1025
1026    // Minimum XZ file: 12 byte header + 12 byte footer + 8 byte minimum index.
1027    if file_size < 32 {
1028        return Err(error_invalid_data(
1029            "File too small to contain a valid XZ stream",
1030        ));
1031    }
1032
1033    reader.seek(SeekFrom::End(-12))?;
1034
1035    let stream_footer = StreamFooter::parse(&mut reader)?;
1036
1037    let header_flags = [0, check_type as u8];
1038
1039    if stream_footer.stream_flags != header_flags {
1040        return Err(error_invalid_data(
1041            "stream header and footer flags mismatch",
1042        ));
1043    }
1044
1045    // Now read the index using backward size.
1046    let index_size = (stream_footer.backward_size + 1) * 4;
1047    let index_start_pos = file_size - 12 - index_size as u64;
1048
1049    reader.seek(SeekFrom::Start(index_start_pos))?;
1050
1051    // Parse the index.
1052    let index_indicator = reader.read_u8()?;
1053
1054    if index_indicator != 0 {
1055        return Err(error_invalid_data("invalid XZ index indicator"));
1056    }
1057
1058    let index = Index::parse(&mut reader)?;
1059
1060    let mut blocks = Vec::new();
1061    let mut block_start_pos = header_end_pos;
1062
1063    for record in &index.records {
1064        blocks.push(Block {
1065            start_pos: block_start_pos,
1066            unpadded_size: record.unpadded_size,
1067            uncompressed_size: record.uncompressed_size,
1068        });
1069
1070        let padding_needed = (4 - (record.unpadded_size % 4)) % 4;
1071        let actual_block_size = record.unpadded_size + padding_needed;
1072
1073        block_start_pos += actual_block_size;
1074    }
1075
1076    if blocks.is_empty() {
1077        return Err(io::Error::new(
1078            io::ErrorKind::InvalidData,
1079            "No valid XZ blocks found",
1080        ));
1081    }
1082
1083    reader.seek(SeekFrom::Start(0))?;
1084
1085    Ok((reader, blocks, check_type))
1086}
1087
1088#[cfg(feature = "std")]
1089fn create_filter_chain<'reader>(
1090    mut chain_reader: Box<dyn Read + 'reader>,
1091    filters: &[Option<FilterType>],
1092    properties: &[u32],
1093) -> Box<dyn Read + 'reader> {
1094    for (filter, property) in filters
1095        .iter()
1096        .copied()
1097        .zip(properties)
1098        .filter_map(|(filter, property)| filter.map(|filter| (filter, *property)))
1099        .rev()
1100    {
1101        chain_reader = match filter {
1102            FilterType::Delta => {
1103                let distance = property as usize;
1104                Box::new(DeltaReader::new(chain_reader, distance))
1105            }
1106            FilterType::BcjX86 => {
1107                let start_offset = property as usize;
1108                Box::new(BcjReader::new_x86(chain_reader, start_offset))
1109            }
1110            FilterType::BcjPpc => {
1111                let start_offset = property as usize;
1112                Box::new(BcjReader::new_ppc(chain_reader, start_offset))
1113            }
1114            FilterType::BcjIa64 => {
1115                let start_offset = property as usize;
1116                Box::new(BcjReader::new_ia64(chain_reader, start_offset))
1117            }
1118            FilterType::BcjArm => {
1119                let start_offset = property as usize;
1120                Box::new(BcjReader::new_arm(chain_reader, start_offset))
1121            }
1122            FilterType::BcjArmThumb => {
1123                let start_offset = property as usize;
1124                Box::new(BcjReader::new_arm_thumb(chain_reader, start_offset))
1125            }
1126            FilterType::BcjSparc => {
1127                let start_offset = property as usize;
1128                Box::new(BcjReader::new_sparc(chain_reader, start_offset))
1129            }
1130            FilterType::BcjArm64 => {
1131                let start_offset = property as usize;
1132                Box::new(BcjReader::new_arm64(chain_reader, start_offset))
1133            }
1134            FilterType::BcjRiscv => {
1135                let start_offset = property as usize;
1136                Box::new(BcjReader::new_riscv(chain_reader, start_offset))
1137            }
1138            FilterType::Lzma2 => {
1139                let dict_size = property;
1140                Box::new(Lzma2Reader::new(chain_reader, dict_size, None))
1141            }
1142        };
1143    }
1144
1145    chain_reader
1146}
1147
1148#[cfg(feature = "encoder")]
1149fn add_padding<W: Write + ?Sized>(writer: &mut W, padding_needed: usize) -> crate::Result<()> {
1150    match padding_needed {
1151        1 => writer.write_all(&[0]),
1152        2 => writer.write_all(&[0, 0]),
1153        3 => writer.write_all(&[0, 0, 0]),
1154        _ => Ok(()),
1155    }
1156}
1157
1158#[cfg(feature = "encoder")]
1159fn generate_block_header_data(
1160    filters: &[FilterConfig],
1161    lzma_dict_size: u32,
1162) -> crate::Result<Vec<u8>> {
1163    let mut header_data = Vec::new();
1164    let num_filters = filters.len();
1165
1166    if num_filters > 4 {
1167        return Err(error_invalid_input("too many filters in chain (maximum 4)"));
1168    }
1169
1170    // Block flags: no compressed size, no uncompressed size, filter count
1171    let block_flags = (num_filters - 1) as u8; // -1 because 0 means 1 filter, 3 means 4 filters
1172    header_data.push(block_flags);
1173
1174    let mut temp_buf = [0u8; 10];
1175
1176    for filter_config in filters {
1177        // Write filter ID.
1178        let filter_id = match filter_config.filter_type {
1179            FilterType::Delta => 0x03,
1180            FilterType::BcjX86 => 0x04,
1181            FilterType::BcjPpc => 0x05,
1182            FilterType::BcjIa64 => 0x06,
1183            FilterType::BcjArm => 0x07,
1184            FilterType::BcjArmThumb => 0x08,
1185            FilterType::BcjSparc => 0x09,
1186            FilterType::BcjArm64 => 0x0A,
1187            FilterType::BcjRiscv => 0x0B,
1188            FilterType::Lzma2 => 0x21,
1189        };
1190        let size = encode_multibyte_integer(filter_id, &mut temp_buf)?;
1191        header_data.extend_from_slice(&temp_buf[..size]);
1192
1193        // Write filter properties.
1194        match filter_config.filter_type {
1195            FilterType::Delta => {
1196                // Properties size (1 byte)
1197                let size = encode_multibyte_integer(1, &mut temp_buf)?;
1198                header_data.extend_from_slice(&temp_buf[..size]);
1199                // Distance property (encoded as distance - 1)
1200                let distance_prop = (filter_config.property - 1) as u8;
1201                header_data.push(distance_prop);
1202            }
1203            FilterType::BcjX86
1204            | FilterType::BcjPpc
1205            | FilterType::BcjIa64
1206            | FilterType::BcjArm
1207            | FilterType::BcjArmThumb
1208            | FilterType::BcjSparc
1209            | FilterType::BcjArm64
1210            | FilterType::BcjRiscv => {
1211                if filter_config.property == 0 {
1212                    // No start offset.
1213                    let size = encode_multibyte_integer(0, &mut temp_buf)?;
1214                    header_data.extend_from_slice(&temp_buf[..size]);
1215                } else {
1216                    // 4-byte start offset.
1217                    let size = encode_multibyte_integer(4, &mut temp_buf)?;
1218                    header_data.extend_from_slice(&temp_buf[..size]);
1219                    header_data.extend_from_slice(&filter_config.property.to_le_bytes());
1220                }
1221            }
1222            FilterType::Lzma2 => {
1223                let size = encode_multibyte_integer(1, &mut temp_buf)?;
1224                header_data.extend_from_slice(&temp_buf[..size]);
1225
1226                let dict_size_prop = encode_lzma2_dict_size(lzma_dict_size)?;
1227                header_data.push(dict_size_prop);
1228            }
1229        }
1230    }
1231
1232    Ok(header_data)
1233}
1234
1235#[cfg(feature = "encoder")]
1236fn write_xz_block_header<W: Write>(
1237    writer: &mut W,
1238    filters: &[FilterConfig],
1239    lzma_dict_size: u32,
1240) -> crate::Result<u64> {
1241    let header_data = generate_block_header_data(filters, lzma_dict_size)?;
1242
1243    // Calculate header size (including size byte and CRC32, rounded up to multiple of 4)
1244    let total_size_needed: usize = 1 + header_data.len() + 4;
1245    let header_size = total_size_needed.div_ceil(4) * 4;
1246    let header_size_encoded = ((header_size / 4) - 1) as u8;
1247
1248    let padding_needed = header_size - 1 - header_data.len() - 4;
1249
1250    // Calculate and write CRC32 of header size byte + header data + padding
1251    let mut crc = Crc32::new();
1252    crc.update(&[header_size_encoded]);
1253    crc.update(&header_data);
1254    update_crc_with_padding(&mut crc, padding_needed);
1255
1256    let crc_value = crc.finalize();
1257
1258    // Now write everything to the writer
1259    writer.write_u8(header_size_encoded)?;
1260    writer.write_all(&header_data)?;
1261    add_padding(writer, padding_needed)?;
1262    writer.write_u32(crc_value)?;
1263
1264    Ok(header_size as u64)
1265}
1266
1267#[cfg(feature = "encoder")]
1268fn write_xz_index<W: Write>(writer: &mut W, index_records: &[IndexRecord]) -> crate::Result<()> {
1269    let mut index_data = Vec::new();
1270
1271    let mut temp_buf = [0u8; 10];
1272    let size = encode_multibyte_integer(index_records.len() as u64, &mut temp_buf)?;
1273    index_data.extend_from_slice(&temp_buf[..size]);
1274
1275    for record in index_records {
1276        let size = encode_multibyte_integer(record.unpadded_size, &mut temp_buf)?;
1277        index_data.extend_from_slice(&temp_buf[..size]);
1278
1279        let size = encode_multibyte_integer(record.uncompressed_size, &mut temp_buf)?;
1280        index_data.extend_from_slice(&temp_buf[..size]);
1281    }
1282
1283    let bytes_written = 1 + index_data.len(); // indicator + index data
1284    let padding_needed = (4 - (bytes_written % 4)) % 4;
1285
1286    let mut crc = Crc32::new();
1287    crc.update(&[0x00]);
1288    crc.update(&index_data);
1289    update_crc_with_padding(&mut crc, padding_needed);
1290
1291    let crc_value = crc.finalize();
1292
1293    // Index indicator (0x00).
1294    writer.write_u8(0x00)?;
1295    writer.write_all(&index_data)?;
1296    add_padding(writer, padding_needed)?;
1297    writer.write_u32(crc_value)?;
1298
1299    Ok(())
1300}
1301
1302#[cfg(feature = "encoder")]
1303fn write_xz_stream_footer<W: Write>(
1304    writer: &mut W,
1305    index_records: &[IndexRecord],
1306    check_type: CheckType,
1307) -> crate::Result<()> {
1308    // Calculate backward size (index size in 4-byte blocks).
1309    let mut index_size = 1; // indicator
1310    index_size += count_multibyte_integer_size_for_value(index_records.len() as u64);
1311
1312    for record in index_records {
1313        index_size += count_multibyte_integer_size_for_value(record.unpadded_size);
1314        index_size += count_multibyte_integer_size_for_value(record.uncompressed_size);
1315    }
1316
1317    let padding_needed = (4 - (index_size % 4)) % 4;
1318    index_size += padding_needed;
1319    index_size += 4; // CRC32
1320
1321    let backward_size = ((index_size / 4) - 1) as u32;
1322
1323    // Stream flags (same as header).
1324    let stream_flags = [0u8, check_type as u8];
1325
1326    // Calculate CRC32 of backward size + stream flags
1327    let mut crc = Crc32::new();
1328    crc.update(&backward_size.to_le_bytes());
1329    crc.update(&stream_flags);
1330
1331    writer.write_u32(crc.finalize())?;
1332    writer.write_u32(backward_size)?;
1333    writer.write_all(&stream_flags)?;
1334    writer.write_all(&XZ_FOOTER_MAGIC)?;
1335
1336    Ok(())
1337}
1338
1339#[cfg(test)]
1340mod tests {
1341    use super::*;
1342
1343    #[test]
1344    fn test_encode_decode_multibyte_integer() {
1345        let values = [0, 127, 128, 16383, 16384, 2097151, 2097152];
1346
1347        for &value in &values {
1348            let mut buf = [0u8; 9];
1349            let encoded_size = encode_multibyte_integer(value, &mut buf).unwrap();
1350
1351            let decoded = parse_multibyte_integer(&buf[..encoded_size]).unwrap();
1352            assert_eq!(decoded, value);
1353
1354            let size_for_value = count_multibyte_integer_size_for_value(value);
1355            assert_eq!(size_for_value, encoded_size);
1356        }
1357    }
1358
1359    #[test]
1360    fn test_multibyte_integer_limits() {
1361        // Test maximum allowed value (63 bits)
1362        let max_value = u64::MAX / 2;
1363        let mut buf = [0u8; 9];
1364        let encoded_size = encode_multibyte_integer(max_value, &mut buf).unwrap();
1365
1366        let decoded = parse_multibyte_integer(&buf[..encoded_size]).unwrap();
1367        assert_eq!(decoded, max_value);
1368
1369        // Test value that's too large
1370        let too_large = u64::MAX;
1371        let encoded_size = encode_multibyte_integer(too_large, &mut buf);
1372        assert!(encoded_size.is_err());
1373    }
1374
1375    #[test]
1376    fn test_index_record_creation() {
1377        let record = IndexRecord {
1378            unpadded_size: 1024,
1379            uncompressed_size: 2048,
1380        };
1381
1382        assert_eq!(record.unpadded_size, 1024);
1383        assert_eq!(record.uncompressed_size, 2048);
1384    }
1385
1386    #[test]
1387    fn test_checksum_calculator_crc32() {
1388        let mut calc = ChecksumCalculator::new(CheckType::Crc32);
1389        calc.update(b"123456789");
1390
1391        // CRC32 of "123456789" in little-endian format
1392        let expected = [0x26, 0x39, 0xF4, 0xCB];
1393        assert!(calc.verify(&expected));
1394    }
1395
1396    #[test]
1397    fn test_checksum_calculator_crc64() {
1398        let mut calc = ChecksumCalculator::new(CheckType::Crc64);
1399        calc.update(b"123456789");
1400
1401        // CRC64 of "123456789" in little-endian format.
1402        let expected = [250, 57, 25, 223, 187, 201, 93, 153];
1403        assert!(calc.verify(&expected));
1404    }
1405
1406    #[test]
1407    fn test_checksum_calculator_sha256() {
1408        let mut calc = ChecksumCalculator::new(CheckType::Sha256);
1409        calc.update(b"123456789");
1410
1411        // SHA256 of "123456789"
1412        let expected = [
1413            21, 226, 176, 211, 195, 56, 145, 235, 176, 241, 239, 96, 158, 196, 25, 66, 12, 32, 227,
1414            32, 206, 148, 198, 95, 188, 140, 51, 18, 68, 142, 178, 37,
1415        ];
1416        assert!(calc.verify(&expected));
1417    }
1418}