use std::io::Write;
use hex::encode;
#[cfg(windows)]
use memchr::memmem;
pub trait Digest {
fn new() -> Self
where
Self: Sized;
fn hash_update(&mut self, input: &[u8]);
fn hash_finalize(&mut self, out: &mut [u8]);
fn reset(&mut self);
fn output_bits(&self) -> usize;
fn output_bytes(&self) -> usize {
self.output_bits().div_ceil(8)
}
fn result_str(&mut self) -> String {
let mut buf: Vec<u8> = vec![0; self.output_bytes()];
self.hash_finalize(&mut buf);
encode(buf)
}
}
pub struct Blake2b(blake2b_simd::State, usize);
impl Blake2b {
pub fn with_output_bytes(output_bytes: usize) -> Self {
let mut params = blake2b_simd::Params::new();
params.hash_length(output_bytes);
let state = params.to_state();
Self(state, output_bytes * 8)
}
}
impl Digest for Blake2b {
fn new() -> Self {
Self::with_output_bytes(64)
}
fn hash_update(&mut self, input: &[u8]) {
self.0.update(input);
}
fn hash_finalize(&mut self, out: &mut [u8]) {
let hash_result = &self.0.finalize();
out.copy_from_slice(hash_result.as_bytes());
}
fn reset(&mut self) {
*self = Self::with_output_bytes(self.output_bytes());
}
fn output_bits(&self) -> usize {
self.1
}
}
pub struct Blake3(blake3::Hasher);
impl Digest for Blake3 {
fn new() -> Self {
Self(blake3::Hasher::new())
}
fn hash_update(&mut self, input: &[u8]) {
self.0.update(input);
}
fn hash_finalize(&mut self, out: &mut [u8]) {
let hash_result = &self.0.finalize();
out.copy_from_slice(hash_result.as_bytes());
}
fn reset(&mut self) {
*self = Self::new();
}
fn output_bits(&self) -> usize {
256
}
}
pub struct Sm3(sm3::Sm3);
impl Digest for Sm3 {
fn new() -> Self {
Self(<sm3::Sm3 as sm3::Digest>::new())
}
fn hash_update(&mut self, input: &[u8]) {
<sm3::Sm3 as sm3::Digest>::update(&mut self.0, input);
}
fn hash_finalize(&mut self, out: &mut [u8]) {
out.copy_from_slice(&<sm3::Sm3 as sm3::Digest>::finalize(self.0.clone()));
}
fn reset(&mut self) {
*self = Self::new();
}
fn output_bits(&self) -> usize {
256
}
}
pub struct Crc {
digest: crc_fast::Digest,
size: usize,
}
impl Crc {
fn get_posix_cksum_params() -> crc_fast::CrcParams {
crc_fast::CrcParams::new(
"CRC-32/CKSUM", 32, 0x04c11db7, 0x00000000, false, 0xffffffff, 0, )
}
}
impl Digest for Crc {
fn new() -> Self {
Self {
digest: crc_fast::Digest::new_with_params(Self::get_posix_cksum_params()),
size: 0,
}
}
fn hash_update(&mut self, input: &[u8]) {
self.digest.update(input);
self.size += input.len();
}
fn hash_finalize(&mut self, out: &mut [u8]) {
let mut sz = self.size;
while sz > 0 {
self.digest.update(&[sz as u8]);
sz >>= 8;
}
out.copy_from_slice(&self.digest.finalize().to_ne_bytes());
}
fn result_str(&mut self) -> String {
let mut out: [u8; 8] = [0; 8];
self.hash_finalize(&mut out);
u64::from_ne_bytes(out).to_string()
}
fn reset(&mut self) {
self.digest.reset();
self.size = 0;
}
fn output_bits(&self) -> usize {
256
}
}
pub struct CRC32B {
digest: crc_fast::Digest,
}
impl Digest for CRC32B {
fn new() -> Self {
Self {
digest: crc_fast::Digest::new(crc_fast::CrcAlgorithm::Crc32IsoHdlc),
}
}
fn hash_update(&mut self, input: &[u8]) {
self.digest.update(input);
}
fn hash_finalize(&mut self, out: &mut [u8]) {
let result = self.digest.finalize();
let crc32_value = (result & 0xffffffff) as u32;
out.copy_from_slice(&crc32_value.to_be_bytes());
}
fn reset(&mut self) {
self.digest.reset();
}
fn output_bits(&self) -> usize {
32
}
fn result_str(&mut self) -> String {
let mut out = [0; 4];
self.hash_finalize(&mut out);
format!("{}", u32::from_be_bytes(out))
}
}
pub struct Bsd {
state: u16,
}
impl Digest for Bsd {
fn new() -> Self {
Self { state: 0 }
}
fn hash_update(&mut self, input: &[u8]) {
for &byte in input {
self.state = (self.state >> 1) + ((self.state & 1) << 15);
self.state = self.state.wrapping_add(u16::from(byte));
}
}
fn hash_finalize(&mut self, out: &mut [u8]) {
out.copy_from_slice(&self.state.to_ne_bytes());
}
fn result_str(&mut self) -> String {
let mut _out: Vec<u8> = vec![0; 2];
self.hash_finalize(&mut _out);
format!("{}", self.state)
}
fn reset(&mut self) {
*self = Self::new();
}
fn output_bits(&self) -> usize {
128
}
}
pub struct SysV {
state: u32,
}
impl Digest for SysV {
fn new() -> Self {
Self { state: 0 }
}
fn hash_update(&mut self, input: &[u8]) {
for &byte in input {
self.state = self.state.wrapping_add(u32::from(byte));
}
}
fn hash_finalize(&mut self, out: &mut [u8]) {
self.state = (self.state & 0xffff) + (self.state >> 16);
self.state = (self.state & 0xffff) + (self.state >> 16);
out.copy_from_slice(&(self.state as u16).to_ne_bytes());
}
fn result_str(&mut self) -> String {
let mut _out: Vec<u8> = vec![0; 2];
self.hash_finalize(&mut _out);
format!("{}", self.state)
}
fn reset(&mut self) {
*self = Self::new();
}
fn output_bits(&self) -> usize {
512
}
}
macro_rules! impl_digest_common {
($algo_type: ty, $size: expr) => {
impl Digest for $algo_type {
fn new() -> Self {
Self(Default::default())
}
fn hash_update(&mut self, input: &[u8]) {
digest::Digest::update(&mut self.0, input);
}
fn hash_finalize(&mut self, out: &mut [u8]) {
digest::Digest::finalize_into_reset(&mut self.0, out.into());
}
fn reset(&mut self) {
*self = Self::new();
}
fn output_bits(&self) -> usize {
$size
}
}
};
}
macro_rules! impl_digest_shake {
($algo_type: ty) => {
impl Digest for $algo_type {
fn new() -> Self {
Self(Default::default())
}
fn hash_update(&mut self, input: &[u8]) {
digest::Update::update(&mut self.0, input);
}
fn hash_finalize(&mut self, out: &mut [u8]) {
digest::ExtendableOutputReset::finalize_xof_reset_into(&mut self.0, out);
}
fn reset(&mut self) {
*self = Self::new();
}
fn output_bits(&self) -> usize {
0
}
}
};
}
pub struct Md5(md5::Md5);
pub struct Sha1(sha1::Sha1);
pub struct Sha224(sha2::Sha224);
pub struct Sha256(sha2::Sha256);
pub struct Sha384(sha2::Sha384);
pub struct Sha512(sha2::Sha512);
impl_digest_common!(Md5, 128);
impl_digest_common!(Sha1, 160);
impl_digest_common!(Sha224, 224);
impl_digest_common!(Sha256, 256);
impl_digest_common!(Sha384, 384);
impl_digest_common!(Sha512, 512);
pub struct Sha3_224(sha3::Sha3_224);
pub struct Sha3_256(sha3::Sha3_256);
pub struct Sha3_384(sha3::Sha3_384);
pub struct Sha3_512(sha3::Sha3_512);
impl_digest_common!(Sha3_224, 224);
impl_digest_common!(Sha3_256, 256);
impl_digest_common!(Sha3_384, 384);
impl_digest_common!(Sha3_512, 512);
pub struct Shake128(sha3::Shake128);
pub struct Shake256(sha3::Shake256);
impl_digest_shake!(Shake128);
impl_digest_shake!(Shake256);
pub struct DigestWriter<'a> {
digest: &'a mut Box<dyn Digest>,
#[allow(dead_code)]
binary: bool,
#[allow(dead_code)]
was_last_character_carriage_return: bool,
}
impl<'a> DigestWriter<'a> {
pub fn new(digest: &'a mut Box<dyn Digest>, binary: bool) -> Self {
let was_last_character_carriage_return = false;
DigestWriter {
digest,
binary,
was_last_character_carriage_return,
}
}
pub fn finalize(&mut self) -> bool {
if self.was_last_character_carriage_return {
self.digest.hash_update(b"\r");
true
} else {
false
}
}
}
impl Write for DigestWriter<'_> {
#[cfg(not(windows))]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.digest.hash_update(buf);
Ok(buf.len())
}
#[cfg(windows)]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.binary {
self.digest.hash_update(buf);
return Ok(buf.len());
}
let n = buf.len();
if self.was_last_character_carriage_return && n > 0 && buf[0] != b'\n' {
self.digest.hash_update(b"\r");
}
let mut i_prev = 0;
for i in memmem::find_iter(buf, b"\r\n") {
self.digest.hash_update(&buf[i_prev..i]);
i_prev = i + 1;
}
if n > 0 && buf[n - 1] == b'\r' {
self.was_last_character_carriage_return = true;
self.digest.hash_update(&buf[i_prev..n - 1]);
} else {
self.was_last_character_carriage_return = false;
self.digest.hash_update(&buf[i_prev..n]);
}
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
#[cfg(windows)]
#[test]
fn test_crlf_across_blocks() {
use std::io::Write;
use super::Digest;
use super::DigestWriter;
use super::Md5;
let mut digest = Box::new(Md5::new()) as Box<dyn Digest>;
let mut writer_crlf = DigestWriter::new(&mut digest, false);
writer_crlf.write_all(b"\r").unwrap();
writer_crlf.write_all(b"\n").unwrap();
writer_crlf.finalize();
let result_crlf = digest.result_str();
let mut digest = Box::new(Md5::new()) as Box<dyn Digest>;
let mut writer_lf = DigestWriter::new(&mut digest, false);
writer_lf.write_all(b"\n").unwrap();
writer_lf.finalize();
let result_lf = digest.result_str();
assert_eq!(result_crlf, result_lf);
}
use super::{Crc, Digest};
#[test]
fn test_crc_basic_functionality() {
let mut crc1 = Crc::new();
let mut crc2 = Crc::new();
crc1.hash_update(b"test");
crc2.hash_update(b"test");
let mut out1 = [0u8; 8];
let mut out2 = [0u8; 8];
crc1.hash_finalize(&mut out1);
crc2.hash_finalize(&mut out2);
assert_eq!(out1, out2);
}
#[test]
fn test_crc_digest_basic() {
let mut crc = Crc::new();
let mut output = [0u8; 8];
crc.hash_finalize(&mut output);
let empty_result = u64::from_ne_bytes(output);
let mut crc = Crc::new();
crc.hash_update(b"test");
crc.hash_finalize(&mut output);
let test_result = u64::from_ne_bytes(output);
assert_ne!(empty_result, test_result);
assert_eq!(test_result, 3076352578);
}
#[test]
fn test_crc_digest_incremental() {
let mut crc1 = Crc::new();
let mut crc2 = Crc::new();
let data = b"Hello, World! This is a test string for CRC computation.";
crc1.hash_update(data);
let mut output1 = [0u8; 8];
crc1.hash_finalize(&mut output1);
crc2.hash_update(&data[0..10]);
crc2.hash_update(&data[10..30]);
crc2.hash_update(&data[30..]);
let mut output2 = [0u8; 8];
crc2.hash_finalize(&mut output2);
assert_eq!(output1, output2);
}
#[test]
fn test_crc_slice8_vs_single_byte() {
let test_data = b"This is a longer test string to verify slice-by-8 optimization works correctly with various data sizes including remainders.";
let mut crc_optimized = Crc::new();
crc_optimized.hash_update(test_data);
let mut output_opt = [0u8; 8];
crc_optimized.hash_finalize(&mut output_opt);
let mut crc_reference = Crc::new();
for &byte in test_data {
crc_reference.hash_update(&[byte]);
}
let mut output_ref = [0u8; 8];
crc_reference.hash_finalize(&mut output_ref);
assert_eq!(output_opt, output_ref);
}
#[test]
fn test_crc_known_values() {
let test_cases = [
("", 4294967295_u64),
("a", 1220704766_u64),
("abc", 1219131554_u64),
];
for (input, expected) in test_cases {
let mut crc = Crc::new();
crc.hash_update(input.as_bytes());
let mut output = [0u8; 8];
crc.hash_finalize(&mut output);
let result = u64::from_ne_bytes(output);
assert_eq!(result, expected, "CRC mismatch for input: '{input}'");
}
}
#[test]
fn test_crc_hash_update_edge_cases() {
let mut crc = Crc::new();
let data7 = b"1234567"; crc.hash_update(data7);
let data9 = b"123456789"; let mut crc2 = Crc::new();
crc2.hash_update(data9);
let mut out1 = [0u8; 8];
let mut out2 = [0u8; 8];
crc.hash_finalize(&mut out1);
crc2.hash_finalize(&mut out2);
assert_ne!(out1, out2);
}
}