hdiff-update-core 0.1.1

Core library for signed HDiffPatch-based differential application updates.
Documentation
use std::{fs, path::PathBuf};

use hdiff_update_core::{
    apply_patch, create_patch, sha256_file, ApplyPatchOptions, CreatePatchOptions,
};
use tempfile::tempdir;

#[test]
fn hdiff_tools_roundtrip_small_binary() {
    let workspace = workspace_root();
    let hdiffz = workspace.join("hdiffz.exe");
    let hpatchz = workspace.join("hpatchz.exe");
    if !hdiffz.is_file() || !hpatchz.is_file() {
        eprintln!("skipping HDiffPatch roundtrip because hdiffz.exe or hpatchz.exe is missing");
        return;
    }

    let dir = tempdir().expect("tempdir");
    let old = dir.path().join("old.bin");
    let new = dir.path().join("new.bin");
    let patch = dir.path().join("delta.hpatch");
    let output = dir.path().join("output.bin");

    let mut old_bytes = vec![0_u8; 1024 * 1024];
    let mut new_bytes = vec![0_u8; 1024 * 1024];
    for index in 0..old_bytes.len() {
        old_bytes[index] = (index % 251) as u8;
        new_bytes[index] = old_bytes[index];
    }
    for index in 100_000..105_000 {
        new_bytes[index] = ((index * 7) % 251) as u8;
    }
    fs::write(&old, old_bytes).expect("old");
    fs::write(&new, new_bytes).expect("new");

    let created = create_patch(&CreatePatchOptions {
        old_path: old.clone(),
        new_path: new.clone(),
        patch_path: patch.clone(),
        hdiffz_path: Some(hdiffz),
        force: true,
        step_size: Some("64k".to_string()),
        old_window_size: Some("512k".to_string()),
        compression: Some("zstd-20".to_string()),
        checksum: Some("xxh128".to_string()),
        parallel_threads: Some(2),
    })
    .expect("create patch");
    assert!(created.patch.size > 0);

    let expected = sha256_file(&new).expect("hash new").sha256;
    let applied = apply_patch(&ApplyPatchOptions {
        old_path: old,
        patch_path: patch,
        output_path: output,
        hpatchz_path: Some(hpatchz),
        force: true,
        expected_sha256: Some(expected.clone()),
        cache_size: Some("8m".to_string()),
        parallel_threads: Some(2),
        verify_checksums: true,
    })
    .expect("apply patch");

    assert_eq!(applied.output.sha256, expected);
    assert_eq!(
        fs::read(new).expect("read new"),
        fs::read(applied.output.path).expect("read out")
    );
}

fn workspace_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(|path| path.parent())
        .expect("workspace root")
        .to_path_buf()
}