JenkHash 0.3.0

Bob Jenkins hash functions for Rust with a digest-compatible API.
Documentation
use super::{GOLDEN_RATIO, Hash, Lookup2};
use crate::extensions::ext_slice_u8::ExtSliceU8;
use core::cmp::min;
use digest::{Digest, FixedOutput, Output, Update};

/// Type marker for the little-endian optimized hash variant.
///
/// This variant is optimized for little-endian byte interpretation and provides
/// better performance than [`Hash`] while maintaining cross-platform compatibility.
/// It works deterministically on all machines by always interpreting input bytes
/// as little-endian, regardless of the machine's native endianness.
///
/// # Recommended Usage
///
/// This is the recommended variant for most use cases as it provides:
/// - Better performance than the byte-oriented [`Hash`] variant
/// - Cross-platform compatibility through consistent endianness handling
///
/// [`Hash`]: crate::lookup2::Hash
///
/// # Examples
///
/// ```
/// use digest::Digest;
/// use JenkHash::lookup2::{Lookup2, Hash3};
///
/// // One-shot hashing
/// let hash = Lookup2::<Hash3>::digest(b"hello world");
/// let hash_value = u32::from_le_bytes(hash[..].try_into().unwrap());
///
/// // With custom seed
/// let hasher = Lookup2::<Hash3>::from_seed(0xDEADBEEF);
/// ```
pub struct Hash3;

impl Lookup2<Hash3> {
    /// Computes the hash of the input data and returns it as a `u32`.
    ///
    /// This is a convenience method that combines [`Digest::digest`] with
    /// byte conversion. Equivalent to calling `digest()` and converting
    /// the result to `u32`.
    ///
    /// # Arguments
    ///
    /// * `data` - The data to hash
    ///
    /// # Returns
    ///
    /// The hash value as a 32-bit unsigned integer.
    ///
    /// # Examples
    ///
    /// ```
    /// use JenkHash::lookup2::{Lookup2, Hash3};
    /// let hash = Lookup2::<Hash3>::digest_unchecked(b"hello world");
    /// ```
    pub fn digest_unchecked(data: &[u8]) -> u32 {
        let hash = Lookup2::<Hash3>::digest(data);

        u32::from_le_bytes(hash[..].try_into().unwrap())
    }
}

impl Update for Lookup2<Hash3> {
    #[rustfmt::skip]
    fn update(&mut self, mut data: &[u8]) {
        self.total_length += data.len();

        if self.rem_offset > 0 {
            let remaining = min(12_u8.saturating_sub(self.rem_offset) as usize, data.len());

            self.rem[self.rem_offset as usize..remaining + self.rem_offset as usize].copy_from_slice(&data[..remaining]);
            self.rem_offset += remaining as u8;
            data = &data[remaining..];

            if self.rem_offset < 12 { return; }

            self.state[0] = self.state[0].wrapping_add(self.rem[0..].read_u32_le());
            self.state[1] = self.state[1].wrapping_add(self.rem[4..].read_u32_le());
            self.state[2] = self.state[2].wrapping_add(self.rem[8..].read_u32_le());

            self.state = Self::mix(self.state);

            self.rem_offset = 0;
        }

        while data.len() >= 12 {
            self.state[0] = self.state[0].wrapping_add(data[0..].read_u32_le());
            self.state[1] = self.state[1].wrapping_add(data[4..].read_u32_le());
            self.state[2] = self.state[2].wrapping_add(data[8..].read_u32_le());

            self.state = Self::mix(self.state);
            data = &data[12..];
        }

        self.rem[self.rem_offset as usize..self.rem_offset as usize + data.len()].copy_from_slice(data);
        self.rem_offset += data.len() as u8
    }
}
impl FixedOutput for Lookup2<Hash3> {
    #[rustfmt::skip]
    fn finalize_into(mut self, out: &mut Output<Self>) {
        self.state[2] = self.state[2].wrapping_add(self.total_length as u32);

        if self.rem_offset == 11 { self.state[2] = Lookup2::add_msb(self.state[2], self.rem[10], 24); }
        if self.rem_offset >= 10 { self.state[2] = Lookup2::add_msb(self.state[2], self.rem[09], 16); }
        if self.rem_offset >= 09 { self.state[2] = Lookup2::add_msb(self.state[2], self.rem[08], 08); }
        if self.rem_offset >= 08 { self.state[1] = Lookup2::add_msb(self.state[1], self.rem[07], 24); }
        if self.rem_offset >= 07 { self.state[1] = Lookup2::add_msb(self.state[1], self.rem[06], 16); }
        if self.rem_offset >= 06 { self.state[1] = Lookup2::add_msb(self.state[1], self.rem[05], 08); }
        if self.rem_offset >= 05 { self.state[1] = Lookup2::add_lsb(self.state[1], self.rem[04]); }
        if self.rem_offset >= 04 { self.state[0] = Lookup2::add_msb(self.state[0], self.rem[03], 24); }
        if self.rem_offset >= 03 { self.state[0] = Lookup2::add_msb(self.state[0], self.rem[02], 16); }
        if self.rem_offset >= 02 { self.state[0] = Lookup2::add_msb(self.state[0], self.rem[01], 08); }
        if self.rem_offset >= 01 { self.state[0] = Lookup2::add_lsb(self.state[0], self.rem[00]); }

        self.state = Self::mix(self.state);

        out.copy_from_slice(&self.state[2].to_le_bytes())
    }
}