use crate::guts::{self, Finalize, Implementation, Job, LastNode, Stride};
use crate::state_words_to_bytes;
use crate::Count;
use crate::Hash;
use crate::Params;
use crate::State;
use crate::Word;
use crate::BLOCKBYTES;
use arrayvec::ArrayVec;
use core::fmt;
pub const MAX_DEGREE: usize = guts::MAX_DEGREE;
pub fn degree() -> usize {
guts::Implementation::detect().degree()
}
type JobsVec<'a, 'b> = ArrayVec<Job<'a, 'b>, { guts::MAX_DEGREE }>;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline(always)]
fn fill_jobs_vec<'a, 'b>(
jobs_iter: &mut impl Iterator<Item = Job<'a, 'b>>,
vec: &mut JobsVec<'a, 'b>,
target_len: usize,
) {
while vec.len() < target_len {
if let Some(job) = jobs_iter.next() {
vec.push(job);
} else {
break;
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline(always)]
fn evict_finished<'a, 'b>(vec: &mut JobsVec<'a, 'b>, num_jobs: usize) {
for i in (0..num_jobs).rev() {
debug_assert!(vec.len() > i);
if vec.len() > i && vec[i].input.is_empty() {
vec.pop_at(i);
}
}
}
pub(crate) fn compress_many<'a, 'b, I>(
jobs: I,
imp: Implementation,
finalize: Finalize,
stride: Stride,
) where
I: IntoIterator<Item = Job<'a, 'b>>,
{
#[allow(unused_mut)]
let mut jobs_iter = jobs.into_iter().fuse();
#[allow(unused_mut)]
let mut jobs_vec = JobsVec::new();
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if imp.degree() >= 4 {
loop {
fill_jobs_vec(&mut jobs_iter, &mut jobs_vec, 4);
if jobs_vec.len() < 4 {
break;
}
let jobs_array = arrayref::array_mut_ref!(jobs_vec, 0, 4);
imp.compress4_loop(jobs_array, finalize, stride);
evict_finished(&mut jobs_vec, 4);
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if imp.degree() >= 2 {
loop {
fill_jobs_vec(&mut jobs_iter, &mut jobs_vec, 2);
if jobs_vec.len() < 2 {
break;
}
let jobs_array = arrayref::array_mut_ref!(jobs_vec, 0, 2);
imp.compress2_loop(jobs_array, finalize, stride);
evict_finished(&mut jobs_vec, 2);
}
}
for job in jobs_vec.into_iter().chain(jobs_iter) {
let Job {
input,
words,
count,
last_node,
} = job;
imp.compress1_loop(input, words, count, last_node, finalize, stride);
}
}
pub fn update_many<'a, 'b, I, T>(pairs: I)
where
I: IntoIterator<Item = (&'a mut State, &'b T)>,
T: 'b + AsRef<[u8]> + ?Sized,
{
let mut peekable_pairs = pairs.into_iter().peekable();
let implementation = if let Some((state, _)) = peekable_pairs.peek() {
state.implementation
} else {
return;
};
let jobs = peekable_pairs.flat_map(|(state, input_t)| {
let mut input = input_t.as_ref();
state.compress_buffer_if_possible(&mut input);
if input.is_empty() {
return None;
}
let mut last_block_start = input.len() - 1;
last_block_start -= last_block_start % BLOCKBYTES;
let (blocks, last_block) = input.split_at(last_block_start);
state.buf[..last_block.len()].copy_from_slice(last_block);
state.buflen = last_block.len() as u8;
if blocks.is_empty() {
None
} else {
let count = state.count;
state.count = state.count.wrapping_add(blocks.len() as Count);
Some(Job {
input: blocks,
words: &mut state.words,
count,
last_node: state.last_node,
})
}
});
compress_many(jobs, implementation, Finalize::No, Stride::Serial);
}
#[derive(Clone)]
pub struct HashManyJob<'a> {
words: [Word; 8],
count: Count,
last_node: LastNode,
hash_length: u8,
input: &'a [u8],
finished: bool,
implementation: guts::Implementation,
}
impl<'a> HashManyJob<'a> {
#[inline]
pub fn new(params: &Params, input: &'a [u8]) -> Self {
let mut words = params.to_words();
let mut count = 0;
let mut finished = false;
if params.key_length > 0 {
let mut finalization = Finalize::No;
if input.is_empty() {
finalization = Finalize::Yes;
finished = true;
}
params.implementation.compress1_loop(
¶ms.key_block,
&mut words,
0,
params.last_node,
finalization,
Stride::Serial,
);
count = BLOCKBYTES as Count;
}
Self {
words,
count,
last_node: params.last_node,
hash_length: params.hash_length,
input,
finished,
implementation: params.implementation,
}
}
#[inline]
pub fn to_hash(&self) -> Hash {
debug_assert!(self.finished, "job hasn't been run yet");
Hash {
bytes: state_words_to_bytes(&self.words),
len: self.hash_length,
}
}
}
impl<'a> fmt::Debug for HashManyJob<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"HashManyJob {{ count: {}, hash_length: {}, last_node: {}, input_len: {} }}",
self.count,
self.hash_length,
self.last_node.yes(),
self.input.len(),
)
}
}
pub fn hash_many<'a, 'b, I>(hash_many_jobs: I)
where
'b: 'a,
I: IntoIterator<Item = &'a mut HashManyJob<'b>>,
{
let mut peekable_jobs = hash_many_jobs.into_iter().peekable();
let implementation = if let Some(job) = peekable_jobs.peek() {
job.implementation
} else {
return;
};
let unfinished_jobs = peekable_jobs.into_iter().filter(|j| !j.finished);
let jobs = unfinished_jobs.map(|j| {
j.finished = true;
Job {
input: j.input,
words: &mut j.words,
count: j.count,
last_node: j.last_node,
}
});
compress_many(jobs, implementation, Finalize::Yes, Stride::Serial);
}
#[cfg(test)]
mod test {
use super::*;
use crate::guts;
use crate::paint_test_input;
use crate::BLOCKBYTES;
use arrayvec::ArrayVec;
#[test]
fn test_degree() {
assert!(degree() <= MAX_DEGREE);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[cfg(feature = "std")]
{
if is_x86_feature_detected!("avx2") {
assert!(degree() >= 4);
}
if is_x86_feature_detected!("sse4.1") {
assert!(degree() >= 2);
}
}
}
#[test]
fn test_hash_many() {
const LEN: usize = 2 * guts::MAX_DEGREE - 1;
let mut input = [0; LEN * BLOCKBYTES];
paint_test_input(&mut input);
for start_offset in 0..LEN {
let mut inputs: [&[u8]; LEN] = [&[]; LEN];
for i in 0..LEN {
let chunks = (i + start_offset) % LEN;
inputs[i] = &input[..chunks * BLOCKBYTES];
}
let mut params: ArrayVec<Params, LEN> = ArrayVec::new();
for i in 0..LEN {
let mut p = Params::new();
p.node_offset(i as u64);
if i % 2 == 1 {
p.last_node(true);
p.key(b"foo");
}
params.push(p);
}
let mut jobs: ArrayVec<HashManyJob, LEN> = ArrayVec::new();
for i in 0..LEN {
jobs.push(HashManyJob::new(¶ms[i], inputs[i]));
}
hash_many(&mut jobs);
for i in 0..LEN {
let expected = params[i].hash(inputs[i]);
assert_eq!(expected, jobs[i].to_hash());
}
}
}
#[test]
fn test_update_many() {
const LEN: usize = 2 * guts::MAX_DEGREE - 1;
let mut input = [0; LEN * BLOCKBYTES];
paint_test_input(&mut input);
for start_offset in 0..LEN {
let mut inputs: [&[u8]; LEN] = [&[]; LEN];
for i in 0..LEN {
let chunks = (i + start_offset) % LEN;
inputs[i] = &input[..chunks * BLOCKBYTES];
}
let mut params: ArrayVec<Params, LEN> = ArrayVec::new();
for i in 0..LEN {
let mut p = Params::new();
p.node_offset(i as u64);
if i % 2 == 1 {
p.last_node(true);
p.key(b"foo");
}
params.push(p);
}
let mut states: ArrayVec<State, LEN> = ArrayVec::new();
for i in 0..LEN {
states.push(params[i].to_state());
}
update_many(states.iter_mut().zip(inputs.iter()));
update_many(states.iter_mut().zip(inputs.iter()));
for i in 0..LEN {
let mut reference_state = params[i].to_state();
reference_state.update(inputs[i]);
reference_state.update(inputs[i]);
assert_eq!(reference_state.finalize(), states[i].finalize());
assert_eq!(2 * inputs[i].len() as Count, states[i].count());
}
}
}
}