#[cfg(feature = "digest")]
use sha2::{Digest, Sha256};
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
const CHUNK: usize = 64 * 1024;
pub fn hex(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
out.push_str(&format!("{b:02x}"));
}
out
}
pub fn is_hex(text: &str) -> bool {
text.bytes().all(|b| b.is_ascii_hexdigit())
}
pub fn unhex(text: &str) -> Option<Vec<u8>> {
if !is_hex(text) || !text.len().is_multiple_of(2) {
return None;
}
text.as_bytes()
.chunks(2)
.map(|pair| {
let high = (pair[0] as char).to_digit(16)?;
let low = (pair[1] as char).to_digit(16)?;
Some(((high << 4) | low) as u8)
})
.collect()
}
#[cfg(feature = "digest")]
#[derive(Clone, Default)]
pub struct Sha256Hasher(Sha256);
#[cfg(feature = "digest")]
impl Sha256Hasher {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, bytes: &[u8]) {
self.0.update(bytes);
}
pub fn finalize(self) -> String {
hex(self.0.finalize().as_slice())
}
pub fn finalize_bytes(self) -> [u8; 32] {
self.0.finalize().into()
}
}
#[cfg(feature = "digest")]
impl io::Write for Sha256Hasher {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.update(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(feature = "digest")]
pub fn sha256_bytes(bytes: &[u8]) -> String {
hex(Sha256::digest(bytes).as_slice())
}
#[cfg(feature = "digest")]
pub fn sha256_reader<R: Read>(reader: &mut R) -> io::Result<String> {
sha256_reader_counted(reader).map(|(digest, _)| digest)
}
#[cfg(feature = "digest")]
pub fn sha256_file(path: &Path) -> io::Result<String> {
sha256_file_with_len(path).map(|(digest, _)| digest)
}
#[cfg(feature = "digest")]
pub fn sha256_file_with_len(path: &Path) -> io::Result<(String, u64)> {
let mut file = File::open(path)?;
sha256_reader_counted(&mut file)
}
#[cfg(feature = "digest")]
fn sha256_reader_counted<R: Read>(reader: &mut R) -> io::Result<(String, u64)> {
let mut hasher = Sha256::new();
let mut buffer = vec![0u8; CHUNK];
let mut total = 0u64;
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
total += n as u64;
}
Ok((hex(hasher.finalize().as_slice()), total))
}
#[cfg(feature = "blake3")]
#[derive(Clone, Default)]
pub struct Blake3Hasher(blake3::Hasher);
#[cfg(feature = "blake3")]
impl Blake3Hasher {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, bytes: &[u8]) {
self.0.update(bytes);
}
pub fn finalize(self) -> String {
self.0.finalize().to_hex().to_string()
}
pub fn finalize_bytes(self) -> [u8; 32] {
*self.0.finalize().as_bytes()
}
}
#[cfg(feature = "blake3")]
impl io::Write for Blake3Hasher {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.update(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[cfg(feature = "blake3")]
pub fn blake3_bytes(bytes: &[u8]) -> String {
blake3::hash(bytes).to_hex().to_string()
}
#[cfg(feature = "blake3")]
pub fn blake3_reader<R: Read>(reader: &mut R) -> io::Result<String> {
blake3_reader_counted(reader, &mut |_| {}).map(|(digest, _)| digest)
}
#[cfg(feature = "blake3")]
pub fn blake3_file(path: &Path) -> io::Result<String> {
blake3_file_with_len(path).map(|(digest, _)| digest)
}
#[cfg(feature = "blake3")]
pub fn blake3_file_with_len(path: &Path) -> io::Result<(String, u64)> {
blake3_file_with_progress(path, |_, _| {})
}
#[cfg(feature = "blake3")]
pub fn blake3_file_with_progress(
path: &Path,
mut progress: impl FnMut(u64, u64),
) -> io::Result<(String, u64)> {
let mut file = File::open(path)?;
let length = file.metadata()?.len();
progress(0, length);
blake3_reader_counted(&mut file, &mut |bytes| progress(bytes, length))
}
#[cfg(feature = "blake3")]
fn blake3_reader_counted<R: Read>(
reader: &mut R,
progress: &mut dyn FnMut(u64),
) -> io::Result<(String, u64)> {
let mut hasher = Blake3Hasher::new();
let mut buffer = vec![0; CHUNK];
let mut total = 0;
loop {
let n = match reader.read(&mut buffer) {
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
result => result?,
};
if n == 0 {
break;
}
hasher.update(&buffer[..n]);
total += n as u64;
progress(total);
}
Ok((hasher.finalize(), total))
}