zstd-complete 0.3.0

A pure Rust Zstandard compressor and decompressor with bounded streaming I/O
Documentation
use alloc::vec::Vec;

use super::{
    cctx_params::{CctxParameters, ParamSwitch},
    dictionary::{parse_dictionary, DictionaryContentType, ParsedDictionary},
    opt_frame::{
        encode_frame_btopt_with_dictionary, encode_frame_btultra2_with_dictionary,
        encode_frame_btultra_with_dictionary, encode_frame_opt_no_dict_with_cctx,
    },
    test_dictionary::{dictionary_content, full_dictionary_fixture, DICT_ID},
};
use crate::{
    blocks::block::BlockType,
    decoding::{dictionary::Dictionary, FrameDecoder},
};

#[test]
fn btopt_frame_with_dictionary_writes_dict_id_and_round_trips() {
    let encoded = encode_with_fixture_dictionary(16, encode_frame_btopt_with_dictionary);

    assert_dictionary_frame_round_trips(&encoded);
}

#[test]
fn btultra_frame_with_dictionary_writes_dict_id_and_round_trips() {
    let encoded = encode_with_fixture_dictionary(18, encode_frame_btultra_with_dictionary);

    assert_dictionary_frame_round_trips(&encoded);
}

#[test]
fn btultra2_frame_with_dictionary_writes_dict_id_and_round_trips() {
    let encoded = encode_with_fixture_dictionary(19, encode_frame_btultra2_with_dictionary);

    assert_dictionary_frame_round_trips(&encoded);
}

#[test]
fn btultra2_dictionary_path_uses_btultra_block_compressor_like_c() {
    let dict_bytes = full_dictionary_fixture();
    let parsed = parse_dictionary(&dict_bytes, DictionaryContentType::Auto, false)
        .unwrap()
        .expect("full dictionary");
    let data = dictionary_payload();

    let btultra = encode_frame_btultra_with_dictionary(&data, 19, parsed.clone());
    let btultra2 = encode_frame_btultra2_with_dictionary(&data, 19, parsed);

    assert_eq!(btultra2, btultra);
}

#[test]
fn target_c_block_size_uses_basic_superblock_when_sequence_block_fits() {
    let data = repeated_no_dict_payload();
    let mut target_cctx = CctxParameters::for_level(16, data.len() as u64, 0);
    target_cctx.post_block_splitter = ParamSwitch::Enable;
    assert!(target_cctx.set_target_c_block_size(2048));

    let target_encoded = encode_frame_opt_no_dict_with_cctx(&data, target_cctx);

    assert_eq!(
        first_frame_block_type(&target_encoded),
        BlockType::Compressed
    );
    assert_round_trips(&target_encoded, &data);
}

fn encode_with_fixture_dictionary(
    level: i32,
    encode: fn(&[u8], i32, ParsedDictionary<'_>) -> Vec<u8>,
) -> Vec<u8> {
    let dict_bytes = full_dictionary_fixture();
    let parsed = parse_dictionary(&dict_bytes, DictionaryContentType::Auto, false)
        .unwrap()
        .expect("full dictionary");
    let data = dictionary_payload();

    encode(&data, level, parsed)
}

fn assert_round_trips(encoded: &[u8], expected: &[u8]) {
    let mut decoded = Vec::with_capacity(expected.len());
    let mut decoder = FrameDecoder::new();
    decoder.decode_all_to_vec(encoded, &mut decoded).unwrap();

    assert_eq!(decoded, expected);
}

fn assert_dictionary_frame_round_trips(encoded: &[u8]) {
    let dict_bytes = full_dictionary_fixture();
    let data = dictionary_payload();
    let (header, _) =
        crate::decoding::frame::read_frame_header(encoded).expect("frame header should parse");

    assert_eq!(header.dictionary_id(), Some(DICT_ID));
    assert_eq!(first_frame_block_type(encoded), BlockType::Compressed);
    assert_round_trips_with_dictionary(encoded, &data, &dict_bytes);
}

fn assert_round_trips_with_dictionary(encoded: &[u8], expected: &[u8], dict: &[u8]) {
    let mut decoded = Vec::with_capacity(expected.len());
    let mut decoder = FrameDecoder::new();
    decoder
        .add_dict(Dictionary::decode_dict(dict).unwrap())
        .unwrap();
    decoder.decode_all_to_vec(encoded, &mut decoded).unwrap();

    assert_eq!(decoded, expected);
}

fn first_frame_block_type(encoded: &[u8]) -> BlockType {
    let (_, frame_header_size) =
        crate::decoding::frame::read_frame_header(encoded).expect("frame header should parse");
    let mut block_decoder = crate::decoding::block_decoder::new();
    let (header, _) = block_decoder
        .read_block_header(&encoded[frame_header_size as usize..])
        .expect("block header should parse");

    header.block_type
}

fn dictionary_payload() -> Vec<u8> {
    let mut data = Vec::new();
    for _ in 0..10 {
        data.extend_from_slice(dictionary_content());
    }
    data
}

fn repeated_no_dict_payload() -> Vec<u8> {
    let mut data = Vec::new();
    for _ in 0..64 {
        data.extend_from_slice(b"target-c-block-size-opt-frame ");
    }
    data
}