fix: hash collision edge case

This commit is contained in:
sreedevk
2025-07-19 11:10:12 +00:00
parent e78dfc4211
commit c0286b17cc
4 changed files with 55 additions and 8 deletions

View File

@@ -25,12 +25,18 @@ pub struct FileInfo {
impl FileInfo {
pub fn hash(&self, seed: i64) -> Result<u128> {
if self.size == 0 {
return Ok(0u128);
};
let file = fs::File::open(&self.path)?;
let mapper = unsafe { Mmap::map(&file)? };
Ok(mapper
let content_hash = mapper
.chunks(4096)
.fold(0u128, |acc, chunk: &[u8]| acc ^ gxhash128(chunk, seed)))
.fold(0u128, |acc, chunk: &[u8]| acc ^ gxhash128(chunk, seed));
// NOTE: avoids collision bw an empty file & a file full of null bytes.
Ok(content_hash ^ gxhash128(&self.size.to_ne_bytes(), seed))
}
pub fn initpages_hash(&self, seed: i64) -> Result<u128> {
@@ -61,3 +67,38 @@ impl FileInfo {
*self_state == FileState::SwProcessed
}
}
#[cfg(test)]
mod test {
use super::*;
use tempfile::TempDir;
use std::fs::File;
use std::io::Write;
use anyhow::Result;
fn generate_null_bytes(size: usize) -> Vec<u8> {
(0..size).map(|_| 0).collect::<Vec<u8>>()
}
#[test]
fn hash_differentiates_between_a_file_of_null_bytes_vs_an_empty_file() -> Result<()> {
let root = TempDir::new()?;
let empty_file_name = root.path().join("empty_file.bin");
File::create_new(&empty_file_name)?;
let file_with_null_bytes_name = root.path().join("file_with_null_bytes.bin");
let mut file_with_null_bytes = File::create_new(&file_with_null_bytes_name)?;
file_with_null_bytes.write_all(&generate_null_bytes(1000 * 4096))?;
let empty_file_info = FileInfo::new(empty_file_name)?;
let file_with_empty_bytes_info = FileInfo::new(file_with_null_bytes_name)?;
let seed: i64 = 246910456374;
assert_ne!(empty_file_info.hash(seed)?, file_with_empty_bytes_info.hash(seed)?);
Ok(())
}
}

View File

@@ -67,9 +67,9 @@ impl Server {
self.threadpool.execute(move || {
Scanner::new(app_args_clone_for_sc)
.unwrap()
.expect("unable to initialize scanner.")
.scan(file_queue_clone_sc, progbarbox_sc_clone)
.unwrap();
.expect("scanner failed.");
sfin_sc_tr_cl.store(true, std::sync::atomic::Ordering::Relaxed);
});
@@ -82,7 +82,7 @@ impl Server {
file_queue_clone_pr,
progbarbox_pr_clone_for_sw,
)
.unwrap();
.expect("sizewise scanner failed.");
swfin_pr_tr_sw.store(true, std::sync::atomic::Ordering::Relaxed);
});
@@ -97,7 +97,7 @@ impl Server {
seed,
swfin_pr_tr_hw,
)
.unwrap();
.expect("sizewise scanner failed.");
});
progbarbox.clear()?;