zesven 3.2.0

A pure Rust implementation of the 7z archive format
Documentation
//! Compressed header encoding.
//!
//! A plain header lists every entry's name, size, timestamps and attributes, so
//! it grows with the archive and compresses extremely well. 7-Zip compresses it
//! by default; leaving it raw meant that for a few hundred small files the
//! header was most of the archive.

#[cfg(feature = "lzma2")]
use std::io::Write;

#[cfg(feature = "lzma2")]
use crate::Result;
#[cfg(feature = "lzma2")]
use crate::format::property_id;
#[cfg(feature = "lzma2")]
use crate::format::reader::write_variable_u64;

/// Headers below this size are left alone.
///
/// Compressing a header costs a coder chain of roughly forty bytes to describe,
/// so for a small one the wrapper is most of what is saved.
#[cfg(feature = "lzma2")]
const COMPRESSION_THRESHOLD: usize = 512;

/// Compresses a header, returning `(payload, structure)`.
///
/// The payload is an ordinary packed stream that belongs in the data area;
/// the structure is the ENCODED_HEADER that points at it and becomes the
/// archive's next header. `pack_pos` is the payload's offset relative to the
/// end of the signature header.
///
/// Returns `None` when compressing would not pay for itself.
///
/// A free function rather than a method: the async writer builds the same
/// archive from the same model and has to compress its header the same way,
/// and a second implementation of this is a second set of bytes.
#[cfg(feature = "lzma2")]
pub(crate) fn encode_compressed_header(
    plain_header: &[u8],
    pack_pos: u64,
) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
    use crate::codec::method;
    use crate::write::header_encryption::lzma2_dictionary_property;

    if plain_header.len() < COMPRESSION_THRESHOLD {
        return Ok(None);
    }

    let compressed = {
        use lzma_rust2::{Lzma2Options, Lzma2Writer};

        let mut compressed = Vec::new();
        let options = Lzma2Options::with_preset(5);
        let mut encoder = Lzma2Writer::new(&mut compressed, options);
        encoder.write_all(plain_header).map_err(crate::Error::Io)?;
        encoder.finish().map_err(crate::Error::Io)?;
        compressed
    };

    // Describing the compressed stream costs bytes of its own; if the result
    // is not smaller than what it replaces, write the header as it is.
    if compressed.len() >= plain_header.len() {
        return Ok(None);
    }

    let mut encoded = Vec::new();

    encoded.push(property_id::ENCODED_HEADER);

    // PackInfo: one stream, at pack_pos, of the compressed length.
    encoded.push(property_id::PACK_INFO);
    write_variable_u64(&mut encoded, pack_pos)?;
    write_variable_u64(&mut encoded, 1)?;
    encoded.push(property_id::SIZE);
    write_variable_u64(&mut encoded, compressed.len() as u64)?;
    encoded.push(property_id::END);

    // UnpackInfo: a single LZMA2 coder producing the original header.
    encoded.push(property_id::UNPACK_INFO);
    encoded.push(property_id::FOLDER);
    write_variable_u64(&mut encoded, 1)?;
    encoded.push(0); // external = 0

    encoded.push(0x01); // one coder
    let lzma2_props = [lzma2_dictionary_property(plain_header.len() as u64)];
    encoded.push((method::LZMA2.len() as u8) | 0x20);
    encoded.extend_from_slice(method::LZMA2);
    write_variable_u64(&mut encoded, lzma2_props.len() as u64)?;
    encoded.extend_from_slice(&lzma2_props);

    encoded.push(property_id::CODERS_UNPACK_SIZE);
    write_variable_u64(&mut encoded, plain_header.len() as u64)?;

    // The checksum of what the stream decodes to, so a reader can tell a
    // damaged header from a valid one before trying to parse it.
    encoded.push(property_id::CRC);
    encoded.push(1); // all defined
    encoded.extend_from_slice(&crc32fast::hash(plain_header).to_le_bytes());

    encoded.push(property_id::END); // End UnpackInfo
    encoded.push(property_id::END); // End streams

    Ok(Some((compressed, encoded)))
}