msy 0.4.2

Modern musl rsync alternative - Fast, parallel file synchronization
Documentation
#[allow(dead_code)] // Public API and hasher infrastructure
use crate::error::Result;
use std::fs::File;
use std::io::Read;
use std::path::Path;

/// Wrapper around BLAKE3 hasher
pub struct Blake3Hasher;

#[allow(dead_code)] // Public API with tested methods
impl Blake3Hasher {
    /// Compute BLAKE3 hash of a file
    ///
    /// This reads the entire file and computes its hash.
    /// For large files, this may use significant memory.
    pub fn hash_file(path: &Path) -> Result<blake3::Hash> {
        let mut file = File::open(path)?;
        let mut hasher = blake3::Hasher::new();

        // Read and hash in chunks to avoid loading entire file into memory
        let mut buffer = vec![0u8; 1024 * 1024]; // 1MB chunks
        loop {
            let bytes_read = file.read(&mut buffer)?;
            if bytes_read == 0 {
                break;
            }
            hasher.update(&buffer[..bytes_read]);
        }

        Ok(hasher.finalize())
    }

    /// Compute BLAKE3 hash of data in memory
    pub fn hash_data(data: &[u8]) -> blake3::Hash {
        blake3::hash(data)
    }

    /// Compute BLAKE3 hash incrementally (for streaming)
    pub fn new_hasher() -> blake3::Hasher {
        blake3::Hasher::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn test_hash_data() {
        let data = b"Hello, BLAKE3!";
        let hash = Blake3Hasher::hash_data(data);
        assert_eq!(hash.as_bytes().len(), 32); // BLAKE3 produces 32-byte hashes
    }

    #[test]
    fn test_hash_data_deterministic() {
        let data = b"Test data";
        let hash1 = Blake3Hasher::hash_data(data);
        let hash2 = Blake3Hasher::hash_data(data);
        assert_eq!(hash1, hash2);
    }

    #[test]
    fn test_hash_data_different() {
        let hash1 = Blake3Hasher::hash_data(b"Data 1");
        let hash2 = Blake3Hasher::hash_data(b"Data 2");
        assert_ne!(hash1, hash2);
    }

    #[test]
    fn test_hash_file() {
        let temp_dir = TempDir::new().unwrap();
        let file_path = temp_dir.path().join("test.txt");
        let content = b"File content for BLAKE3";
        fs::write(&file_path, content).unwrap();

        let hash = Blake3Hasher::hash_file(&file_path).unwrap();
        assert_eq!(hash.as_bytes().len(), 32);

        // Should match hash of raw data
        let data_hash = Blake3Hasher::hash_data(content);
        assert_eq!(hash, data_hash);
    }

    #[test]
    fn test_hash_large_file() {
        let temp_dir = TempDir::new().unwrap();
        let file_path = temp_dir.path().join("large.txt");

        // Create 10MB file
        let chunk = vec![0x42u8; 1024 * 1024]; // 1MB
        let mut file = File::create(&file_path).unwrap();
        use std::io::Write;
        for _ in 0..10 {
            file.write_all(&chunk).unwrap();
        }
        drop(file);

        // Hash it
        let hash = Blake3Hasher::hash_file(&file_path).unwrap();
        assert_eq!(hash.as_bytes().len(), 32);
    }

    #[test]
    fn test_incremental_hasher() {
        let mut hasher = Blake3Hasher::new_hasher();
        hasher.update(b"Hello, ");
        hasher.update(b"world!");
        let hash1 = hasher.finalize();

        let hash2 = Blake3Hasher::hash_data(b"Hello, world!");
        assert_eq!(hash1, hash2);
    }

    #[test]
    fn test_empty_data() {
        let hash = Blake3Hasher::hash_data(b"");
        assert_eq!(hash.as_bytes().len(), 32);
    }

    #[test]
    fn test_empty_file() {
        let temp_dir = TempDir::new().unwrap();
        let file_path = temp_dir.path().join("empty.txt");
        fs::write(&file_path, b"").unwrap();

        let file_hash = Blake3Hasher::hash_file(&file_path).unwrap();
        let data_hash = Blake3Hasher::hash_data(b"");
        assert_eq!(file_hash, data_hash);
    }
}