use crate::util::hints::{likely, unlikely};
use crate::util::mix::{rapid_mix, rapid_mum};
use crate::util::read::{read_u32, read_u64};
use crate::v3::rapid_const::rapidhash_finish;
use crate::v3::RapidSecrets;
pub type RapidStreamHasherV3<'a> = RapidStreamHasherInlineV3<'a, true, false>;
pub struct RapidStreamHasherInlineV3<'a, const AVALANCHE: bool, const PROTECTED: bool> {
seed: u64,
secrets: &'a [u64; 7],
state: RapidStreamChunkState<PROTECTED>,
buffer: [u8; CHUNK_PREV + CHUNK_SIZE],
}
const CHUNK_SIZE: usize = 112;
const CHUNK_PREV: usize = 16;
struct RapidStreamChunkState<const PROTECTED: bool> {
seeds: [u64; 7],
buffer_len: usize,
processed: bool,
}
impl<'a, const AVALANCHE: bool, const PROTECTED: bool> RapidStreamHasherInlineV3<'a, AVALANCHE, PROTECTED> {
#[inline(always)]
pub fn new(secrets: &'a RapidSecrets) -> Self {
Self {
seed: secrets.seed,
secrets: &secrets.secrets,
state: RapidStreamChunkState::new(secrets.seed),
buffer: [0; CHUNK_PREV + CHUNK_SIZE],
}
}
#[inline(always)]
pub fn write(&mut self, data: &[u8]) {
if unlikely(CHUNK_SIZE < self.state.buffer_len + data.len()) {
self.write_inner(data);
return;
}
let start = CHUNK_PREV + self.state.buffer_len;
let end = start + data.len();
self.buffer[start..end].copy_from_slice(data);
self.state.buffer_len += data.len();
}
#[inline]
fn write_inner(&mut self, data: &[u8]) {
let (chunk_prev, chunk_curr) = self.buffer.split_at_mut(CHUNK_PREV);
let chunk_prev: &mut [u8; CHUNK_PREV] = chunk_prev.try_into().unwrap();
let chunk_buffer: &mut [u8; CHUNK_SIZE] = chunk_curr.try_into().unwrap();
let copy_bytes = CHUNK_SIZE - self.state.buffer_len;
let start = self.state.buffer_len;
chunk_buffer[start..].copy_from_slice(&data[..copy_bytes]);
debug_assert_eq!(CHUNK_SIZE, self.state.buffer_len + copy_bytes);
self.state.chunk_write(self.secrets, chunk_buffer);
let remaining_data = &data[copy_bytes..];
let stop = (remaining_data.len().saturating_sub(1) / CHUNK_SIZE) * CHUNK_SIZE;
let mut chunk_last = None;
let mut pos = 0;
while pos < stop {
let chunk = remaining_data[pos..pos + CHUNK_SIZE].try_into().unwrap();
chunk_last = Some(chunk);
self.state.chunk_write(self.secrets, chunk);
pos += CHUNK_SIZE;
}
let unprocessed_data = &remaining_data[pos..];
if let Some(chunk) = chunk_last {
chunk_prev.copy_from_slice(&chunk[CHUNK_SIZE - CHUNK_PREV..]);
} else {
let trailing_end = chunk_buffer.len() - CHUNK_PREV;
chunk_prev.copy_from_slice(&chunk_buffer[trailing_end..]);
}
chunk_buffer[..unprocessed_data.len()].copy_from_slice(unprocessed_data);
self.state.buffer_len = unprocessed_data.len();
}
#[inline(always)]
#[must_use]
pub fn finish(&self) -> u64 {
let mut seed = self.seed;
let mut a;
let mut b;
let remainder;
if likely(!self.state.processed && self.state.buffer_len <= 16) {
let data =
&self.buffer[CHUNK_PREV..CHUNK_PREV + self.state.buffer_len];
if data.len() >= 4 {
seed ^= data.len() as u64;
if data.len() >= 8 {
let plast = data.len() - 8;
a = read_u64(data, 0);
b = read_u64(data, plast);
} else {
let plast = data.len() - 4;
a = read_u32(data, 0) as u64;
b = read_u32(data, plast) as u64;
}
} else if !data.is_empty() {
a = ((data[0] as u64) << 45) | data[data.len() - 1] as u64;
b = data[data.len() >> 1] as u64;
} else {
a = 0;
b = 0;
}
remainder = data.len() as u64;
} else {
if self.state.processed {
seed =
self.state.seeds[0]
^ self.state.seeds[1]
^ self.state.seeds[2]
^ self.state.seeds[3]
^ self.state.seeds[4]
^ self.state.seeds[5]
^ self.state.seeds[6];
}
let slice = &self.buffer[CHUNK_PREV..CHUNK_PREV + self.state.buffer_len];
if slice.len() > 16 {
seed = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ self.secrets[2], read_u64(slice, 8) ^ seed);
if slice.len() > 32 {
seed = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ self.secrets[2], read_u64(slice, 24) ^ seed);
if slice.len() > 48 {
seed = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ self.secrets[1], read_u64(slice, 40) ^ seed);
if slice.len() > 64 {
seed = rapid_mix::<PROTECTED>(read_u64(slice, 48) ^ self.secrets[1], read_u64(slice, 56) ^ seed);
if slice.len() > 80 {
seed = rapid_mix::<PROTECTED>(read_u64(slice, 64) ^ self.secrets[2], read_u64(slice, 72) ^ seed);
if slice.len() > 96 {
seed = rapid_mix::<PROTECTED>(read_u64(slice, 80) ^ self.secrets[1], read_u64(slice, 88) ^ seed);
}
}
}
}
}
}
let data = &self.buffer[..CHUNK_PREV + self.state.buffer_len];
a = read_u64(data, data.len() - 16) ^ slice.len() as u64;
b = read_u64(data, data.len() - 8);
remainder = self.state.buffer_len as u64;
}
a ^= self.secrets[1];
b ^= seed;
(a, b) = rapid_mum::<PROTECTED>(a, b);
if AVALANCHE {
rapidhash_finish::<PROTECTED>(a, b, remainder, self.secrets)
} else {
a ^ b
}
}
#[inline(always)]
pub fn reset(&mut self) {
self.state.reset(self.seed);
}
}
impl<const PROTECTED: bool> RapidStreamChunkState<PROTECTED> {
#[inline(always)]
pub fn new(seed: u64) -> Self {
Self {
seeds: [seed; 7],
processed: false,
buffer_len: 0,
}
}
#[inline(always)]
fn chunk_write(&mut self, secrets: &[u64; 7], chunk: &[u8; 112]) {
let slice = chunk.as_slice();
self.seeds[0] = rapid_mix::<PROTECTED>(read_u64(slice, 0) ^ secrets[0], read_u64(slice, 8) ^ self.seeds[0]);
self.seeds[1] = rapid_mix::<PROTECTED>(read_u64(slice, 16) ^ secrets[1], read_u64(slice, 24) ^ self.seeds[1]);
self.seeds[2] = rapid_mix::<PROTECTED>(read_u64(slice, 32) ^ secrets[2], read_u64(slice, 40) ^ self.seeds[2]);
self.seeds[3] = rapid_mix::<PROTECTED>(read_u64(slice, 48) ^ secrets[3], read_u64(slice, 56) ^ self.seeds[3]);
self.seeds[4] = rapid_mix::<PROTECTED>(read_u64(slice, 64) ^ secrets[4], read_u64(slice, 72) ^ self.seeds[4]);
self.seeds[5] = rapid_mix::<PROTECTED>(read_u64(slice, 80) ^ secrets[5], read_u64(slice, 88) ^ self.seeds[5]);
self.seeds[6] = rapid_mix::<PROTECTED>(read_u64(slice, 96) ^ secrets[6], read_u64(slice, 104) ^ self.seeds[6]);
self.processed = true;
}
#[inline(always)]
pub fn reset(&mut self, seed: u64) {
self.seeds = [seed; 7];
self.processed = false;
self.buffer_len = 0;
}
}
#[cfg(test)]
mod tests {
use crate::util::macros::compare_rapid_stream_hasher;
use crate::v3::{rapidhash_v3_inline, DEFAULT_RAPID_SECRETS};
use super::*;
compare_rapid_stream_hasher!(compare_stream_hasher_v3, rapidhash_v3_inline::<true, false, false>, RapidStreamHasherV3<'a>);
compare_rapid_stream_hasher!(compare_stream_hasher_v3_protected, rapidhash_v3_inline::<true, false, true>, RapidStreamHasherInlineV3::<'a, true, true>);
compare_rapid_stream_hasher!(compare_stream_hasher_v3_no_avalanche, rapidhash_v3_inline::<false, false, false>, RapidStreamHasherInlineV3::<'a, false, false>);
#[test]
fn test_rapid_stream_hasher() {
let secrets = DEFAULT_RAPID_SECRETS;
let data: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7];
let expected_hash = rapidhash_v3_inline::<true, false, false>(data, &secrets);
let mut hasher = RapidStreamHasherV3::new(&secrets);
hasher.write(data);
assert_eq!(expected_hash, hasher.finish());
hasher.reset();
hasher.write(&data[..1]);
hasher.write(&data[1..3]);
hasher.write(&data[3..6]);
hasher.write(&data[6..]);
assert_eq!(expected_hash, hasher.finish());
}
#[test]
fn test_chunk_writing() {
let secrets = DEFAULT_RAPID_SECRETS;
let mut hasher = RapidStreamHasherV3::new(&secrets);
let mut data = [0; 128];
for i in 0..data.len() {
data[i] = i as u8;
}
hasher.write(&data);
assert_eq!(&hasher.buffer[..32], &data[128-32..]);
}
}