use std::fmt;
use std::fmt::Display;
use std::path::Path;
use std::str::FromStr;
use serde::{Deserialize, Serialize, Serializer};
use serde::de::Error;
use const_format::concatcp;
use crate::stages::build::intermediary_build_data::BuildFile;
use crate::path::FilePath;
#[cfg(any(feature = "hash-sha2", feature = "hash-sha1", feature = "hash-xxh"))]
use crate::utils;
#[derive(Debug, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub enum GeneralHashType {
#[cfg(feature = "hash-sha2")]
SHA512,
#[cfg(feature = "hash-sha2")]
SHA256,
#[cfg(feature = "hash-sha1")]
SHA1,
#[cfg(feature = "hash-xxh")]
XXH64,
#[cfg(feature = "hash-xxh")]
XXH32,
NULL,
}
impl GeneralHashType {
pub fn hasher(&self) -> Box<dyn GeneralHasher> {
match self {
#[cfg(feature = "hash-sha2")]
GeneralHashType::SHA512 => Box::new(sha2::Sha512Hasher::new()),
#[cfg(feature = "hash-sha2")]
GeneralHashType::SHA256 => Box::new(sha2::Sha256Hasher::new()),
#[cfg(feature = "hash-sha1")]
GeneralHashType::SHA1 => Box::new(sha1::Sha1Hasher::new()),
#[cfg(feature = "hash-xxh")]
GeneralHashType::XXH64 => Box::new(xxh::Xxh64Hasher::new()),
#[cfg(feature = "hash-xxh")]
GeneralHashType::XXH32 => Box::new(xxh::Xxh32Hasher::new()),
GeneralHashType::NULL => Box::new(null::NullHasher::new()),
}
}
}
impl GeneralHashType {
pub const fn supported_algorithms() -> &'static str {
const SHA2: &'static str = if cfg!(feature = "hash-sha2") { "SHA512, SHA256, " } else { "" };
const SHA1: &'static str = if cfg!(feature = "hash-sha1") { "SHA1, " } else { "" };
const XXH: &'static str = if cfg!(feature = "hash-xxh") { "XXH64, XXH32, " } else { "" };
const NULL: &'static str = "NULL";
concatcp!(SHA2, SHA1, XXH, NULL)
}
}
impl FromStr for GeneralHashType {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
#[cfg(feature = "hash-sha2")]
"SHA512" => Ok(GeneralHashType::SHA512),
#[cfg(feature = "hash-sha2")]
"SHA256" => Ok(GeneralHashType::SHA256),
#[cfg(feature = "hash-sha1")]
"SHA1" => Ok(GeneralHashType::SHA1),
#[cfg(feature = "hash-xxh")]
"XXH64" => Ok(GeneralHashType::XXH64),
#[cfg(feature = "hash-xxh")]
"XXH32" => Ok(GeneralHashType::XXH32),
"NULL" => Ok(GeneralHashType::NULL),
_ => Err(GeneralHashType::supported_algorithms()),
}
}
}
impl Display for GeneralHashType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
#[cfg(feature = "hash-sha2")]
GeneralHashType::SHA512 => write!(f, "SHA512"),
#[cfg(feature = "hash-sha2")]
GeneralHashType::SHA256 => write!(f, "SHA256"),
#[cfg(feature = "hash-sha1")]
GeneralHashType::SHA1 => write!(f, "SHA1"),
#[cfg(feature = "hash-xxh")]
GeneralHashType::XXH64 => write!(f, "XXH64"),
#[cfg(feature = "hash-xxh")]
GeneralHashType::XXH32 => write!(f, "XXH32"),
GeneralHashType::NULL => write!(f, "NULL"),
}
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, PartialOrd)]
pub enum GeneralHash {
#[cfg(feature = "hash-sha2")]
SHA512([u8; 64]),
#[cfg(feature = "hash-sha2")]
SHA256([u8; 32]),
#[cfg(feature = "hash-sha1")]
SHA1([u8; 20]),
#[cfg(feature = "hash-xxh")]
XXH64([u8; 8]),
#[cfg(feature = "hash-xxh")]
XXH32([u8; 4]),
NULL,
}
impl Display for GeneralHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let capacity = match self {
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA512(_) => 128,
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA256(_) => 64,
#[cfg(feature = "hash-sha1")]
GeneralHash::SHA1(_) => 40,
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH64(_) => 16,
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH32(_) => 8,
GeneralHash::NULL => 0,
};
let mut hex = String::with_capacity(capacity + 1 + 6);
hex.push_str((self.hash_type().to_string() + ":").as_str());
match self {
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA512(data) => for byte in data {
hex.push_str(&format!("{:02x}", byte));
},
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA256(data) => for byte in data {
hex.push_str(&format!("{:02x}", byte));
},
#[cfg(feature = "hash-sha1")]
GeneralHash::SHA1(data) => for byte in data {
hex.push_str(&format!("{:02x}", byte));
},
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH64(data) => for byte in data {
hex.push_str(&format!("{:02x}", byte));
},
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH32(data) => for byte in data {
hex.push_str(&format!("{:02x}", byte));
},
GeneralHash::NULL => {
hex.push_str("00");
}
}
write!(f, "{}", hex)
}
}
impl Serialize for GeneralHash {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
serializer.serialize_str(self.to_string().as_str())
}
}
impl FromStr for GeneralHash {
type Err = &'static str;
fn from_str(hex: &str) -> Result<Self, Self::Err> {
let mut iter = hex.split(':');
let hash_type = GeneralHashType::from_str(iter.next().ok_or_else(|| "No hash type")?).map_err(|_| "Failed to parse hash type")?;
#[cfg(any(feature = "hash-sha2", feature = "hash-sha1", feature = "hash-xxh"))]
let data = match hash_type {
GeneralHashType::NULL => Vec::new(),
_ => {
let data = iter.next().ok_or_else(|| "No hash data")?;
utils::decode_hex(data).map_err(|_| "Failed to decode hash data")?
}
};
let mut hash = GeneralHash::from_type(hash_type);
match &mut hash {
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA512(target_data) => {
if data.len() != 64 {
return Err("Invalid data length");
}
target_data.copy_from_slice(&data);
},
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA256(target_data) => {
if data.len() != 32 {
return Err("Invalid data length");
}
target_data.copy_from_slice(&data);
},
#[cfg(feature = "hash-sha1")]
GeneralHash::SHA1(target_data) => {
if data.len() != 20 {
return Err("Invalid data length");
}
target_data.copy_from_slice(&data);
},
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH64(target_data) => {
if data.len() != 8 {
return Err("Invalid data length");
}
target_data.copy_from_slice(&data);
},
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH32(target_data) => {
if data.len() != 4 {
return Err("Invalid data length");
}
target_data.copy_from_slice(&data);
},
GeneralHash::NULL => {}
}
Ok(hash)
}
}
impl<'de> Deserialize<'de> for GeneralHash {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
let hex = String::deserialize(deserializer)?;
GeneralHash::from_str(hex.as_str()).map_err(D::Error::custom)
}
}
impl GeneralHash {
pub fn as_bytes(&self) -> &[u8] {
match self {
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA512(data) => data,
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA256(data) => data,
#[cfg(feature = "hash-sha1")]
GeneralHash::SHA1(data) => data,
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH64(data) => data,
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH32(data) => data,
GeneralHash::NULL => &[0; 0],
}
}
#[cfg(feature = "hash-sha2")]
pub fn new_sha512() -> Self { Self::from_type(GeneralHashType::SHA512) }
#[cfg(feature = "hash-sha2")]
pub fn new_sha256() -> Self { Self::from_type(GeneralHashType::SHA256) }
#[cfg(feature = "hash-sha1")]
pub fn new_sha1() -> Self { Self::from_type(GeneralHashType::SHA1) }
#[cfg(feature = "hash-xxh")]
pub fn new_xxh64() -> Self { Self::from_type(GeneralHashType::XXH64) }
#[cfg(feature = "hash-xxh")]
pub fn new_xxh32() -> Self { Self::from_type(GeneralHashType::XXH32) }
pub fn hash_type(&self) -> GeneralHashType {
match self {
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA512(_) => GeneralHashType::SHA512,
#[cfg(feature = "hash-sha2")]
GeneralHash::SHA256(_) => GeneralHashType::SHA256,
#[cfg(feature = "hash-sha1")]
GeneralHash::SHA1(_) => GeneralHashType::SHA1,
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH64(_) => GeneralHashType::XXH64,
#[cfg(feature = "hash-xxh")]
GeneralHash::XXH32(_) => GeneralHashType::XXH32,
GeneralHash::NULL => GeneralHashType::NULL,
}
}
pub fn from_type(hash_type: GeneralHashType) -> Self {
match hash_type {
#[cfg(feature = "hash-sha2")]
GeneralHashType::SHA512 => GeneralHash::SHA512([0; 64]),
#[cfg(feature = "hash-sha2")]
GeneralHashType::SHA256 => GeneralHash::SHA256([0; 32]),
#[cfg(feature = "hash-sha1")]
GeneralHashType::SHA1 => GeneralHash::SHA1([0; 20]),
#[cfg(feature = "hash-xxh")]
GeneralHashType::XXH64 => GeneralHash::XXH64([0; 8]),
#[cfg(feature = "hash-xxh")]
GeneralHashType::XXH32 => GeneralHash::XXH32([0; 4]),
GeneralHashType::NULL => GeneralHash::NULL,
}
}
pub fn hasher(&self) -> Box<dyn GeneralHasher> {
self.hash_type().hasher()
}
pub fn hash_file<T>(&mut self, mut reader: T) -> anyhow::Result<u64>
where T: std::io::Read {
let mut hasher = self.hasher();
let mut buffer = [0; 4096];
let mut content_size = 0;
loop {
let bytes_read = reader.read(&mut buffer)?;
content_size += bytes_read as u64;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
*self = hasher.finalize();
Ok(content_size)
}
pub fn hash_directory<'a>(&mut self, children: impl Iterator<Item = &'a BuildFile>) -> anyhow::Result<u64> {
let mut hasher = self.hasher();
let mut content_size = 0;
for child in children {
content_size += 1;
hasher.update(child.get_content_hash().as_bytes());
}
*self = hasher.finalize();
Ok(content_size)
}
pub fn hash_path(&mut self, path: &Path) -> anyhow::Result<()> {
let mut hasher = self.hasher();
hasher.update(path.as_os_str().as_encoded_bytes());
*self = hasher.finalize();
Ok(())
}
pub fn hash_filepath(&mut self, path: &FilePath) -> anyhow::Result<()> {
let mut hasher = self.hasher();
for component in &path.path {
hasher.update(component.path.as_os_str().as_encoded_bytes());
}
*self = hasher.finalize();
Ok(())
}
}
pub trait GeneralHasher {
fn new() -> Self where Self: Sized;
fn update(&mut self, data: &[u8]);
fn finalize(self: Box<Self>) -> GeneralHash;
}
#[cfg(feature = "hash-sha1")]
mod sha1;
#[cfg(feature = "hash-sha2")]
mod sha2;
#[cfg(feature = "hash-xxh")]
mod xxh;
mod null;