ahsah 2.1.0

Incremental hashing contexts for MD5 and SHA-2 with reader helpers and optional SIMD decode paths
Documentation
use std::convert::TryInto;

#[derive(Clone, Debug)]
pub(crate) struct BlockBuffer<
    const BLOCK_SIZE: usize,
    const LENGTH_SIZE: usize,
    const BIG_ENDIAN: bool,
> {
    block: [u8; BLOCK_SIZE],
    len: usize,
    total_len: u128,
}

impl<const BLOCK_SIZE: usize, const LENGTH_SIZE: usize, const BIG_ENDIAN: bool>
    BlockBuffer<BLOCK_SIZE, LENGTH_SIZE, BIG_ENDIAN>
{
    pub(crate) fn new() -> Self {
        Self {
            block: [0u8; BLOCK_SIZE],
            len: 0,
            total_len: 0,
        }
    }

    pub(crate) fn reset(&mut self) {
        self.block = [0u8; BLOCK_SIZE];
        self.len = 0;
        self.total_len = 0;
    }

    pub(crate) fn message_len(&self) -> u128 {
        self.total_len
    }

    pub(crate) fn update<F>(&mut self, mut input: &[u8], mut process_block: F)
    where
        F: FnMut(&[u8; BLOCK_SIZE]),
    {
        self.total_len = self
            .total_len
            .checked_add(input.len() as u128)
            .expect("message length overflow");

        if self.len > 0 {
            let to_copy = (BLOCK_SIZE - self.len).min(input.len());
            self.block[self.len..self.len + to_copy].copy_from_slice(&input[..to_copy]);
            self.len += to_copy;
            input = &input[to_copy..];

            if self.len == BLOCK_SIZE {
                process_block(&self.block);
                self.len = 0;
            }
        }

        for chunk in input.chunks_exact(BLOCK_SIZE) {
            let block: &[u8; BLOCK_SIZE] =
                chunk.try_into().expect("chunk length matches block size");
            process_block(block);
        }

        let remainder = input.len() % BLOCK_SIZE;
        if remainder > 0 {
            let start = input.len() - remainder;
            self.block[..remainder].copy_from_slice(&input[start..]);
            self.len = remainder;
        }
    }

    pub(crate) fn finalize<F>(&mut self, mut process_block: F)
    where
        F: FnMut(&[u8; BLOCK_SIZE]),
    {
        let mut tail = Vec::with_capacity(BLOCK_SIZE * 2);
        tail.extend_from_slice(&self.block[..self.len]);
        tail.push(0x80);

        while (tail.len() + LENGTH_SIZE) % BLOCK_SIZE != 0 {
            tail.push(0);
        }

        let bit_len = self.total_len.checked_mul(8).expect("bit length overflow");
        let length_bytes = if BIG_ENDIAN {
            bit_len.to_be_bytes()
        } else {
            bit_len.to_le_bytes()
        };
        if BIG_ENDIAN {
            tail.extend_from_slice(&length_bytes[16 - LENGTH_SIZE..]);
        } else {
            tail.extend_from_slice(&length_bytes[..LENGTH_SIZE]);
        }

        for chunk in tail.chunks_exact(BLOCK_SIZE) {
            let block: &[u8; BLOCK_SIZE] = chunk
                .try_into()
                .expect("final padded chunk length matches block size");
            process_block(block);
        }
    }
}