Files
hashstream/src/hashes.rs
2025-08-19 00:57:38 -04:00

309 lines
10 KiB
Rust

mod blake256;
mod blake3;
mod blake512;
mod crc32;
pub use crc32::CRC32TYPE;
mod md5;
mod null_hash;
mod sha1;
mod sha256;
mod sha3_256;
use std::sync::Arc;
type BITS512 = [u8; 64];
type BITS256 = [u8; 32];
type BITS160 = [u8; 20];
type BITS128 = [u8; 16];
type BITS32 = u32;
type ArcU8 = Arc<[u8]>;
macro_rules! arc_u8_sized {
($size: literal) => {
Arc::new([0u8, $size])
};
}
fn arc_u8_empty() -> ArcU8 {
arc_u8_sized!(0)
}
fn bits512_default() -> BITS512 {
[0; 64]
}
#[derive(Debug, PartialEq)]
pub enum HashReturn {
CRC32(BITS32, crc32::CRC32TYPE),
SHA256(BITS256),
SHA3_256(BITS256),
BLAKE256(BITS256),
BLAKE512(BITS512),
BLAKE3(BITS256),
SHA1(BITS160),
MD5(BITS128),
RAW(ArcU8),
}
impl HashReturn {
pub fn into_bytes(self) -> Arc<[u8]> {
match self {
HashReturn::CRC32(inner, _) => Arc::new(inner.to_be_bytes()),
HashReturn::SHA256(inner) => Arc::new(inner),
HashReturn::SHA3_256(inner) => Arc::new(inner),
HashReturn::BLAKE256(inner) => Arc::new(inner),
HashReturn::BLAKE512(inner) => Arc::new(inner),
HashReturn::BLAKE3(inner) => Arc::new(inner),
HashReturn::SHA1(inner) => Arc::new(inner),
HashReturn::MD5(inner) => Arc::new(inner),
HashReturn::RAW(inner) => inner,
}
}
pub fn as_hex(self) -> String {
crate::u8_array_to_lower_hex_string(&self.into_bytes()).unwrap()
}
pub fn serialize(self) -> String {
match self {
HashReturn::CRC32(_, t) => {
format!("crc32_{t:?}: {}", self.as_hex())
}
HashReturn::SHA256(_) => {
format!("sha256: {}", self.as_hex())
}
HashReturn::SHA3_256(_) => {
format!("sha3_256: {}", self.as_hex())
}
HashReturn::BLAKE256(_) => {
format!("blake256: {}", self.as_hex())
}
HashReturn::BLAKE512(_) => {
format!("blake512: {}", self.as_hex())
}
HashReturn::BLAKE3(_) => {
format!("blake3: {}", self.as_hex())
}
HashReturn::SHA1(_) => {
format!("sha1: {}", self.as_hex())
}
HashReturn::MD5(_) => {
format!("md5: {}", self.as_hex())
}
HashReturn::RAW(_) => {
format!("raw: {}", self.as_hex())
}
}
}
}
trait Hasher {
fn new() -> Self;
fn digest(&mut self, bytes: impl AsRef<[u8]>);
fn complete(self) -> HashReturn;
}
#[derive(Debug, Copy, Clone)]
pub enum HashType {
CRC32(crc32::CRC32TYPE),
SHA256,
SHA3_256,
BLAKE256,
BLAKE512,
BLAKE3,
SHA1,
MD5,
Null,
}
// Why? because static dispatch is why!
// TODO: Make the concrete types return this too
// Maybe even a macro that does the below boilerplate? idk
enum HasherHolder {
CRC32(crc32::CRC32),
SHA256(sha256::SHA256),
SHA3_256(sha3_256::SHA3_256),
BLAKE256(blake256::BLAKE256),
BLAKE512(blake512::BLAKE512),
BLAKE3(blake3::BLAKE3),
SHA1(sha1::SHA1),
MD5(md5::MD5),
NULL(null_hash::NullHash),
}
pub struct Hashes {
hashers: Vec<HasherHolder>,
}
impl Hashes {
pub fn new(types: &[HashType]) -> Self {
let mut hashers = Vec::with_capacity(types.len());
for t in types {
match t {
HashType::CRC32(t) => match t {
crc32::CRC32TYPE::ISO => hashers.push(HasherHolder::CRC32(crc32::crc32_iso())),
crc32::CRC32TYPE::CKSUM => {
hashers.push(HasherHolder::CRC32(crc32::crc32_cksum()))
}
crc32::CRC32TYPE::XFER => {
hashers.push(HasherHolder::CRC32(crc32::crc32_xfer()))
}
},
HashType::SHA256 => hashers.push(HasherHolder::SHA256(sha256::SHA256::new())),
HashType::SHA3_256 => {
hashers.push(HasherHolder::SHA3_256(sha3_256::SHA3_256::new()))
}
HashType::BLAKE256 => {
hashers.push(HasherHolder::BLAKE256(blake256::BLAKE256::new()))
}
HashType::BLAKE512 => {
hashers.push(HasherHolder::BLAKE512(blake512::BLAKE512::new()))
}
HashType::BLAKE3 => hashers.push(HasherHolder::BLAKE3(blake3::BLAKE3::new())),
HashType::SHA1 => hashers.push(HasherHolder::SHA1(sha1::SHA1::new())),
HashType::MD5 => hashers.push(HasherHolder::MD5(md5::MD5::new())),
HashType::Null => hashers.push(HasherHolder::NULL(null_hash::NullHash::new())),
}
}
Hashes { hashers }
}
pub fn digest(&mut self, bytes: impl AsRef<[u8]>) {
for hasher in &mut self.hashers {
match hasher {
HasherHolder::CRC32(hasher) => hasher.digest(&bytes),
HasherHolder::SHA256(hasher) => hasher.digest(&bytes),
HasherHolder::SHA3_256(hasher) => hasher.digest(&bytes),
HasherHolder::BLAKE256(hasher) => hasher.digest(&bytes),
HasherHolder::BLAKE512(hasher) => hasher.digest(&bytes),
HasherHolder::BLAKE3(hasher) => hasher.digest(&bytes),
HasherHolder::SHA1(hasher) => hasher.digest(&bytes),
HasherHolder::MD5(hasher) => hasher.digest(&bytes),
HasherHolder::NULL(hasher) => hasher.digest(&bytes),
}
}
}
pub fn complete(mut self) -> Vec<HashReturn> {
let mut ret = Vec::with_capacity(self.hashers.len());
for hasher in self.hashers {
match hasher {
HasherHolder::CRC32(hasher) => ret.push(hasher.complete()),
HasherHolder::SHA256(hasher) => ret.push(hasher.complete()),
HasherHolder::SHA3_256(hasher) => ret.push(hasher.complete()),
HasherHolder::BLAKE256(hasher) => ret.push(hasher.complete()),
HasherHolder::BLAKE512(hasher) => ret.push(hasher.complete()),
HasherHolder::BLAKE3(hasher) => ret.push(hasher.complete()),
HasherHolder::SHA1(hasher) => ret.push(hasher.complete()),
HasherHolder::MD5(hasher) => ret.push(hasher.complete()),
HasherHolder::NULL(hasher) => ret.push(hasher.complete()),
}
}
ret
}
}
#[cfg(test)]
mod test {
use crate::hashes::crc32::CRC32TYPE;
use crate::hashes::{HashReturn, HashType, Hashes};
#[test]
fn test_multi_hash() {
let hash_types = [
HashType::CRC32(CRC32TYPE::CKSUM),
HashType::CRC32(CRC32TYPE::ISO),
HashType::CRC32(CRC32TYPE::XFER),
HashType::SHA256,
HashType::SHA3_256,
HashType::BLAKE256,
HashType::BLAKE512,
HashType::BLAKE3,
HashType::SHA1,
HashType::MD5,
//HashType::Null,
];
let mut hashes = Hashes::new(&hash_types);
hashes.digest("HashStream2025");
let ret = hashes.complete();
// Not using .into_bytes() to properly check each hash type
for hash in ret {
match hash {
HashReturn::CRC32(hash, t) => {
let hash_out =
crate::hex_table::u8_array_to_lower_hex_string(&hash.to_be_bytes())
.unwrap();
match t {
CRC32TYPE::CKSUM => {
assert_eq!(hash_out, "5f4f456d");
}
CRC32TYPE::XFER => {
assert_eq!(hash_out, "99e560da");
}
CRC32TYPE::ISO => {
assert_eq!(hash_out, "66eac774");
}
}
}
HashReturn::SHA256(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(
hash_out,
"9975cc7fe9ae0c307eae57ac62a099f07749d0d7d01362e92435941ca18a635e"
);
}
HashReturn::SHA3_256(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(
hash_out,
"47eec6a5beaf06f0bf5f0ee06f0cae374fd46b968849d2b65de2d8623e907e16"
);
}
HashReturn::BLAKE256(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(
hash_out,
"850e58e95935ccde7320cc41ff99c41877b4e8956f6c5c09d0a5afd40c659519"
);
}
HashReturn::BLAKE512(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(
hash_out,
"29f9c9c6f598e570fa6d036f68bc9e22d34cab2359a1dcf738d9a32a98721a5f0b7de34f20429c814529d3fac96e68a2d37460aeea355a3712e30f601ea3e607"
);
}
HashReturn::BLAKE3(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(
hash_out,
"6323d7085cd64ef68d16b2b53ec50de2b65f0f6e9d6fc44833de8925790eb3e6"
);
}
HashReturn::SHA1(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(hash_out, "d07e867570b94d81178a644e4b0359dc354872f1");
}
HashReturn::MD5(hash) => {
let hash_out = crate::hex_table::u8_array_to_lower_hex_string(&hash).unwrap();
assert_eq!(hash_out, "e001d16d3d95cf04ec4a86ecb4c5ab6a");
}
HashReturn::RAW(_) => {
todo!("Shouldn't be here for this test...")
}
}
}
}
}