use crate::low::Blockwise;
use crate::low::cpu::verify_cpu_features;
#[derive(Clone)]
pub(crate) struct Sha1State {
h: [u32; 5],
nblocks: u64,
}
impl Sha1State {
pub(crate) const fn new() -> Self {
Self {
h: [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0],
nblocks: 0,
}
}
pub(crate) fn update_blocks(&mut self, blocks: &[u8]) {
debug_assert!(blocks.len().is_multiple_of(Sha1Context::BLOCK_SZ));
if !blocks.is_empty() {
Self::compress(&mut self.h, blocks);
self.nblocks = self
.nblocks
.saturating_add((blocks.len() / Sha1Context::BLOCK_SZ) as u64);
}
}
fn compress(h: &mut [u32; 5], blocks: &[u8]) {
verify_cpu_features();
crate::low::sha1_compress_blocks(h, blocks);
}
pub(crate) fn finish_with(&self, message: &[u8]) -> [u8; Sha1Context::OUTPUT_SZ] {
const BLOCK_SZ: usize = Sha1Context::BLOCK_SZ;
let (blocks, tail) = message.split_at(message.len() & !(BLOCK_SZ - 1));
let bits = self
.nblocks
.checked_mul(BLOCK_SZ as u64)
.and_then(|bytes| bytes.checked_add(message.len() as u64))
.and_then(|bytes| bytes.checked_mul(8))
.expect("excess data processed by hash function");
let mut h = self.h;
if !blocks.is_empty() {
Self::compress(&mut h, blocks);
}
let mut block = [0u8; BLOCK_SZ];
block[..tail.len()].copy_from_slice(tail);
block[tail.len()] = 0x80;
if tail.len() >= BLOCK_SZ - 8 {
Self::compress(&mut h, &block);
block = [0u8; BLOCK_SZ];
}
block[BLOCK_SZ - 8..].copy_from_slice(&bits.to_be_bytes());
Self::compress(&mut h, &block);
let mut r = [0u8; Sha1Context::OUTPUT_SZ];
let (chunks, _) = r.as_chunks_mut::<4>();
for (out, word) in chunks.iter_mut().zip(h.iter()) {
out.copy_from_slice(&word.to_be_bytes());
}
r
}
}
#[derive(Clone)]
pub(crate) struct Sha1Context {
state: Sha1State,
blockwise: Blockwise<{ Self::BLOCK_SZ }>,
}
impl Sha1Context {
pub(crate) const fn new() -> Self {
Self::resume(Sha1State::new())
}
pub(crate) const fn resume(state: Sha1State) -> Self {
Self {
state,
blockwise: Blockwise::new(),
}
}
pub(crate) fn update(&mut self, bytes: &[u8]) {
if self.blockwise.used() == 0 && bytes.len().is_multiple_of(Self::BLOCK_SZ) {
self.state.update_blocks(bytes);
return;
}
let bytes = self.blockwise.add_leading(bytes);
if let Some(block) = self.blockwise.take() {
self.state.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.state.update_blocks(whole_blocks);
self.blockwise.add_trailing(remainder);
}
pub(crate) fn finish(&self) -> [u8; Self::OUTPUT_SZ] {
self.state.finish_with(self.blockwise.buffered())
}
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.state.nblocks = bytes / Sha1Context::BLOCK_SZ as u64;
ctx.update(b"x");
ctx.finish();
}
}
}