use crate::low::Blockwise;
use crate::low::cpu::verify_cpu_features;
#[derive(Clone)]
pub(crate) struct Sha1Context {
h: [u32; 5],
blockwise: Blockwise<{ Self::BLOCK_SZ }>,
nblocks: u64,
}
impl Sha1Context {
pub(crate) const fn new() -> Self {
Self {
h: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0],
blockwise: Blockwise::new(),
nblocks: 0,
}
}
pub(crate) fn update(&mut self, bytes: &[u8]) {
if self.blockwise.used() == 0 && bytes.len().is_multiple_of(Self::BLOCK_SZ) {
self.update_blocks(bytes);
return;
}
let bytes = self.blockwise.add_leading(bytes);
if let Some(block) = self.blockwise.take() {
self.update_blocks(&block);
}
let (whole_blocks, remainder) = {
let whole_len = bytes.len() - (bytes.len() & (Self::BLOCK_SZ - 1));
(&bytes[..whole_len], &bytes[whole_len..])
};
self.update_blocks(whole_blocks);
self.blockwise.add_trailing(remainder);
}
pub(crate) fn finish(mut self) -> [u8; Self::OUTPUT_SZ] {
let bytes = self
.nblocks
.checked_mul(Self::BLOCK_SZ as u64)
.and_then(|bytes| bytes.checked_add(self.blockwise.used() as u64))
.unwrap();
let bits = bytes
.checked_mul(8)
.expect("excess data processed by hash function");
let last_blocks = self.blockwise.md_pad_with_length(&bits.to_be_bytes());
self.update_blocks(last_blocks.as_ref());
Self::output(&self.h)
}
pub(crate) fn finish_block(&self, tail: &[u8]) -> [u8; Self::OUTPUT_SZ] {
assert!(self.blockwise.used() == 0 && tail.len() < Self::BLOCK_SZ - 8);
let mut block = [0u8; Self::BLOCK_SZ];
block[..tail.len()].copy_from_slice(tail);
block[tail.len()] = 0x80;
let bits = (self.nblocks * Self::BLOCK_SZ as u64 + tail.len() as u64) * 8;
block[Self::BLOCK_SZ - 8..].copy_from_slice(&bits.to_be_bytes());
let mut h = self.h;
verify_cpu_features();
crate::low::sha1_compress_blocks(&mut h, &block);
Self::output(&h)
}
fn output(h: &[u32; 5]) -> [u8; Self::OUTPUT_SZ] {
let mut r = [0u8; Self::OUTPUT_SZ];
let (chunks, _) = r.as_chunks_mut::<4>();
for (out, state) in chunks.iter_mut().zip(h.iter()) {
out.copy_from_slice(&state.to_be_bytes());
}
r
}
fn update_blocks(&mut self, blocks: &[u8]) {
debug_assert!(blocks.len().is_multiple_of(Self::BLOCK_SZ));
if !blocks.is_empty() {
verify_cpu_features();
crate::low::sha1_compress_blocks(&mut self.h, blocks);
self.nblocks = self
.nblocks
.saturating_add((blocks.len() / Self::BLOCK_SZ) as u64);
}
}
pub(crate) const BLOCK_SZ: usize = 64;
pub(crate) const OUTPUT_SZ: usize = 20;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn length_past_32_bit_usize() {
for bytes in [1u64 << 29, 1 << 32] {
let mut ctx = Sha1Context::new();
ctx.nblocks = bytes / Sha1Context::BLOCK_SZ as u64;
ctx.update(b"x");
ctx.finish();
}
}
}