use crate::error::Result;
use std::path::Path;
mod blake3;
mod xxhash3;
pub use self::blake3::Blake3Hasher;
pub use self::xxhash3::XxHash3Hasher;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChecksumType {
None,
Fast,
#[allow(dead_code)]
Cryptographic,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Checksum {
None,
Fast(Vec<u8>),
Cryptographic(Vec<u8>),
}
#[allow(dead_code)] impl Checksum {
pub fn none() -> Self {
Self::None
}
pub fn fast(hash: Vec<u8>) -> Self {
Self::Fast(hash)
}
pub fn cryptographic(hash: Vec<u8>) -> Self {
Self::Cryptographic(hash)
}
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub fn bytes(&self) -> Option<&[u8]> {
match self {
Self::None => None,
Self::Fast(bytes) | Self::Cryptographic(bytes) => Some(bytes),
}
}
pub fn to_hex(&self) -> String {
match self.bytes() {
Some(bytes) => hex::encode(bytes),
None => "none".to_string(),
}
}
}
#[derive(Clone)]
pub struct IntegrityVerifier {
checksum_type: ChecksumType,
verify_on_write: bool,
}
#[allow(dead_code)] impl IntegrityVerifier {
pub fn new(checksum_type: ChecksumType, verify_on_write: bool) -> Self {
Self { checksum_type, verify_on_write }
}
pub fn checksum_type(&self) -> ChecksumType {
self.checksum_type
}
pub fn verify_on_write(&self) -> bool {
self.verify_on_write
}
pub fn compute_file_checksum(&self, path: &Path) -> Result<Checksum> {
match self.checksum_type {
ChecksumType::None => Ok(Checksum::None),
ChecksumType::Fast => {
let hash = XxHash3Hasher::hash_file(path)?;
Ok(Checksum::Fast(hash.to_le_bytes().to_vec()))
}
ChecksumType::Cryptographic => {
let hash = Blake3Hasher::hash_file(path)?;
Ok(Checksum::Cryptographic(hash.as_bytes().to_vec()))
}
}
}
pub fn compute_data_checksum(&self, data: &[u8]) -> Result<Checksum> {
match self.checksum_type {
ChecksumType::None => Ok(Checksum::None),
ChecksumType::Fast => {
let hash = XxHash3Hasher::hash_data(data);
Ok(Checksum::Fast(hash.to_le_bytes().to_vec()))
}
ChecksumType::Cryptographic => {
let hash = Blake3Hasher::hash_data(data);
Ok(Checksum::Cryptographic(hash.as_bytes().to_vec()))
}
}
}
pub fn verify_transfer(&self, source: &Path, dest: &Path) -> Result<bool> {
let source_sum = self.compute_file_checksum(source)?;
let dest_sum = self.compute_file_checksum(dest)?;
Ok(source_sum == dest_sum)
}
pub fn verify_block(&self, expected_data: &[u8], actual_data: &[u8]) -> Result<bool> {
if !self.verify_on_write {
return Ok(true);
}
let expected_sum = self.compute_data_checksum(expected_data)?;
let actual_sum = self.compute_data_checksum(actual_data)?;
Ok(expected_sum == actual_sum)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_checksum_none() {
let checksum = Checksum::none();
assert!(checksum.is_none());
assert_eq!(checksum.bytes(), None);
assert_eq!(checksum.to_hex(), "none");
}
#[test]
fn test_checksum_fast() {
let checksum = Checksum::fast(vec![1, 2, 3, 4]);
assert!(!checksum.is_none());
assert_eq!(checksum.bytes(), Some(&[1, 2, 3, 4][..]));
assert_eq!(checksum.to_hex(), "01020304");
}
#[test]
fn test_checksum_cryptographic() {
let checksum = Checksum::cryptographic(vec![0xde, 0xad, 0xbe, 0xef]);
assert!(!checksum.is_none());
assert_eq!(checksum.bytes(), Some(&[0xde, 0xad, 0xbe, 0xef][..]));
assert_eq!(checksum.to_hex(), "deadbeef");
}
#[test]
fn test_checksum_equality() {
let cs1 = Checksum::fast(vec![1, 2, 3]);
let cs2 = Checksum::fast(vec![1, 2, 3]);
let cs3 = Checksum::fast(vec![4, 5, 6]);
assert_eq!(cs1, cs2);
assert_ne!(cs1, cs3);
}
#[test]
fn test_verifier_none() {
let verifier = IntegrityVerifier::new(ChecksumType::None, false);
assert_eq!(verifier.checksum_type(), ChecksumType::None);
assert!(!verifier.verify_on_write());
let checksum = verifier.compute_data_checksum(b"test data").unwrap();
assert!(checksum.is_none());
}
#[test]
fn test_verifier_cryptographic() {
let verifier = IntegrityVerifier::new(ChecksumType::Cryptographic, false);
assert_eq!(verifier.checksum_type(), ChecksumType::Cryptographic);
let data = b"Hello, world!";
let checksum = verifier.compute_data_checksum(data).unwrap();
assert!(!checksum.is_none());
let checksum2 = verifier.compute_data_checksum(data).unwrap();
assert_eq!(checksum, checksum2);
let checksum3 = verifier.compute_data_checksum(b"Different data").unwrap();
assert_ne!(checksum, checksum3);
}
#[test]
fn test_verifier_file_checksum() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
fs::write(&file_path, b"Test file content").unwrap();
let verifier = IntegrityVerifier::new(ChecksumType::Cryptographic, false);
let checksum = verifier.compute_file_checksum(&file_path).unwrap();
assert!(!checksum.is_none());
}
#[test]
fn test_verify_transfer() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source.txt");
let dest_path = temp_dir.path().join("dest.txt");
fs::write(&source_path, b"File content").unwrap();
fs::write(&dest_path, b"File content").unwrap();
let verifier = IntegrityVerifier::new(ChecksumType::Cryptographic, false);
assert!(verifier.verify_transfer(&source_path, &dest_path).unwrap());
fs::write(&dest_path, b"Different content").unwrap();
assert!(!verifier.verify_transfer(&source_path, &dest_path).unwrap());
}
#[test]
fn test_verifier_fast() {
let verifier = IntegrityVerifier::new(ChecksumType::Fast, false);
assert_eq!(verifier.checksum_type(), ChecksumType::Fast);
let data = b"Hello, xxHash3!";
let checksum = verifier.compute_data_checksum(data).unwrap();
assert!(!checksum.is_none());
let checksum2 = verifier.compute_data_checksum(data).unwrap();
assert_eq!(checksum, checksum2);
let checksum3 = verifier.compute_data_checksum(b"Different data").unwrap();
assert_ne!(checksum, checksum3);
}
#[test]
fn test_verifier_fast_file() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
fs::write(&file_path, b"Test file content").unwrap();
let verifier = IntegrityVerifier::new(ChecksumType::Fast, false);
let checksum = verifier.compute_file_checksum(&file_path).unwrap();
assert!(!checksum.is_none());
let checksum2 = verifier.compute_file_checksum(&file_path).unwrap();
assert_eq!(checksum, checksum2);
}
#[test]
fn test_verify_transfer_fast() {
let temp_dir = TempDir::new().unwrap();
let source_path = temp_dir.path().join("source.txt");
let dest_path = temp_dir.path().join("dest.txt");
fs::write(&source_path, b"File content").unwrap();
fs::write(&dest_path, b"File content").unwrap();
let verifier = IntegrityVerifier::new(ChecksumType::Fast, false);
assert!(verifier.verify_transfer(&source_path, &dest_path).unwrap());
fs::write(&dest_path, b"Different content").unwrap();
assert!(!verifier.verify_transfer(&source_path, &dest_path).unwrap());
}
#[test]
fn test_verify_block_disabled() {
let verifier = IntegrityVerifier::new(ChecksumType::Cryptographic, false);
let data1 = b"Hello, world!";
let data2 = b"Goodbye, world!";
assert!(verifier.verify_block(data1, data2).unwrap());
}
#[test]
fn test_verify_block_matching() {
let verifier = IntegrityVerifier::new(ChecksumType::Cryptographic, true);
let data = b"Test data for block verification";
assert!(verifier.verify_block(data, data).unwrap());
}
#[test]
fn test_verify_block_mismatched() {
let verifier = IntegrityVerifier::new(ChecksumType::Cryptographic, true);
let expected = b"Expected data";
let actual = b"Different data";
assert!(!verifier.verify_block(expected, actual).unwrap());
}
#[test]
fn test_verify_block_fast_checksum() {
let verifier = IntegrityVerifier::new(ChecksumType::Fast, true);
let data = b"Fast checksum test data";
assert!(verifier.verify_block(data, data).unwrap());
let corrupted = b"Corrupted checksum data";
assert!(!verifier.verify_block(data, corrupted).unwrap());
}
}