use sha1::{Digest, Sha1};
use crate::hashes::Hasher;
pub const SIZE: usize = 20;
const START_STRING: &[u8] = b"mrCloud";
pub struct MailruHasher {
sha: Sha1,
total: usize,
small: Vec<u8>,
}
impl MailruHasher {
pub fn new() -> Self {
let mut sha = Sha1::new();
sha.update(START_STRING);
Self {
sha,
total: 0,
small: Vec::new(),
}
}
}
impl Default for MailruHasher {
fn default() -> Self {
Self::new()
}
}
impl Hasher for MailruHasher {
fn update(&mut self, data: &[u8]) {
self.sha.update(data);
self.total += data.len();
if self.total <= SIZE {
self.small.extend_from_slice(data);
}
}
fn finalize_hex(self: Box<Self>) -> String {
let Self { sha, total, small } = *self;
if total <= SIZE {
let mut padded = [0u8; SIZE];
padded[..total].copy_from_slice(&small);
hex::encode(padded)
} else {
let mut sha = sha;
sha.update(total.to_string().as_bytes());
hex::encode(sha.finalize())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hashes::Hasher;
fn hash(data: &[u8]) -> String {
let mut h = MailruHasher::new();
h.update(data);
Box::new(h).finalize_hex()
}
#[test]
fn mailru_empty() {
let result = hash(b"");
assert_eq!(result.len(), 40, "must be 40 hex chars");
assert_eq!(result, "0000000000000000000000000000000000000000");
}
#[test]
fn mailru_abc() {
assert_eq!(hash(b"abc"), "6162630000000000000000000000000000000000");
}
#[test]
fn mailru_hello_world() {
assert_eq!(
hash(b"hello world"),
"68656c6c6f20776f726c64000000000000000000"
);
}
#[test]
fn mailru_19_bytes() {
let data = [b'B'; 19];
assert_eq!(hash(&data), "4242424242424242424242424242424242424200");
}
#[test]
fn mailru_exactly_20_bytes() {
let data = [b'A'; 20];
assert_eq!(hash(&data), "4141414141414141414141414141414141414141");
}
#[test]
fn mailru_21_bytes() {
let data = [b'C'; 21];
assert_eq!(hash(&data), "de6f4121a7e0230684bc6fa002cb5052ab73020c");
}
#[test]
fn mailru_100_bytes() {
let data = [b'x'; 100];
assert_eq!(hash(&data), "195475d61466510189551d1d84ab1bb35eaebf17");
}
#[test]
fn default_equals_new() {
let mut h = MailruHasher::default();
h.update(b"abc");
assert_eq!(Box::new(h).finalize_hex(), hash(b"abc"));
}
#[test]
fn chunked_small_matches_single() {
let data = b"hello world";
let single = hash(data);
let mut h = MailruHasher::new();
for chunk in data.chunks(3) {
h.update(chunk);
}
assert_eq!(Box::new(h).finalize_hex(), single);
}
#[test]
fn chunked_large_matches_single() {
let data = vec![0xABu8; 1024];
let single = hash(&data);
let mut h = MailruHasher::new();
for chunk in data.chunks(100) {
h.update(chunk);
}
assert_eq!(Box::new(h).finalize_hex(), single);
}
#[test]
fn small_and_large_paths_differ() {
let result_small = hash(&[b'Z'; 20]);
let result_large = hash(&[b'Z'; 21]);
assert_ne!(result_small, result_large);
}
}