//! SHA-512 (FIPS 180-4).

#![allow(clippy::indexing_slicing)] // Fixed-size arrays + compression schedule

use self::kernels::CompressBlocksFn;
use crate::{
  hashes::{
    crypto::dispatch_util::{SizeClassDispatch, len_hint_from_u128},
    util::{Aligned64, rotr64},
  },
  traits::Digest,
};

#[doc(hidden)]
pub(crate) mod dispatch;
#[doc(hidden)]
pub(crate) mod dispatch_tables;
#[cfg(test)]
mod kernel_test;
pub(crate) mod kernels;

#[cfg(target_arch = "aarch64")]
pub(crate) mod aarch64;
#[cfg(target_arch = "riscv64")]
pub(crate) mod riscv64;
#[cfg(target_arch = "s390x")]
pub(crate) mod s390x;
#[cfg(target_arch = "wasm32")]
pub(crate) mod wasm;
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86_64;
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86_64_avx2;
#[cfg(target_arch = "x86_64")]
pub(crate) mod x86_64_avx512vl;

const BLOCK_LEN: usize = 128;

pub(crate) const H0: [u64; 8] = [
  0x6a09_e667_f3bc_c908,
  0xbb67_ae85_84ca_a73b,
  0x3c6e_f372_fe94_f82b,
  0xa54f_f53a_5f1d_36f1,
  0x510e_527f_ade6_82d1,
  0x9b05_688c_2b3e_6c1f,
  0x1f83_d9ab_fb41_bd6b,
  0x5be0_cd19_137e_2179,
];

/// 80×u64 round constants aligned to a 64-byte cache line boundary.
///
/// Without alignment the 640-byte table can straddle up to 10+1 L1 cache lines,
/// causing extra misses on first access. Aligning to 64 bytes guarantees it
/// starts at a line boundary, filling exactly 10 lines.
static K: Aligned64<[u64; 80]> = Aligned64([
  0x428a_2f98_d728_ae22,
  0x7137_4491_23ef_65cd,
  0xb5c0_fbcf_ec4d_3b2f,
  0xe9b5_dba5_8189_dbbc,
  0x3956_c25b_f348_b538,
  0x59f1_11f1_b605_d019,
  0x923f_82a4_af19_4f9b,
  0xab1c_5ed5_da6d_8118,
  0xd807_aa98_a303_0242,
  0x1283_5b01_4570_6fbe,
  0x2431_85be_4ee4_b28c,
  0x550c_7dc3_d5ff_b4e2,
  0x72be_5d74_f27b_896f,
  0x80de_b1fe_3b16_96b1,
  0x9bdc_06a7_25c7_1235,
  0xc19b_f174_cf69_2694,
  0xe49b_69c1_9ef1_4ad2,
  0xefbe_4786_384f_25e3,
  0x0fc1_9dc6_8b8c_d5b5,
  0x240c_a1cc_77ac_9c65,
  0x2de9_2c6f_592b_0275,
  0x4a74_84aa_6ea6_e483,
  0x5cb0_a9dc_bd41_fbd4,
  0x76f9_88da_8311_53b5,
  0x983e_5152_ee66_dfab,
  0xa831_c66d_2db4_3210,
  0xb003_27c8_98fb_213f,
  0xbf59_7fc7_beef_0ee4,
  0xc6e0_0bf3_3da8_8fc2,
  0xd5a7_9147_930a_a725,
  0x06ca_6351_e003_826f,
  0x1429_2967_0a0e_6e70,
  0x27b7_0a85_46d2_2ffc,
  0x2e1b_2138_5c26_c926,
  0x4d2c_6dfc_5ac4_2aed,
  0x5338_0d13_9d95_b3df,
  0x650a_7354_8baf_63de,
  0x766a_0abb_3c77_b2a8,
  0x81c2_c92e_47ed_aee6,
  0x9272_2c85_1482_353b,
  0xa2bf_e8a1_4cf1_0364,
  0xa81a_664b_bc42_3001,
  0xc24b_8b70_d0f8_9791,
  0xc76c_51a3_0654_be30,
  0xd192_e819_d6ef_5218,
  0xd699_0624_5565_a910,
  0xf40e_3585_5771_202a,
  0x106a_a070_32bb_d1b8,
  0x19a4_c116_b8d2_d0c8,
  0x1e37_6c08_5141_ab53,
  0x2748_774c_df8e_eb99,
  0x34b0_bcb5_e19b_48a8,
  0x391c_0cb3_c5c9_5a63,
  0x4ed8_aa4a_e341_8acb,
  0x5b9c_ca4f_7763_e373,
  0x682e_6ff3_d6b2_b8a3,
  0x748f_82ee_5def_b2fc,
  0x78a5_636f_4317_2f60,
  0x84c8_7814_a1f0_ab72,
  0x8cc7_0208_1a64_39ec,
  0x90be_fffa_2363_1e28,
  0xa450_6ceb_de82_bde9,
  0xbef9_a3f7_b2c6_7915,
  0xc671_78f2_e372_532b,
  0xca27_3ece_ea26_619c,
  0xd186_b8c7_21c0_c207,
  0xeada_7dd6_cde0_eb1e,
  0xf57d_4f7f_ee6e_d178,
  0x06f0_67aa_7217_6fba,
  0x0a63_7dc5_a2c8_98a6,
  0x113f_9804_bef9_0dae,
  0x1b71_0b35_131c_471b,
  0x28db_77f5_2304_7d84,
  0x32ca_ab7b_40c7_2493,
  0x3c9e_be0a_15c9_bebc,
  0x431d_67c4_9c10_0d4c,
  0x4cc5_d4be_cb3e_42b6,
  0x597f_299c_fc65_7e2a,
  0x5fcb_6fab_3ad6_faec,
  0x6c44_198c_4a47_5817,
]);

#[inline(always)]
fn ch(x: u64, y: u64, z: u64) -> u64 {
  (x & y) ^ (!x & z)
}

#[inline(always)]
fn maj(x: u64, y: u64, z: u64) -> u64 {
  (x & y) ^ (x & z) ^ (y & z)
}

#[inline(always)]
pub(super) fn big_sigma0(x: u64) -> u64 {
  rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39)
}

#[inline(always)]
pub(super) fn big_sigma1(x: u64) -> u64 {
  rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41)
}

#[inline(always)]
fn small_sigma0(x: u64) -> u64 {
  rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7)
}

#[inline(always)]
fn small_sigma1(x: u64) -> u64 {
  rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6)
}

/// SHA-512 digest state.
///
/// Standardized in FIPS 180-4.
///
/// # Examples
///
/// ```
/// use rscrypto::{Digest, Sha512};
///
/// let mut hasher = Sha512::new();
/// hasher.update(b"abc");
///
/// assert_eq!(hasher.finalize(), Sha512::digest(b"abc"));
/// ```
#[derive(Clone)]
pub struct Sha512 {
  state: [u64; 8],
  block: [u8; BLOCK_LEN],
  block_len: usize,
  bytes_hashed: u128,
  compress_blocks: CompressBlocksFn,
  dispatch: Option<SizeClassDispatch<CompressBlocksFn>>,
}

#[derive(Clone, Copy)]
#[cfg(feature = "hmac")]
pub(crate) struct Sha512Prefix {
  state: [u64; 8],
  bytes_hashed: u128,
  compress_blocks: CompressBlocksFn,
  dispatch: Option<SizeClassDispatch<CompressBlocksFn>>,
}

#[cfg(feature = "hmac")]
impl Sha512Prefix {
  /// Volatile-zero key-derived state to prevent lingering in memory.
  pub(crate) fn zeroize(&mut self) {
    for word in self.state.iter_mut() {
      // SAFETY: word is a valid, aligned, dereferenceable pointer to initialized memory.
      unsafe { core::ptr::write_volatile(word, 0) };
    }
    // SAFETY: bytes_hashed is a valid, aligned, dereferenceable pointer to initialized memory.
    unsafe { core::ptr::write_volatile(&mut self.bytes_hashed, 0) };
    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
  }
}

impl core::fmt::Debug for Sha512 {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    f.debug_struct("Sha512").finish_non_exhaustive()
  }
}

impl Default for Sha512 {
  #[inline]
  fn default() -> Self {
    Self {
      state: H0,
      block: [0u8; BLOCK_LEN],
      block_len: 0,
      bytes_hashed: 0,
      compress_blocks: Sha512::compress_blocks_portable,
      dispatch: None,
    }
  }
}

impl Sha512 {
  /// Compute the digest of `data` in one shot.
  ///
  /// This selects the best available kernel for the current platform and input
  /// length (cached after first use).
  #[inline]
  #[must_use]
  pub fn digest(data: &[u8]) -> [u8; 64] {
    dispatch::digest(data)
  }

  /// Compute `SHA512(prefix || data)` where `prefix` is exactly 64 bytes.
  ///
  /// This is the hot fixed-prefix shape used by Ed25519 challenge hashing
  /// (`R || A || message`). It avoids constructing a streaming hasher and
  /// shifts only the first half block plus the final tail.
  #[inline]
  #[must_use]
  #[cfg(feature = "ed25519")]
  pub(crate) fn digest_64_byte_prefix(prefix: &[u8; 64], data: &[u8]) -> [u8; 64] {
    let total_len = 64u128.strict_add(data.len() as u128);
    let compress = dispatch::compress_dispatch().select(len_hint_from_u128(total_len));
    Self::digest_64_byte_prefix_with(prefix, data, compress)
  }

  #[inline]
  pub(crate) fn compress_blocks_portable(state: &mut [u64; 8], blocks: &[u8]) {
    debug_assert_eq!(blocks.len() % BLOCK_LEN, 0);
    let mut chunks = blocks.chunks_exact(BLOCK_LEN);
    for chunk in &mut chunks {
      // SAFETY: `chunks_exact(BLOCK_LEN)` yields slices of exactly `BLOCK_LEN` bytes.
      let block = unsafe { &*(chunk.as_ptr() as *const [u8; BLOCK_LEN]) };
      Self::compress_block(state, block);
    }
    debug_assert!(chunks.remainder().is_empty());
  }

  #[inline]
  fn select_compress(&mut self, incoming_len: usize) -> CompressBlocksFn {
    let dispatch = match self.dispatch {
      Some(d) => d,
      None => {
        let d = dispatch::compress_dispatch();
        self.dispatch = Some(d);
        d
      }
    };

    let total = self
      .bytes_hashed
      .strict_add(self.block_len as u128)
      .strict_add(incoming_len as u128);
    let compress = dispatch.select(len_hint_from_u128(total));
    self.compress_blocks = compress;
    compress
  }

  #[inline]
  fn update_with(&mut self, mut data: &[u8], compress_blocks: CompressBlocksFn) {
    if data.is_empty() {
      return;
    }

    if self.block_len != 0 {
      let take = core::cmp::min(BLOCK_LEN.strict_sub(self.block_len), data.len());
      self.block[self.block_len..self.block_len.strict_add(take)].copy_from_slice(&data[..take]);
      self.block_len = self.block_len.strict_add(take);
      data = &data[take..];

      if self.block_len == BLOCK_LEN {
        compress_blocks(&mut self.state, &self.block);
        self.bytes_hashed = self.bytes_hashed.strict_add(BLOCK_LEN as u128);
        self.block_len = 0;
      }
    }

    let full_len = data.len().strict_sub(data.len() % BLOCK_LEN);
    if full_len != 0 {
      let (blocks, rest) = data.split_at(full_len);
      compress_blocks(&mut self.state, blocks);
      self.bytes_hashed = self.bytes_hashed.strict_add(blocks.len() as u128);
      data = rest;
    }

    if !data.is_empty() {
      self.block[..data.len()].copy_from_slice(data);
      self.block_len = data.len();
    }
  }

  #[inline(always)]
  fn compress_block(state: &mut [u64; 8], block: &[u8; BLOCK_LEN]) {
    compress_block_with(state, block, big_sigma0, big_sigma1, small_sigma0, small_sigma1);
  }

  #[inline(always)]
  #[cfg(any(feature = "ed25519", test))]
  fn digest_64_byte_prefix_with(prefix: &[u8; 64], data: &[u8], compress_blocks: CompressBlocksFn) -> [u8; 64] {
    let total_len = 64u128.strict_add(data.len() as u128);
    let mut state = H0;
    let mut block = [0u8; BLOCK_LEN];
    block[..64].copy_from_slice(prefix);

    let first_take = core::cmp::min(64, data.len());
    let first_end = 64usize.strict_add(first_take);
    block[64..first_end].copy_from_slice(&data[..first_take]);
    let mut rest = &data[first_take..];

    if first_take < 64 {
      return Self::finalize_state(state, block, first_end, total_len, compress_blocks);
    }

    compress_blocks(&mut state, &block);

    let full_len = rest.len().strict_sub(rest.len() % BLOCK_LEN);
    if full_len != 0 {
      let (blocks, tail) = rest.split_at(full_len);
      compress_blocks(&mut state, blocks);
      rest = tail;
    }

    block = [0u8; BLOCK_LEN];
    block[..rest.len()].copy_from_slice(rest);
    Self::finalize_state(state, block, rest.len(), total_len, compress_blocks)
  }

  #[inline(always)]
  fn finalize_state(
    state: [u64; 8],
    block: [u8; BLOCK_LEN],
    block_len: usize,
    total_len: u128,
    compress_blocks: CompressBlocksFn,
  ) -> [u8; 64] {
    let state = Self::finalize_state_words(state, block, block_len, total_len, compress_blocks);
    Self::state_to_digest(&state)
  }

  #[inline(always)]
  pub(crate) fn finalize_state_words(
    mut state: [u64; 8],
    mut block: [u8; BLOCK_LEN],
    mut block_len: usize,
    total_len: u128,
    compress_blocks: CompressBlocksFn,
  ) -> [u64; 8] {
    let bit_len = total_len << 3;

    block[block_len] = 0x80;
    block_len = block_len.strict_add(1);

    if block_len > 112 {
      block[block_len..].fill(0);
      compress_blocks(&mut state, &block);
      block = [0u8; BLOCK_LEN];
      block_len = 0;
    }

    block[block_len..112].fill(0);
    block[112..128].copy_from_slice(&bit_len.to_be_bytes());
    compress_blocks(&mut state, &block);

    state
  }

  #[inline(always)]
  fn finalize_inner_with(&self, compress_blocks: CompressBlocksFn) -> [u8; 64] {
    let total_len = self.bytes_hashed.strict_add(self.block_len as u128);
    Self::finalize_state(self.state, self.block, self.block_len, total_len, compress_blocks)
  }

  #[inline(always)]
  fn state_to_digest(state: &[u64; 8]) -> [u8; 64] {
    let mut out = [0u8; 64];
    for (chunk, &word) in out.chunks_exact_mut(8).zip(state.iter()) {
      chunk.copy_from_slice(&word.to_be_bytes());
    }
    out
  }

  #[inline]
  #[must_use]
  #[cfg(feature = "hmac")]
  pub(crate) fn aligned_prefix(&self) -> Sha512Prefix {
    debug_assert_eq!(self.block_len, 0);
    Sha512Prefix {
      state: self.state,
      bytes_hashed: self.bytes_hashed,
      compress_blocks: self.compress_blocks,
      dispatch: self.dispatch,
    }
  }

  #[inline]
  #[must_use]
  #[cfg(feature = "hmac")]
  pub(crate) fn from_aligned_prefix(prefix: Sha512Prefix) -> Self {
    Self {
      state: prefix.state,
      block: [0u8; BLOCK_LEN],
      block_len: 0,
      bytes_hashed: prefix.bytes_hashed,
      compress_blocks: prefix.compress_blocks,
      dispatch: prefix.dispatch,
    }
  }

  #[inline]
  #[cfg(feature = "hmac")]
  pub(crate) fn reset_to_aligned_prefix(&mut self, prefix: Sha512Prefix) {
    self.state = prefix.state;
    self.block_len = 0;
    self.bytes_hashed = prefix.bytes_hashed;
    self.compress_blocks = prefix.compress_blocks;
    self.dispatch = prefix.dispatch;
  }

  #[cfg(all(feature = "hmac", any(test, feature = "diag")))]
  #[inline]
  #[allow(dead_code)]
  pub(crate) fn new_with_compress_for_test(compress_blocks: CompressBlocksFn) -> Self {
    Self {
      state: H0,
      block: [0u8; BLOCK_LEN],
      block_len: 0,
      bytes_hashed: 0,
      compress_blocks,
      dispatch: Some(SizeClassDispatch {
        boundaries: [usize::MAX; 3],
        xs: compress_blocks,
        s: compress_blocks,
        m: compress_blocks,
        l: compress_blocks,
      }),
    }
  }
}

/// Read SHA-512 round constant K[i].
///
/// On x86/x86_64, 64-bit constants can sometimes be encoded via `movabs`+`add`,
/// but on all other architectures (POWER, aarch64, s390x), materializing a
/// 64-bit immediate requires 4+ instructions. Loading from the static K array
/// via a single `ld`/`ldr` is far cheaper.
///
/// We use `core::hint::black_box` on the table base pointer rather than
/// `core::ptr::read_volatile` — see the matching `sha256::rk()` doc for
/// the LLVM ppc64le miscompilation that motivated dropping `volatile`
/// here too.
#[inline(always)]
fn rk(i: usize) -> u64 {
  #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
  {
    // SAFETY: i is always in 0..80, and K has exactly 80 elements.
    unsafe { core::ptr::read(K.0.as_ptr().add(i)) }
  }
  #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
  {
    let base = core::hint::black_box(K.0.as_ptr());
    // SAFETY: i is always in 0..80, K has exactly 80 elements, and
    // `black_box(ptr)` returns the same provenance / value, only opaque
    // to subsequent constant propagation.
    unsafe { core::ptr::read(base.add(i)) }
  }
}

/// Core SHA-512 block compression, parameterized over the four sigma/sum
/// operations. The portable kernel passes software rotate-xor-shift; RISC-V
/// Zknh passes hardware `sha512sum0/1` and `sha512sig0/1` intrinsics.
///
/// The compiler devirtualizes constant `fn` items at all optimization levels,
/// producing identical codegen to a hand-inlined version.
#[inline(always)]
pub(crate) fn compress_block_with(
  state: &mut [u64; 8],
  block: &[u8; BLOCK_LEN],
  big_s0: fn(u64) -> u64,
  big_s1: fn(u64) -> u64,
  small_s0: fn(u64) -> u64,
  small_s1: fn(u64) -> u64,
) {
  let mut w = [0u64; 16];
  let (chunks, _) = block.as_chunks::<8>();
  for (i, c) in chunks.iter().enumerate() {
    w[i] = u64::from_be_bytes(*c);
  }
  let [
    mut w0,
    mut w1,
    mut w2,
    mut w3,
    mut w4,
    mut w5,
    mut w6,
    mut w7,
    mut w8,
    mut w9,
    mut w10,
    mut w11,
    mut w12,
    mut w13,
    mut w14,
    mut w15,
  ] = w;

  let mut a = state[0];
  let mut b = state[1];
  let mut c = state[2];
  let mut d = state[3];
  let mut e = state[4];
  let mut f = state[5];
  let mut g = state[6];
  let mut h = state[7];

  macro_rules! round {
    ($k:expr, $wi:expr) => {{
      let t1 = h
        .wrapping_add(big_s1(e))
        .wrapping_add(ch(e, f, g))
        .wrapping_add($k)
        .wrapping_add($wi);
      let t2 = big_s0(a).wrapping_add(maj(a, b, c));
      h = g;
      g = f;
      f = e;
      e = d.wrapping_add(t1);
      d = c;
      c = b;
      b = a;
      a = t1.wrapping_add(t2);
    }};
  }

  macro_rules! sched {
    ($w2:expr, $w7:expr, $w15:expr, $w16:expr) => {{
      small_s1($w2)
        .wrapping_add($w7)
        .wrapping_add(small_s0($w15))
        .wrapping_add($w16)
    }};
  }

  round!(rk(0), w0);
  round!(rk(1), w1);
  round!(rk(2), w2);
  round!(rk(3), w3);
  round!(rk(4), w4);
  round!(rk(5), w5);
  round!(rk(6), w6);
  round!(rk(7), w7);
  round!(rk(8), w8);
  round!(rk(9), w9);
  round!(rk(10), w10);
  round!(rk(11), w11);
  round!(rk(12), w12);
  round!(rk(13), w13);
  round!(rk(14), w14);
  round!(rk(15), w15);

  w0 = sched!(w14, w9, w1, w0);
  round!(rk(16), w0);
  w1 = sched!(w15, w10, w2, w1);
  round!(rk(17), w1);
  w2 = sched!(w0, w11, w3, w2);
  round!(rk(18), w2);
  w3 = sched!(w1, w12, w4, w3);
  round!(rk(19), w3);
  w4 = sched!(w2, w13, w5, w4);
  round!(rk(20), w4);
  w5 = sched!(w3, w14, w6, w5);
  round!(rk(21), w5);
  w6 = sched!(w4, w15, w7, w6);
  round!(rk(22), w6);
  w7 = sched!(w5, w0, w8, w7);
  round!(rk(23), w7);
  w8 = sched!(w6, w1, w9, w8);
  round!(rk(24), w8);
  w9 = sched!(w7, w2, w10, w9);
  round!(rk(25), w9);
  w10 = sched!(w8, w3, w11, w10);
  round!(rk(26), w10);
  w11 = sched!(w9, w4, w12, w11);
  round!(rk(27), w11);
  w12 = sched!(w10, w5, w13, w12);
  round!(rk(28), w12);
  w13 = sched!(w11, w6, w14, w13);
  round!(rk(29), w13);
  w14 = sched!(w12, w7, w15, w14);
  round!(rk(30), w14);
  w15 = sched!(w13, w8, w0, w15);
  round!(rk(31), w15);

  w0 = sched!(w14, w9, w1, w0);
  round!(rk(32), w0);
  w1 = sched!(w15, w10, w2, w1);
  round!(rk(33), w1);
  w2 = sched!(w0, w11, w3, w2);
  round!(rk(34), w2);
  w3 = sched!(w1, w12, w4, w3);
  round!(rk(35), w3);
  w4 = sched!(w2, w13, w5, w4);
  round!(rk(36), w4);
  w5 = sched!(w3, w14, w6, w5);
  round!(rk(37), w5);
  w6 = sched!(w4, w15, w7, w6);
  round!(rk(38), w6);
  w7 = sched!(w5, w0, w8, w7);
  round!(rk(39), w7);
  w8 = sched!(w6, w1, w9, w8);
  round!(rk(40), w8);
  w9 = sched!(w7, w2, w10, w9);
  round!(rk(41), w9);
  w10 = sched!(w8, w3, w11, w10);
  round!(rk(42), w10);
  w11 = sched!(w9, w4, w12, w11);
  round!(rk(43), w11);
  w12 = sched!(w10, w5, w13, w12);
  round!(rk(44), w12);
  w13 = sched!(w11, w6, w14, w13);
  round!(rk(45), w13);
  w14 = sched!(w12, w7, w15, w14);
  round!(rk(46), w14);
  w15 = sched!(w13, w8, w0, w15);
  round!(rk(47), w15);

  w0 = sched!(w14, w9, w1, w0);
  round!(rk(48), w0);
  w1 = sched!(w15, w10, w2, w1);
  round!(rk(49), w1);
  w2 = sched!(w0, w11, w3, w2);
  round!(rk(50), w2);
  w3 = sched!(w1, w12, w4, w3);
  round!(rk(51), w3);
  w4 = sched!(w2, w13, w5, w4);
  round!(rk(52), w4);
  w5 = sched!(w3, w14, w6, w5);
  round!(rk(53), w5);
  w6 = sched!(w4, w15, w7, w6);
  round!(rk(54), w6);
  w7 = sched!(w5, w0, w8, w7);
  round!(rk(55), w7);
  w8 = sched!(w6, w1, w9, w8);
  round!(rk(56), w8);
  w9 = sched!(w7, w2, w10, w9);
  round!(rk(57), w9);
  w10 = sched!(w8, w3, w11, w10);
  round!(rk(58), w10);
  w11 = sched!(w9, w4, w12, w11);
  round!(rk(59), w11);
  w12 = sched!(w10, w5, w13, w12);
  round!(rk(60), w12);
  w13 = sched!(w11, w6, w14, w13);
  round!(rk(61), w13);
  w14 = sched!(w12, w7, w15, w14);
  round!(rk(62), w14);
  w15 = sched!(w13, w8, w0, w15);
  round!(rk(63), w15);

  w0 = sched!(w14, w9, w1, w0);
  round!(rk(64), w0);
  w1 = sched!(w15, w10, w2, w1);
  round!(rk(65), w1);
  w2 = sched!(w0, w11, w3, w2);
  round!(rk(66), w2);
  w3 = sched!(w1, w12, w4, w3);
  round!(rk(67), w3);
  w4 = sched!(w2, w13, w5, w4);
  round!(rk(68), w4);
  w5 = sched!(w3, w14, w6, w5);
  round!(rk(69), w5);
  w6 = sched!(w4, w15, w7, w6);
  round!(rk(70), w6);
  w7 = sched!(w5, w0, w8, w7);
  round!(rk(71), w7);
  w8 = sched!(w6, w1, w9, w8);
  round!(rk(72), w8);
  w9 = sched!(w7, w2, w10, w9);
  round!(rk(73), w9);
  w10 = sched!(w8, w3, w11, w10);
  round!(rk(74), w10);
  w11 = sched!(w9, w4, w12, w11);
  round!(rk(75), w11);
  w12 = sched!(w10, w5, w13, w12);
  round!(rk(76), w12);
  w13 = sched!(w11, w6, w14, w13);
  round!(rk(77), w13);
  w14 = sched!(w12, w7, w15, w14);
  round!(rk(78), w14);
  w15 = sched!(w13, w8, w0, w15);
  round!(rk(79), w15);

  state[0] = state[0].wrapping_add(a);
  state[1] = state[1].wrapping_add(b);
  state[2] = state[2].wrapping_add(c);
  state[3] = state[3].wrapping_add(d);
  state[4] = state[4].wrapping_add(e);
  state[5] = state[5].wrapping_add(f);
  state[6] = state[6].wrapping_add(g);
  state[7] = state[7].wrapping_add(h);
}

impl Drop for Sha512 {
  fn drop(&mut self) {
    for word in self.state.iter_mut() {
      // SAFETY: word is a valid, aligned, dereferenceable pointer to initialized memory.
      unsafe { core::ptr::write_volatile(word, 0) };
    }
    crate::traits::ct::zeroize(&mut self.block);
    // SAFETY: field is a valid, aligned, dereferenceable pointer to initialized memory.
    unsafe { core::ptr::write_volatile(&mut self.bytes_hashed, 0) };
    // SAFETY: field is a valid, aligned, dereferenceable pointer to initialized memory.
    unsafe { core::ptr::write_volatile(&mut self.block_len, 0) };
    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
  }
}

impl Digest for Sha512 {
  const OUTPUT_SIZE: usize = 64;
  type Output = [u8; 64];

  #[inline]
  fn new() -> Self {
    Self::default()
  }

  #[inline]
  fn update(&mut self, data: &[u8]) {
    if data.is_empty() {
      return;
    }
    let compress = self.select_compress(data.len());
    self.update_with(data, compress);
  }

  #[inline]
  fn finalize(&self) -> Self::Output {
    self.finalize_inner_with(self.compress_blocks)
  }

  #[inline]
  fn reset(&mut self) {
    *self = Self::default();
  }

  #[inline]
  fn digest(data: &[u8]) -> Self::Output {
    dispatch::digest(data)
  }
}

impl_std_io_write_for_digest!(Sha512);

#[cfg(test)]
mod tests {

  use super::Sha512;

  fn hex64(bytes: &[u8; 64]) -> alloc::string::String {
    use alloc::string::String;
    use core::fmt::Write;
    let mut s = String::new();
    for &b in bytes {
      write!(&mut s, "{:02x}", b).unwrap();
    }
    s
  }

  #[test]
  fn known_vectors() {
    // NIST FIPS 180-4 test vectors (short messages).
    assert_eq!(
      hex64(&Sha512::digest(b"")),
      "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
    );
    assert_eq!(
      hex64(&Sha512::digest(b"abc")),
      "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
    );
  }
}
