use std::io::{self, Write};
use super::block::{Block, BLOCK_LENGTH_BYTES};
use super::digest::Digest;
use super::padding::{pad, Padding};
use super::State;
#[derive(Clone, Debug)]
pub struct Hash {
state: State,
buffer: Vec<u8>,
counter: usize,
}
impl Hash {
#[cfg_attr(not(debug_assertions), inline)]
#[must_use]
pub fn digest(&self) -> Digest {
self.state.into()
}
#[cfg_attr(not(debug_assertions), inline)]
#[must_use]
pub fn new() -> Self {
Self {
state: State::new(),
buffer: Vec::with_capacity(BLOCK_LENGTH_BYTES),
counter: 0,
}
}
#[cfg_attr(not(debug_assertions), inline)]
pub fn pad(&mut self) -> &mut Self {
let block_length = self.buffer.len();
let block: Block = {
self.buffer.resize(BLOCK_LENGTH_BYTES, 0x00);
let block = self.buffer.drain(..BLOCK_LENGTH_BYTES);
let block: [u8; BLOCK_LENGTH_BYTES] = block.as_slice().try_into().unwrap();
block.into()
};
match pad(block, block_length, self.counter) {
Padding::Single(block) => {
self.state.update(block.into());
},
Padding::Double(block0, block1) => {
self.state.update(block0.into()).update(block1.into());
},
}
self
}
#[cfg_attr(not(debug_assertions), inline)]
pub fn reset(&mut self) -> &mut Self {
self.state.reset();
self.buffer.clear();
self.counter = 0;
self
}
#[cfg_attr(nightly, optimize(speed))]
pub fn update<T>(&mut self, data: T) -> &mut Self
where
T: AsRef<[u8]>,
{
let data = data.as_ref();
self.counter = self.counter.wrapping_add(data.len());
let mut data = data;
if self.buffer.is_empty() {
while data.len() >= BLOCK_LENGTH_BYTES {
let block: [u8; BLOCK_LENGTH_BYTES] = data[..BLOCK_LENGTH_BYTES].try_into().unwrap();
data = &data[BLOCK_LENGTH_BYTES..];
let block: Block = block.into();
self.state.update(block.into());
}
} else if ((self.buffer.len() % BLOCK_LENGTH_BYTES) + data.len()) > BLOCK_LENGTH_BYTES {
let buffer_length = self.buffer.len() % BLOCK_LENGTH_BYTES;
if buffer_length > 0 {
let buffer_missing = BLOCK_LENGTH_BYTES - buffer_length;
let buffer = &data[..buffer_missing];
data = &data[buffer_missing..];
self.buffer.extend_from_slice(buffer);
}
while self.buffer.len() >= BLOCK_LENGTH_BYTES {
let block: [u8; BLOCK_LENGTH_BYTES] =
self.buffer.drain(..BLOCK_LENGTH_BYTES).as_slice().try_into().unwrap();
let block: Block = block.into();
self.state.update(block.into());
}
while data.len() >= BLOCK_LENGTH_BYTES {
let block: [u8; BLOCK_LENGTH_BYTES] = data[..BLOCK_LENGTH_BYTES].try_into().unwrap();
data = &data[BLOCK_LENGTH_BYTES..];
let block: Block = block.into();
self.state.update(block.into());
}
}
if !data.is_empty() {
self.buffer.extend_from_slice(data);
}
self
}
}
impl Default for Hash {
#[cfg_attr(not(debug_assertions), inline)]
fn default() -> Self {
Self::new()
}
}
impl Write for Hash {
#[cfg_attr(not(debug_assertions), inline)]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
#[cfg_attr(not(debug_assertions), inline)]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.update(buf);
Ok(buf.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::digest::Result;
#[test]
fn hash_new() -> Result<()> {
assert_eq!(
Hash::new().pad().digest(),
Digest::try_from(
"38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B"
)?
);
Ok(())
}
#[test]
fn hash_hello_world() -> Result<()> {
assert_eq!(
Hash::new().update("Hello World").pad().digest(),
Digest::try_from(
"99514329186B2F6AE4A1329E7EE6C610A729636335174AC6B740F9028396FCC803D0E93863A7C3D90F86BEEE782F4F3F"
)?
);
Ok(())
}
#[test]
fn hash_hello_world_by_chunks() -> Result<()> {
assert_eq!(
Hash::new().update("Hello").update(" ").update("World").pad().digest(),
Digest::try_from(
"99514329186B2F6AE4A1329E7EE6C610A729636335174AC6B740F9028396FCC803D0E93863A7C3D90F86BEEE782F4F3F"
)?
);
Ok(())
}
}