use crate::consts::HASH_FIELD_SIZE;
use crate::error::Error;
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC, CRC_64_XZ};
use sha1::Sha1;
use sha2::{Digest, Sha256, Sha512};
use md5::Md5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HashAlgo {
None,
Crc32,
Crc32c,
Crc64,
Md5,
Sha1,
Sha256,
Sha512,
Blake3,
}
impl HashAlgo {
pub fn from_id(id: u8) -> Result<Self, Error> {
Ok(match id {
0 => HashAlgo::None,
1 => HashAlgo::Crc32,
2 => HashAlgo::Crc32c,
3 => HashAlgo::Crc64,
4 => HashAlgo::Md5,
5 => HashAlgo::Sha1,
16 => HashAlgo::Sha256,
17 => HashAlgo::Sha512,
18 => HashAlgo::Blake3,
other => return Err(Error::UnknownHashAlgo(other)),
})
}
pub fn id(self) -> u8 {
match self {
HashAlgo::None => 0,
HashAlgo::Crc32 => 1,
HashAlgo::Crc32c => 2,
HashAlgo::Crc64 => 3,
HashAlgo::Md5 => 4,
HashAlgo::Sha1 => 5,
HashAlgo::Sha256 => 16,
HashAlgo::Sha512 => 17,
HashAlgo::Blake3 => 18,
}
}
pub fn digest_len(self) -> usize {
match self {
HashAlgo::None => 0,
HashAlgo::Crc32 | HashAlgo::Crc32c => 4,
HashAlgo::Crc64 => 8,
HashAlgo::Md5 => 16,
HashAlgo::Sha1 => 20,
HashAlgo::Sha256 | HashAlgo::Blake3 => 32,
HashAlgo::Sha512 => 64,
}
}
pub fn verifies(self) -> bool {
self != HashAlgo::None
}
pub fn compute(self, data: &[u8]) -> [u8; HASH_FIELD_SIZE] {
let mut field = [0u8; HASH_FIELD_SIZE];
match self {
HashAlgo::None => {}
HashAlgo::Crc32 => {
const C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
field[..4].copy_from_slice(&C.checksum(data).to_le_bytes());
}
HashAlgo::Crc32c => {
const C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
field[..4].copy_from_slice(&C.checksum(data).to_le_bytes());
}
HashAlgo::Crc64 => {
const C: Crc<u64> = Crc::<u64>::new(&CRC_64_XZ);
field[..8].copy_from_slice(&C.checksum(data).to_le_bytes());
}
HashAlgo::Md5 => {
let d = Md5::digest(data);
field[..16].copy_from_slice(d.as_slice());
}
HashAlgo::Sha1 => {
let d = Sha1::digest(data);
field[..20].copy_from_slice(d.as_slice());
}
HashAlgo::Sha256 => {
let d = Sha256::digest(data);
field[..32].copy_from_slice(d.as_slice());
}
HashAlgo::Sha512 => {
let d = Sha512::digest(data);
field[..64].copy_from_slice(d.as_slice());
}
HashAlgo::Blake3 => {
field[..32].copy_from_slice(blake3::hash(data).as_bytes());
}
}
field
}
pub fn verify(self, data: &[u8], stored: &[u8; HASH_FIELD_SIZE]) -> bool {
if !self.verifies() {
return true;
}
let computed = self.compute(data);
let n = self.digest_len();
computed[..n] == stored[..n]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crc64_xz_check_value() {
let field = HashAlgo::Crc64.compute(b"123456789");
let mut v = [0u8; 8];
v.copy_from_slice(&field[..8]);
assert_eq!(u64::from_le_bytes(v), 0x995D_C9BB_DF19_39FA);
}
#[test]
fn crc32_iso_hdlc_check_value() {
let field = HashAlgo::Crc32.compute(b"123456789");
let mut v = [0u8; 4];
v.copy_from_slice(&field[..4]);
assert_eq!(u32::from_le_bytes(v), 0xCBF4_3926);
}
#[test]
fn crc32c_check_value() {
let field = HashAlgo::Crc32c.compute(b"123456789");
let mut v = [0u8; 4];
v.copy_from_slice(&field[..4]);
assert_eq!(u32::from_le_bytes(v), 0xE306_9283);
}
#[test]
fn sha256_empty() {
let field = HashAlgo::Sha256.compute(b"");
let expect = [
0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
0x78, 0x52, 0xb8, 0x55,
];
assert_eq!(&field[..32], &expect);
assert!(field[32..].iter().all(|&b| b == 0));
}
#[test]
fn none_is_all_zero_and_skips() {
let field = HashAlgo::None.compute(b"anything");
assert!(field.iter().all(|&b| b == 0));
assert!(HashAlgo::None.verify(b"anything", &[0u8; HASH_FIELD_SIZE]));
}
#[test]
fn roundtrip_ids() {
for a in [
HashAlgo::None,
HashAlgo::Crc32,
HashAlgo::Crc32c,
HashAlgo::Crc64,
HashAlgo::Md5,
HashAlgo::Sha1,
HashAlgo::Sha256,
HashAlgo::Sha512,
HashAlgo::Blake3,
] {
assert_eq!(HashAlgo::from_id(a.id()).unwrap(), a);
}
}
}