blazehash 0.2.5

Forensic file hasher — hashdeep for the modern era, BLAKE3 by default
Documentation
use anyhow::Result;
use blazehash::algorithm::{hash_bytes, Algorithm};
use blazehash::hash::FileHashResult;
use oci_distribution::{
    client::{Client, ClientConfig},
    manifest::{IMAGE_LAYER_GZIP_MEDIA_TYPE, IMAGE_LAYER_MEDIA_TYPE},
    secrets::RegistryAuth,
    Reference,
};
use std::collections::HashMap;

use blazehash::image::{layer_path, parse_image_ref, strip_oci_prefix};

pub fn oci_results(uri: &str, algorithms: &[Algorithm]) -> Result<Vec<FileHashResult>> {
    let rt = tokio::runtime::Runtime::new()?;
    rt.block_on(oci_results_async(uri, algorithms))
}

async fn oci_results_async(uri: &str, algorithms: &[Algorithm]) -> Result<Vec<FileHashResult>> {
    let ref_str = strip_oci_prefix(uri);
    let image_ref = parse_image_ref(ref_str)?;

    let reference = Reference::with_tag(
        image_ref.registry.clone(),
        image_ref.name.clone(),
        image_ref.tag.clone(),
    );

    let client = Client::new(ClientConfig::default());
    let image_data = client
        .pull(
            &reference,
            &RegistryAuth::Anonymous,
            vec![IMAGE_LAYER_MEDIA_TYPE, IMAGE_LAYER_GZIP_MEDIA_TYPE],
        )
        .await
        .map_err(|e| anyhow::anyhow!("OCI pull failed for {uri}: {e}"))?;

    let mut results = Vec::new();
    for (i, layer) in image_data.layers.iter().enumerate() {
        let digest = layer.sha256_digest();
        let path = layer_path(ref_str, i, &digest);
        let size = layer.data.len() as u64;
        let mut hashes = HashMap::new();
        for algo in algorithms {
            hashes.insert(*algo, hash_bytes(*algo, &layer.data));
        }
        results.push(FileHashResult {
            path,
            size,
            hashes,
            entropy: None,
            #[cfg(feature = "yara")]
            yara_matches: None,
            #[cfg(feature = "yara")]
            yara_enrichments: Vec::new(),
        });
    }
    Ok(results)
}