use std::{
fs::File,
io::{Read, Seek, SeekFrom},
ops::RangeInclusive,
path::Path,
};
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use multibase::{decode, encode};
use multihash::{wrap, Code, Multihash, Sha2_256, Sha2_512, Sha3_256, Sha3_384, Sha3_512};
use range_set::RangeSet;
use sha2::{Digest, Sha256, Sha384, Sha512};
use crate::{Error, Result};
const MAX_HASH_BUF: usize = 1024 * 1024 * 1024;
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct Exclusion {
start: usize,
length: usize,
}
impl Exclusion {
pub fn new(start: usize, length: usize) -> Self {
Exclusion { start, length }
}
#[allow(dead_code)]
pub fn set_start(&mut self, start: usize) {
self.start = start;
}
pub fn start(&self) -> usize {
self.start
}
pub fn length(&self) -> usize {
self.length
}
}
pub fn vec_compare(va: &[u8], vb: &[u8]) -> bool {
(va.len() == vb.len()) && va.iter()
.zip(vb)
.all(|(a,b)| a == b)
}
pub fn hash_by_type(hash_type: u8, data: &[u8]) -> Option<Multihash> {
match hash_type {
0x12 => Some(Sha2_256::digest(data)),
0x13 => Some(Sha2_512::digest(data)),
0x14 => Some(Sha3_512::digest(data)),
0x15 => Some(Sha3_384::digest(data)),
0x16 => Some(Sha3_256::digest(data)),
_ => None,
}
}
#[derive(Clone)]
enum Hasher {
SHA256(Sha256),
SHA384(Sha384),
SHA512(Sha512),
}
impl Hasher {
fn update(&mut self, data: &[u8]) {
use Hasher::*;
match self {
SHA256(ref mut d) => d.update(data),
SHA384(ref mut d) => d.update(data),
SHA512(ref mut d) => d.update(data),
}
}
fn finalize(hasher_enum: Hasher) -> Vec<u8> {
use Hasher::*;
match hasher_enum {
SHA256(d) => d.finalize().to_vec(),
SHA384(d) => d.finalize().to_vec(),
SHA512(d) => d.finalize().to_vec(),
}
}
}
pub fn hash_by_alg(alg: &str, data: &[u8], exclusions: Option<Vec<Exclusion>>) -> Vec<u8> {
use Hasher::*;
let mut hasher_enum = match alg {
"sha256" => SHA256(Sha256::new()),
"sha384" => SHA384(Sha384::new()),
"sha512" => SHA512(Sha512::new()),
_ => {
warn!(
"Unsupported hashing algorithm: {}, substituting sha256",
alg
);
SHA256(Sha256::new())
}
};
match exclusions {
Some(mut e) if !e.is_empty() => {
e.sort_by_key(|a| a.start());
let num_blocks = e.len();
let exclusion_end = e[num_blocks - 1].start() + e[num_blocks - 1].length();
let data_len = data.len();
let data_end = data_len - 1;
if data_len < exclusion_end {
debug!("the exclusion range exceed the data length");
return Vec::new();
}
let mut ranges = RangeSet::<[RangeInclusive<usize>; 1]>::from(0..=data_end);
for exclusion in e {
let end = exclusion.start() + exclusion.length() - 1;
ranges.remove_range(exclusion.start()..=end);
}
for r in ranges.into_smallvec() {
hasher_enum.update(&data[r]);
}
Hasher::finalize(hasher_enum)
}
_ => {
hasher_enum.update(data);
Hasher::finalize(hasher_enum)
}
}
}
pub fn hash_asset_by_alg(
alg: &str,
asset_path: &Path,
exclusions: Option<Vec<Exclusion>>,
) -> Result<Vec<u8>> {
use Hasher::*;
let mut hasher_enum = match alg {
"sha256" => SHA256(Sha256::new()),
"sha384" => SHA384(Sha384::new()),
"sha512" => SHA512(Sha512::new()),
_ => {
warn!(
"Unsupported hashing algorithm: {}, substituting sha256",
alg
);
SHA256(Sha256::new())
}
};
let mut data = File::open(asset_path)?;
let data_len = data.seek(SeekFrom::End(0))?;
data.seek(SeekFrom::Start(0))?;
let ranges = match exclusions {
Some(mut e) if !e.is_empty() => {
e.sort_by_key(|a| a.start());
let num_blocks = e.len();
let exclusion_end = e[num_blocks - 1].start() + e[num_blocks - 1].length();
let data_end = data_len - 1;
if data_len < exclusion_end as u64 {
return Err(Error::BadParam(
"The exclusion range exceed the data length".to_string(),
));
}
let mut ranges = RangeSet::<[RangeInclusive<u64>; 1]>::from(0..=data_end);
for exclusion in e {
let end = (exclusion.start() + exclusion.length() - 1) as u64;
let exclusion_start = exclusion.start() as u64;
ranges.remove_range(exclusion_start..=end);
}
ranges
}
_ => {
let data_end = data_len - 1;
RangeSet::<[RangeInclusive<u64>; 1]>::from(0..=data_end)
}
};
if cfg!(feature = "no_interleaved_io") {
for r in ranges.into_smallvec() {
let start = r.start();
let end = r.end();
let mut chunk_left = end - start + 1;
data.seek(SeekFrom::Start(*start))?;
loop {
let mut chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
data.read_exact(&mut chunk)?;
hasher_enum.update(&chunk);
chunk_left -= chunk.len() as u64;
if chunk_left == 0 {
break;
}
}
}
} else {
for r in ranges.into_smallvec() {
let start = r.start();
let end = r.end();
let mut chunk_left = end - start + 1;
data.seek(SeekFrom::Start(*start))?;
let mut chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
data.read_exact(&mut chunk)?;
loop {
let (tx, rx) = std::sync::mpsc::channel();
chunk_left -= chunk.len() as u64;
std::thread::spawn(move || {
hasher_enum.update(&chunk);
tx.send(hasher_enum).unwrap_or_default();
});
if chunk_left == 0 {
hasher_enum = match rx.recv() {
Ok(hasher) => hasher,
Err(_) => return Err(Error::ThreadReceiveError),
};
break;
}
let mut next_chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
data.read_exact(&mut next_chunk)?;
hasher_enum = match rx.recv() {
Ok(hasher) => hasher,
Err(_) => return Err(Error::ThreadReceiveError),
};
chunk = next_chunk;
}
}
}
Ok(Hasher::finalize(hasher_enum))
}
pub fn verify_by_alg(
alg: &str,
hash: &[u8],
data: &[u8],
exclusions: Option<Vec<Exclusion>>,
) -> bool {
let data_hash = hash_by_alg(alg, data, exclusions);
vec_compare(hash, &data_hash)
}
pub fn verify_asset_by_alg(
alg: &str,
hash: &[u8],
asset_path: &Path,
exclusions: Option<Vec<Exclusion>>,
) -> bool {
if let Ok(data_hash) = hash_asset_by_alg(alg, asset_path, exclusions) {
vec_compare(hash, &data_hash)
} else {
false
}
}
#[allow(dead_code)]
pub fn hash256(data: &[u8]) -> String {
let mh = Sha2_256::digest(data);
let digest = mh.digest();
let wrapped: Multihash = wrap(Code::Sha2_256, digest);
encode(multibase::Base::Base64, wrapped.as_bytes())
}
pub fn verify_hash(hash: &str, data: &[u8]) -> bool {
match decode(hash) {
Ok((_code, mh)) => {
if mh.len() < 2 {
return false;
}
let hash_type = mh[0]; let _hash_len = mh[1];
if let Some(data_hash) = hash_by_type(hash_type, data) {
vec_compare(data_hash.digest(), &mh.as_slice()[2..])
} else {
false
}
}
Err(_) => false,
}
}
pub fn blake3_from_asset(path: &Path) -> Result<String> {
let mut data = File::open(path)?;
data.seek(SeekFrom::Start(0))?;
let data_len = data.seek(SeekFrom::End(0))?;
data.seek(SeekFrom::Start(0))?;
let mut hasher = blake3::Hasher::new();
let mut chunk_left = data_len;
if cfg!(feature = "no_interleaved_io") {
loop {
let mut chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
data.read_exact(&mut chunk)?;
hasher.update(&chunk);
chunk_left -= chunk.len() as u64;
if chunk_left == 0 {
break;
}
}
} else {
let mut chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
data.read_exact(&mut chunk)?;
loop {
let (tx, rx) = std::sync::mpsc::channel();
chunk_left -= chunk.len() as u64;
std::thread::spawn(move || {
hasher.update(&chunk);
tx.send(hasher).unwrap_or_default();
});
if chunk_left == 0 {
hasher = match rx.recv() {
Ok(hasher) => hasher,
Err(_) => return Err(Error::ThreadReceiveError),
};
break;
}
let mut next_chunk = vec![0u8; std::cmp::min(chunk_left as usize, MAX_HASH_BUF)];
data.read_exact(&mut next_chunk)?;
hasher = match rx.recv() {
Ok(hasher) => hasher,
Err(_) => return Err(Error::ThreadReceiveError),
};
chunk = next_chunk;
}
}
let hash = hasher.finalize();
Ok(hash.to_hex().as_str().to_owned())
}
pub fn hash_as_source(in_hash: &str, data: &[u8]) -> Option<String> {
match decode(in_hash) {
Ok((code, mh)) => {
if mh.len() < 2 {
return None;
}
let hash_type = mh[0];
match hash_by_type(hash_type, data) {
Some(hash) => {
let digest = hash.digest();
let wrapped = match hash_type {
0x12 => wrap(Code::Sha2_256, digest),
0x13 => wrap(Code::Sha2_512, digest),
0x14 => wrap(Code::Sha3_512, digest),
0x15 => wrap(Code::Sha3_384, digest),
0x16 => wrap(Code::Sha3_256, digest),
_ => return None,
};
Some(encode(code, wrapped.as_bytes()))
}
None => None,
}
}
Err(_) => None,
}
}