#![forbid(unsafe_code)]
#![warn(
future_incompatible,
nonstandard_style,
rust_2018_idioms,
rustdoc,
unused
)]
#![warn(
deprecated_in_future,
missing_crate_level_docs,
missing_docs,
missing_doc_code_examples,
single_use_lifetimes,
trivial_casts,
trivial_numeric_casts,
unused_crate_dependencies,
unused_import_braces,
unused_lifetimes,
unused_qualifications,
unused_results
)]
#![warn(clippy::pedantic, clippy::cargo, clippy::cast_possible_truncation)]
#![allow(clippy::if_same_then_else)]
#![allow(clippy::non_ascii_literal)]
pub const LENGTH_BITS: usize = 160;
pub const LENGTH_BYTES: usize = LENGTH_BITS / 8;
const BLOCK_BITS: usize = 512;
const BLOCK_BYTES: usize = BLOCK_BITS / 8;
pub type Hash = [u8; LENGTH_BYTES];
#[derive(Clone, Copy)]
pub struct Hasher {
state: [u32; LENGTH_BYTES / 4],
partial_block: [u8; BLOCK_BYTES],
bytes_in_partial_block: usize,
data_length: u64,
}
impl Hasher {
#[must_use]
pub fn new() -> Self {
Self {
state: sha::sha1::consts::H,
partial_block: [0_u8; BLOCK_BYTES],
bytes_in_partial_block: 0_usize,
data_length: 0_u64,
}
}
pub fn write(&mut self, bytes: &[u8]) {
use sha::sha1::ops::digest_block;
self.data_length += bytes.len() as u64;
let bytes: &[u8] = if self.bytes_in_partial_block == 0 {
&bytes
} else {
let to_copy: usize =
std::cmp::min(bytes.len(), BLOCK_BYTES - self.bytes_in_partial_block);
self.partial_block[self.bytes_in_partial_block..self.bytes_in_partial_block + to_copy]
.copy_from_slice(&bytes[0..to_copy]);
self.bytes_in_partial_block += to_copy;
if self.bytes_in_partial_block == BLOCK_BYTES {
digest_block(&mut self.state, &self.partial_block);
self.bytes_in_partial_block = 0;
}
&bytes[to_copy..]
};
debug_assert!(self.bytes_in_partial_block == 0 || bytes.is_empty());
let bytes = bytes.chunks_exact(BLOCK_BYTES);
for block in bytes.clone() {
digest_block(&mut self.state, &block);
}
let bytes: &[u8] = bytes.remainder();
if bytes.is_empty() {
} else {
self.partial_block[0..bytes.len()].copy_from_slice(bytes);
self.bytes_in_partial_block = bytes.len();
}
}
#[must_use]
pub fn finish(&self) -> Hash {
self.finish_by_value()
}
#[must_use]
fn finish_by_value(mut self) -> Hash {
const ZEROES: [u8; BLOCK_BYTES] = [0_u8; BLOCK_BYTES];
let data_length_bits: u64 = self.data_length * 8;
self.write(&[0x80_u8]);
if BLOCK_BYTES - self.bytes_in_partial_block < 8 {
self.write(&ZEROES[0..BLOCK_BYTES - self.bytes_in_partial_block]);
}
self.write(&ZEROES[0..BLOCK_BYTES - 8 - self.bytes_in_partial_block]);
self.write(&data_length_bits.to_be_bytes());
let mut result = [0_u8; LENGTH_BYTES];
let mut result_slice: &mut [u8] = &mut result;
for word in &self.state {
use std::io::Write;
result_slice.write_all(&word.to_be_bytes()).unwrap();
}
result
}
}
impl Default for Hasher {
fn default() -> Hasher {
Hasher::new()
}
}
#[cfg(test)]
mod vectors;
#[cfg(test)]
mod test {
fn test_vectors(writer: fn(&mut super::Hasher, &[u8]) -> ()) {
for &vectors in &[super::vectors::SHORT_VECTORS, super::vectors::LONG_VECTORS] {
let vectors = super::vectors::Vectors::new(vectors);
for vector in vectors {
let mut h = super::Hasher::default();
writer(&mut h, &vector.input);
let h = h.finish();
println!(
"Input: {:?}\nExpected: {:?}\nActual: {:?}",
hex::encode(&vector.input),
vector.output,
h
);
assert_eq!(h, vector.output);
}
}
}
#[test]
fn test_vectors_whole() {
test_vectors(|h, d| h.write(d))
}
#[test]
fn test_vectors_bytewise() {
test_vectors(|h, d| {
for &b in d {
h.write(&[b])
}
})
}
#[test]
fn test_vectors_fivebytewise() {
test_vectors(|h, d| {
for chunk in d.chunks(5) {
h.write(chunk)
}
})
}
}