use sha2::{Digest, Sha256};
const ALGORITHM: &str = "SHA256";
const BLOCK_SIZE: usize = 4 * 1024 * 1024;
fn encode_hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for byte in bytes {
s.push(char::from_digit((byte >> 4) as u32, 16).unwrap());
s.push(char::from_digit((byte & 0x0f) as u32, 16).unwrap());
}
s
}
#[derive(Debug, Clone)]
pub struct FileIntegrity {
pub algorithm: String,
pub hash: String,
pub block_size: usize,
pub blocks: Vec<String>,
}
impl FileIntegrity {
pub fn from_buffer(data: &[u8]) -> Self {
let hash = encode_hex(&Sha256::digest(data));
let block_count = data.len().div_ceil(BLOCK_SIZE);
let mut blocks = Vec::with_capacity(std::cmp::max(block_count, 1));
if data.is_empty() {
blocks.push(encode_hex(&Sha256::digest(data)));
} else {
let mut offset = 0;
while offset < data.len() {
let end = std::cmp::min(offset + BLOCK_SIZE, data.len());
blocks.push(encode_hex(&Sha256::digest(&data[offset..end])));
offset = end;
}
}
FileIntegrity {
algorithm: ALGORITHM.to_string(),
hash,
block_size: BLOCK_SIZE,
blocks,
}
}
pub fn from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
let mut file_hasher = Sha256::new();
let mut block_hasher = Sha256::new();
let mut current_block_size = 0usize;
let mut blocks = Vec::new();
let mut buf = vec![0u8; 65536];
loop {
let n = reader.read(&mut buf)?;
if n == 0 {
break;
}
let chunk = &buf[..n];
file_hasher.update(chunk);
let mut offset = 0;
while offset < chunk.len() {
let remaining = BLOCK_SIZE - current_block_size;
let end = std::cmp::min(offset + remaining, chunk.len());
block_hasher.update(&chunk[offset..end]);
current_block_size += end - offset;
if current_block_size == BLOCK_SIZE {
blocks.push(encode_hex(&block_hasher.finalize_reset()));
current_block_size = 0;
}
offset = end;
}
}
if current_block_size > 0 || blocks.is_empty() {
blocks.push(encode_hex(&block_hasher.finalize()));
}
Ok(FileIntegrity {
algorithm: ALGORITHM.to_string(),
hash: encode_hex(&file_hasher.finalize()),
block_size: BLOCK_SIZE,
blocks,
})
}
}