libmhash 0.2.1

A file hashing library that can do multiple hashes for multile files at the same time.
Documentation
use std::mem::size_of;

use crate::{paranoid_hash::Hasher, Error, Result};

#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct MD5 {
    state: [u32; 4],
    count: u64,
    is_done: bool,
    digest: [u8; Self::DIGEST_SIZE],
}

impl Hasher for MD5 {
    fn update(&mut self, data: &[u8]) -> Result<()> {
        transmute_update!(self, data, Self::BLOCK_SIZE, u32, u64, "wrapping", "le");
    }

    fn update_last(&mut self, data: &[u8]) -> Result<()> {
        transmute_update_last!(self, data, Self::BLOCK_SIZE, u32, u64, "wrapping", "le");
    }

    fn digest(&self) -> Result<&[u8]> {
        if !self.is_done {
            return Err(Error::NotFinished);
        }

        Ok(&self.digest)
    }

    fn reset(&mut self) {
        *self = Self::new();
    }

    fn block_size(&self) -> usize {
        Self::BLOCK_SIZE
    }

    fn digest_size(&self) -> usize {
        Self::DIGEST_SIZE
    }
}

impl MD5 {
    pub const BLOCK_SIZE: usize = 64;
    pub const DIGEST_SIZE: usize = 16;

    const U32_BLOCK_SIZE: usize = Self::BLOCK_SIZE / size_of::<u32>();

    pub const fn new() -> Self {
        Self {
            state: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476],
            count: 0,
            is_done: false,
            digest: [0; Self::DIGEST_SIZE],
        }
    }

    #[inline]
    fn update_block(&mut self, block: &[u32; Self::U32_BLOCK_SIZE]) {
        let [mut a, mut b, mut c, mut d] = self.state;

        a = round_1(a, b, c, d, block[0], S11, 0xd76aa478);
        d = round_1(d, a, b, c, block[1], S12, 0xe8c7b756);
        c = round_1(c, d, a, b, block[2], S13, 0x242070db);
        b = round_1(b, c, d, a, block[3], S14, 0xc1bdceee);
        a = round_1(a, b, c, d, block[4], S11, 0xf57c0faf);
        d = round_1(d, a, b, c, block[5], S12, 0x4787c62a);
        c = round_1(c, d, a, b, block[6], S13, 0xa8304613);
        b = round_1(b, c, d, a, block[7], S14, 0xfd469501);
        a = round_1(a, b, c, d, block[8], S11, 0x698098d8);
        d = round_1(d, a, b, c, block[9], S12, 0x8b44f7af);
        c = round_1(c, d, a, b, block[10], S13, 0xffff5bb1);
        b = round_1(b, c, d, a, block[11], S14, 0x895cd7be);
        a = round_1(a, b, c, d, block[12], S11, 0x6b901122);
        d = round_1(d, a, b, c, block[13], S12, 0xfd987193);
        c = round_1(c, d, a, b, block[14], S13, 0xa679438e);
        b = round_1(b, c, d, a, block[15], S14, 0x49b40821);

        a = round_2(a, b, c, d, block[1], S21, 0xf61e2562);
        d = round_2(d, a, b, c, block[6], S22, 0xc040b340);
        c = round_2(c, d, a, b, block[11], S23, 0x265e5a51);
        b = round_2(b, c, d, a, block[0], S24, 0xe9b6c7aa);
        a = round_2(a, b, c, d, block[5], S21, 0xd62f105d);
        d = round_2(d, a, b, c, block[10], S22, 0x2441453);
        c = round_2(c, d, a, b, block[15], S23, 0xd8a1e681);
        b = round_2(b, c, d, a, block[4], S24, 0xe7d3fbc8);
        a = round_2(a, b, c, d, block[9], S21, 0x21e1cde6);
        d = round_2(d, a, b, c, block[14], S22, 0xc33707d6);
        c = round_2(c, d, a, b, block[3], S23, 0xf4d50d87);
        b = round_2(b, c, d, a, block[8], S24, 0x455a14ed);
        a = round_2(a, b, c, d, block[13], S21, 0xa9e3e905);
        d = round_2(d, a, b, c, block[2], S22, 0xfcefa3f8);
        c = round_2(c, d, a, b, block[7], S23, 0x676f02d9);
        b = round_2(b, c, d, a, block[12], S24, 0x8d2a4c8a);

        a = round_3(a, b, c, d, block[5], S31, 0xfffa3942);
        d = round_3(d, a, b, c, block[8], S32, 0x8771f681);
        c = round_3(c, d, a, b, block[11], S33, 0x6d9d6122);
        b = round_3(b, c, d, a, block[14], S34, 0xfde5380c);
        a = round_3(a, b, c, d, block[1], S31, 0xa4beea44);
        d = round_3(d, a, b, c, block[4], S32, 0x4bdecfa9);
        c = round_3(c, d, a, b, block[7], S33, 0xf6bb4b60);
        b = round_3(b, c, d, a, block[10], S34, 0xbebfbc70);
        a = round_3(a, b, c, d, block[13], S31, 0x289b7ec6);
        d = round_3(d, a, b, c, block[0], S32, 0xeaa127fa);
        c = round_3(c, d, a, b, block[3], S33, 0xd4ef3085);
        b = round_3(b, c, d, a, block[6], S34, 0x4881d05);
        a = round_3(a, b, c, d, block[9], S31, 0xd9d4d039);
        d = round_3(d, a, b, c, block[12], S32, 0xe6db99e5);
        c = round_3(c, d, a, b, block[15], S33, 0x1fa27cf8);
        b = round_3(b, c, d, a, block[2], S34, 0xc4ac5665);

        a = round_4(a, b, c, d, block[0], S41, 0xf4292244);
        d = round_4(d, a, b, c, block[7], S42, 0x432aff97);
        c = round_4(c, d, a, b, block[14], S43, 0xab9423a7);
        b = round_4(b, c, d, a, block[5], S44, 0xfc93a039);
        a = round_4(a, b, c, d, block[12], S41, 0x655b59c3);
        d = round_4(d, a, b, c, block[3], S42, 0x8f0ccc92);
        c = round_4(c, d, a, b, block[10], S43, 0xffeff47d);
        b = round_4(b, c, d, a, block[1], S44, 0x85845dd1);
        a = round_4(a, b, c, d, block[8], S41, 0x6fa87e4f);
        d = round_4(d, a, b, c, block[15], S42, 0xfe2ce6e0);
        c = round_4(c, d, a, b, block[6], S43, 0xa3014314);
        b = round_4(b, c, d, a, block[13], S44, 0x4e0811a1);
        a = round_4(a, b, c, d, block[4], S41, 0xf7537e82);
        d = round_4(d, a, b, c, block[11], S42, 0xbd3af235);
        c = round_4(c, d, a, b, block[2], S43, 0x2ad7d2bb);
        b = round_4(b, c, d, a, block[9], S44, 0xeb86d391);

        self.state[0] = self.state[0].wrapping_add(a);
        self.state[1] = self.state[1].wrapping_add(b);
        self.state[2] = self.state[2].wrapping_add(c);
        self.state[3] = self.state[3].wrapping_add(d);
    }
}

#[inline(always)]
fn hash_1(x: u32, y: u32, z: u32) -> u32 {
    x & y | !x & z
}

#[inline(always)]
fn hash_2(x: u32, y: u32, z: u32) -> u32 {
    x & z | y & !z
}

#[inline(always)]
fn hash_3(x: u32, y: u32, z: u32) -> u32 {
    x ^ y ^ z
}

#[inline(always)]
fn hash_4(x: u32, y: u32, z: u32) -> u32 {
    y ^ (x | !z)
}

#[inline(always)]
fn round_1(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32, ac: u32) -> u32 {
    hash_1(b, c, d)
        .wrapping_add(x)
        .wrapping_add(ac)
        .wrapping_add(a)
        .rotate_left(s)
        .wrapping_add(b)
}

#[inline(always)]
fn round_2(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32, ac: u32) -> u32 {
    hash_2(b, c, d)
        .wrapping_add(x)
        .wrapping_add(ac)
        .wrapping_add(a)
        .rotate_left(s)
        .wrapping_add(b)
}

#[inline(always)]
fn round_3(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32, ac: u32) -> u32 {
    hash_3(b, c, d)
        .wrapping_add(x)
        .wrapping_add(ac)
        .wrapping_add(a)
        .rotate_left(s)
        .wrapping_add(b)
}

#[inline(always)]
fn round_4(a: u32, b: u32, c: u32, d: u32, x: u32, s: u32, ac: u32) -> u32 {
    hash_4(b, c, d)
        .wrapping_add(x)
        .wrapping_add(ac)
        .wrapping_add(a)
        .rotate_left(s)
        .wrapping_add(b)
}

const S11: u32 = 7;
const S12: u32 = 12;
const S13: u32 = 17;
const S14: u32 = 22;
const S21: u32 = 5;
const S22: u32 = 9;
const S23: u32 = 14;
const S24: u32 = 20;
const S31: u32 = 4;
const S32: u32 = 11;
const S33: u32 = 16;
const S34: u32 = 23;
const S41: u32 = 6;
const S42: u32 = 10;
const S43: u32 = 15;
const S44: u32 = 21;

#[cfg(test)]
mod tests {
    use crate::paranoid_hash::{
        tester::{HasherTestWrapper, TestData},
        Hasher,
    };

    use super::MD5;

    const TESTS: &[TestData] = &[
        TestData {
            data: "".as_bytes(),
            repeat: 1,
            result: "d41d8cd98f00b204e9800998ecf8427e",
        },
        TestData {
            data: "a".as_bytes(),
            repeat: 1,
            result: "0cc175b9c0f1b6a831c399e269772661",
        },
        TestData {
            data: "abc".as_bytes(),
            repeat: 1,
            result: "900150983cd24fb0d6963f7d28e17f72",
        },
        TestData {
            data: "message digest".as_bytes(),
            repeat: 1,
            result: "f96b697d7cb7938d525a2f31aaf161d0",
        },
        TestData {
            data: "abcdefghijklmnopqrstuvwxyz".as_bytes(),
            repeat: 1,
            result: "c3fcd3d76192e4007dfb496cca67e13b",
        },
        TestData {
            data: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".as_bytes(),
            repeat: 1,
            result: "d174ab98d277d9f5a5611c2c9f419d9f",
        },
        TestData {
            data:
                "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    .as_bytes(),
            repeat: 1,
            result: "57edf4a22be3c955ac49da2e2107b67a",
        },
    ];

    #[test]
    fn tests_from_rfc() {
        HasherTestWrapper::new(MD5::new()).run_tests(TESTS);
    }

    #[test]
    #[should_panic]
    fn panic_test1() {
        let mut hasher = MD5::new();
        hasher
            .update("Not multiple of block size".as_bytes())
            .unwrap();
    }

    #[test]
    #[should_panic]
    fn panic_test2() {
        let mut hasher = MD5::new();
        let data = [0u8; MD5::BLOCK_SIZE + 1];
        hasher.update_last(&data).unwrap();
    }
}