#![allow(clippy::too_many_arguments)]
use crate::{
Error, Mode, Result,
pwxform::{PwxformCtx, SWORDS},
salsa20,
util::{cast_slice, hmac_sha256, slice_as_chunks_mut, xor},
};
use alloc::vec::Vec;
const SBYTES: u64 = crate::pwxform::SBYTES as u64;
pub(crate) fn smix(
b: &mut [u32],
r: u32,
n: u64,
p: u32,
t: u32,
mode: Mode,
v: &mut [u32],
xy: &mut [u32],
passwd: &mut [u8],
) -> Result<()> {
let r = r as usize;
let s = 32 * r;
let mut nchunk = n / u64::from(p);
let mut nloop_all = nchunk;
if mode.is_rw() {
if t <= 1 {
if t != 0 {
nloop_all *= 2; }
nloop_all = nloop_all.div_ceil(3); } else {
nloop_all *= u64::from(t) - 1;
}
} else if t != 0 {
if t == 1 {
nloop_all += nloop_all.div_ceil(2); }
nloop_all *= u64::from(t);
}
let mut nloop_rw = 0;
if mode.is_rw() {
nloop_rw = nloop_all / u64::from(p);
}
nchunk &= !1;
nloop_all += 1;
nloop_all &= !1;
nloop_rw += 1;
nloop_rw &= !1;
let mut sn = if mode.is_rw() {
alloc::vec![[0u32; SWORDS]; p as usize]
} else {
Vec::new()
};
let mut ctxs = Vec::with_capacity(sn.len());
let mut sn = sn.iter_mut();
let mut vchunk = 0;
#[allow(clippy::needless_range_loop)]
for i in 0..p as usize {
let np = if i < p as usize - 1 {
nchunk
} else {
n - vchunk
};
let bs = &mut b[(i * s)..];
let vp = &mut v[(usize::try_from(vchunk)? * s)..];
let mut ctx_i = if mode.is_rw() {
let si = sn.next().ok_or(Error::Internal)?;
smix1(
bs,
1,
SBYTES / 128,
Mode::Classic,
&mut si[..],
xy,
&mut None,
)?;
let (s2, s10) = si.split_at_mut((1 << 8) * 4);
let (s1, s0) = s10.split_at_mut((1 << 8) * 4);
let (s2, _) = slice_as_chunks_mut::<_, 2>(s2);
let (s1, _) = slice_as_chunks_mut::<_, 2>(s1);
let (s0, _) = slice_as_chunks_mut::<_, 2>(s0);
let w = 0;
if i == 0 {
let digest = hmac_sha256(cast_slice(&bs[(s - 16)..s])?, &passwd[..32])?;
passwd[..32].copy_from_slice(&digest);
}
ctxs.push(PwxformCtx { s0, s1, s2, w });
ctxs.last_mut()
} else {
None
};
smix1(bs, r, np, mode, vp, xy, &mut ctx_i)?;
smix2(
bs,
r,
prev_power_of_two(np),
nloop_rw,
mode,
vp,
xy,
&mut ctx_i,
)?;
vchunk += nchunk;
}
#[allow(clippy::needless_range_loop)]
for i in 0..p as usize {
let mut ctx_i = if mode.is_rw() {
Some(&mut ctxs[i])
} else {
None
};
smix2(
&mut b[(i * s)..],
r,
n,
nloop_all - nloop_rw,
if mode.is_worm() {
Mode::Worm
} else {
Mode::Classic
},
v,
xy,
&mut ctx_i,
)?;
}
Ok(())
}
fn smix1(
b: &mut [u32],
r: usize,
n: u64,
mode: Mode,
v: &mut [u32],
xy: &mut [u32],
ctx: &mut Option<&mut PwxformCtx<'_>>,
) -> Result<()> {
let s = 32 * r;
let (x, y) = xy.split_at_mut(s);
for k in 0..(2 * r) {
for i in 0..16 {
x[k * 16 + i] = u32::from_le(b[(k * 16) + (i * 5 % 16)]);
}
}
for i in 0..n {
v[(usize::try_from(i)? * s)..][..s].copy_from_slice(x);
if mode.is_rw() && i > 1 {
let n = prev_power_of_two(i);
let j = usize::try_from((integerify(x, r) & (n - 1)) + (i - n))?;
xor(x, &v[j * s..][..s]);
}
match ctx {
Some(ctx) => ctx.blockmix_pwxform(x, r),
None => salsa20::blockmix_salsa8(x, y, r),
}
}
for k in 0..(2 * r) {
for i in 0..16 {
b[(k * 16) + ((i * 5) % 16)] = (x[k * 16 + i]).to_le();
}
}
Ok(())
}
fn smix2(
b: &mut [u32],
r: usize,
n: u64,
nloop: u64,
mode: Mode,
v: &mut [u32],
xy: &mut [u32],
ctx: &mut Option<&mut PwxformCtx<'_>>,
) -> Result<()> {
let s = 32 * r;
let (x, y) = xy.split_at_mut(s);
for k in 0..(2 * r) {
for i in 0..16usize {
x[k * 16 + i] = u32::from_le(b[(k * 16) + (i * 5 % 16)]);
}
}
for _ in 0..nloop {
let j = usize::try_from(integerify(x, r) & (n - 1))?;
xor(x, &v[j * s..][..s]);
if mode.is_rw() {
v[j * s..][..s].copy_from_slice(x);
}
match ctx {
Some(ctx) => ctx.blockmix_pwxform(x, r),
None => salsa20::blockmix_salsa8(x, y, r),
}
}
for k in 0..(2 * r) {
for i in 0..16 {
b[(k * 16) + ((i * 5) % 16)] = (x[k * 16 + i]).to_le();
}
}
Ok(())
}
fn integerify(b: &[u32], r: usize) -> u64 {
let x = &b[((2 * r) - 1) * 16..];
(u64::from(x[13]) << 32).wrapping_add(u64::from(x[0]))
}
fn prev_power_of_two(mut x: u64) -> u64 {
let mut y;
loop {
y = x & (x - 1);
if y == 0 {
break;
}
x = y;
}
x
}