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 MD4 {
    state: [u32; 4],
    count: u64,
    is_done: bool,
    digest: [u8; Self::DIGEST_SIZE],
}

impl Hasher for MD4 {
    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 MD4 {
    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);
        d = round_1(d, a, b, c, block[1], S12);
        c = round_1(c, d, a, b, block[2], S13);
        b = round_1(b, c, d, a, block[3], S14);
        a = round_1(a, b, c, d, block[4], S11);
        d = round_1(d, a, b, c, block[5], S12);
        c = round_1(c, d, a, b, block[6], S13);
        b = round_1(b, c, d, a, block[7], S14);
        a = round_1(a, b, c, d, block[8], S11);
        d = round_1(d, a, b, c, block[9], S12);
        c = round_1(c, d, a, b, block[10], S13);
        b = round_1(b, c, d, a, block[11], S14);
        a = round_1(a, b, c, d, block[12], S11);
        d = round_1(d, a, b, c, block[13], S12);
        c = round_1(c, d, a, b, block[14], S13);
        b = round_1(b, c, d, a, block[15], S14);

        a = round_2(a, b, c, d, block[0], S21);
        d = round_2(d, a, b, c, block[4], S22);
        c = round_2(c, d, a, b, block[8], S23);
        b = round_2(b, c, d, a, block[12], S24);
        a = round_2(a, b, c, d, block[1], S21);
        d = round_2(d, a, b, c, block[5], S22);
        c = round_2(c, d, a, b, block[9], S23);
        b = round_2(b, c, d, a, block[13], S24);
        a = round_2(a, b, c, d, block[2], S21);
        d = round_2(d, a, b, c, block[6], S22);
        c = round_2(c, d, a, b, block[10], S23);
        b = round_2(b, c, d, a, block[14], S24);
        a = round_2(a, b, c, d, block[3], S21);
        d = round_2(d, a, b, c, block[7], S22);
        c = round_2(c, d, a, b, block[11], S23);
        b = round_2(b, c, d, a, block[15], S24);

        a = round_3(a, b, c, d, block[0], S31);
        d = round_3(d, a, b, c, block[8], S32);
        c = round_3(c, d, a, b, block[4], S33);
        b = round_3(b, c, d, a, block[12], S34);
        a = round_3(a, b, c, d, block[2], S31);
        d = round_3(d, a, b, c, block[10], S32);
        c = round_3(c, d, a, b, block[6], S33);
        b = round_3(b, c, d, a, block[14], S34);
        a = round_3(a, b, c, d, block[1], S31);
        d = round_3(d, a, b, c, block[9], S32);
        c = round_3(c, d, a, b, block[5], S33);
        b = round_3(b, c, d, a, block[13], S34);
        a = round_3(a, b, c, d, block[3], S31);
        d = round_3(d, a, b, c, block[11], S32);
        c = round_3(c, d, a, b, block[7], S33);
        b = round_3(b, c, d, a, block[15], S34);

        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 & y | x & z | y & z
}

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

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

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

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

const S11: u32 = 3;
const S12: u32 = 7;
const S13: u32 = 11;
const S14: u32 = 19;
const S21: u32 = 3;
const S22: u32 = 5;
const S23: u32 = 9;
const S24: u32 = 13;
const S31: u32 = 3;
const S32: u32 = 9;
const S33: u32 = 11;
const S34: u32 = 15;

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

    use super::MD4;

    const TESTS: &[TestData] = &[
        TestData {
            data: "".as_bytes(),
            repeat: 1,
            result: "31d6cfe0d16ae931b73c59d7e0c089c0",
        },
        TestData {
            data: "a".as_bytes(),
            repeat: 1,
            result: "bde52cb31de33e46245e05fbdbd6fb24",
        },
        TestData {
            data: "abc".as_bytes(),
            repeat: 1,
            result: "a448017aaf21d8525fc10ae87aa6729d",
        },
        TestData {
            data: "message digest".as_bytes(),
            repeat: 1,
            result: "d9130a8164549fe818874806e1c7014b",
        },
        TestData {
            data: "abcdefghijklmnopqrstuvwxyz".as_bytes(),
            repeat: 1,
            result: "d79e1c308aa5bbcdeea8ed63df412da9",
        },
        TestData {
            data: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".as_bytes(),
            repeat: 1,
            result: "043f8582f241db351ce627e153e7f0e4",
        },
        TestData {
            data:
                "12345678901234567890123456789012345678901234567890123456789012345678901234567890"
                    .as_bytes(),
            repeat: 1,
            result: "e33b4ddc9c38f2199c3e7b164fcc0536",
        },
    ];

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

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

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