zstd-zero 0.1.0

A no-std, allocation-free streaming Zstandard decoder
Documentation
const PRIME1: u64 = 11_400_714_785_074_694_791;
const PRIME2: u64 = 14_029_467_366_897_019_727;
const PRIME3: u64 = 1_609_587_929_392_839_161;
const PRIME4: u64 = 9_650_029_242_287_828_579;
const PRIME5: u64 = 2_870_177_450_012_600_261;

#[derive(Clone, Copy)]
pub(crate) struct XxHash64 {
    lanes: [u64; 4],
    tail: [u8; 32],
    tail_len: usize,
    total_len: u64,
}

impl XxHash64 {
    pub(crate) const fn new() -> Self {
        Self {
            lanes: [
                PRIME1.wrapping_add(PRIME2),
                PRIME2,
                0,
                0u64.wrapping_sub(PRIME1),
            ],
            tail: [0; 32],
            tail_len: 0,
            total_len: 0,
        }
    }

    pub(crate) fn update(&mut self, mut input: &[u8]) {
        self.total_len = self.total_len.wrapping_add(input.len() as u64);
        if self.tail_len + input.len() < 32 {
            self.tail[self.tail_len..self.tail_len + input.len()].copy_from_slice(input);
            self.tail_len += input.len();
            return;
        }

        if self.tail_len != 0 {
            let needed = 32 - self.tail_len;
            self.tail[self.tail_len..].copy_from_slice(&input[..needed]);
            let block = self.tail;
            self.process(&block);
            input = &input[needed..];
            self.tail_len = 0;
        }

        while input.len() >= 32 {
            self.process(&input[..32]);
            input = &input[32..];
        }
        self.tail[..input.len()].copy_from_slice(input);
        self.tail_len = input.len();
    }

    pub(crate) fn digest(&self) -> u64 {
        let mut hash = if self.total_len >= 32 {
            let mut value = self.lanes[0]
                .rotate_left(1)
                .wrapping_add(self.lanes[1].rotate_left(7))
                .wrapping_add(self.lanes[2].rotate_left(12))
                .wrapping_add(self.lanes[3].rotate_left(18));
            for lane in self.lanes {
                value ^= round(0, lane);
                value = value.wrapping_mul(PRIME1).wrapping_add(PRIME4);
            }
            value
        } else {
            PRIME5
        };
        hash = hash.wrapping_add(self.total_len);

        let mut tail = &self.tail[..self.tail_len];
        while tail.len() >= 8 {
            let lane = round(0, read_u64(tail));
            hash ^= lane;
            hash = hash
                .rotate_left(27)
                .wrapping_mul(PRIME1)
                .wrapping_add(PRIME4);
            tail = &tail[8..];
        }
        if tail.len() >= 4 {
            hash ^= (read_u32(tail) as u64).wrapping_mul(PRIME1);
            hash = hash
                .rotate_left(23)
                .wrapping_mul(PRIME2)
                .wrapping_add(PRIME3);
            tail = &tail[4..];
        }
        for byte in tail {
            hash ^= (*byte as u64).wrapping_mul(PRIME5);
            hash = hash.rotate_left(11).wrapping_mul(PRIME1);
        }
        hash ^= hash >> 33;
        hash = hash.wrapping_mul(PRIME2);
        hash ^= hash >> 29;
        hash = hash.wrapping_mul(PRIME3);
        hash ^ (hash >> 32)
    }

    fn process(&mut self, block: &[u8]) {
        for lane in 0..4 {
            self.lanes[lane] = round(self.lanes[lane], read_u64(&block[lane * 8..]));
        }
    }
}

fn round(accumulator: u64, input: u64) -> u64 {
    accumulator
        .wrapping_add(input.wrapping_mul(PRIME2))
        .rotate_left(31)
        .wrapping_mul(PRIME1)
}

fn read_u32(input: &[u8]) -> u32 {
    u32::from_le_bytes([input[0], input[1], input[2], input[3]])
}

fn read_u64(input: &[u8]) -> u64 {
    u64::from_le_bytes([
        input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
    ])
}

#[cfg(test)]
mod tests {
    use super::XxHash64;

    #[test]
    fn known_vectors() {
        let mut hash = XxHash64::new();
        assert_eq!(hash.digest(), 0xef46_db37_51d8_e999);
        hash.update(b"hello");
        assert_eq!(hash.digest(), 0x26c7_827d_889f_6da3);
    }
}