// MIT License

// Copyright (c) 2018-2026 The orion Developers

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//! # Parameters:
//! - `secret_key`: The authentication key.
//! - `data`: Data to be authenticated.
//! - `expected`: The expected authentication tag.
//!
//! # Errors:
//! An error will be returned if:
//! - [`finalize()`] is called twice without a [`reset()`] in between.
//! - [`update()`] is called after [`finalize()`] without a [`reset()`] in
//!   between.
//! - The HMAC does not match the expected when verifying.
//!
//! # Security:
//! - The secret key should always be generated using a CSPRNG.
//!   [`SecretKey::generate()`] can be used for this.
//! - The minimum recommended size for a secret key is 64 bytes.
//!
//! # Recommendation:
//! - If you are unsure of whether to use HMAC or Poly1305, it is most often
//!   easier to just use HMAC. See also [Cryptographic Right Answers].
//!
//! # Example:
//! ```rust
//! # #[cfg(feature = "safe_api")] {
//! use orion::hazardous::mac::hmac::sha512::{HmacSha512, SecretKey};
//!
//! let key = SecretKey::generate();
//!
//! let mut state = HmacSha512::new(&key);
//! state.update(b"Some message.")?;
//! let tag = state.finalize()?;
//!
//! assert!(HmacSha512::verify(&tag, &key, b"Some message.").is_ok());
//! # }
//! # Ok::<(), orion::errors::UnknownCryptoError>(())
//! ```
//! [`update()`]: hmac::sha512::HmacSha512::update
//! [`reset()`]: hmac::sha512::HmacSha512::reset
//! [`finalize()`]: hmac::sha512::HmacSha512::finalize
//! [`SecretKey::generate()`]: hmac::sha512::SecretKey::generate
//! [Cryptographic Right Answers]: https://latacora.micro.blog/2018/04/03/cryptographic-right-answers.html

use crate::errors::UnknownCryptoError;
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;

/// A trait used to define a cryptographic hash function used by HMAC.
pub(crate) trait HmacHashFunction: Clone {
    /// The blocksize of the hash function.
    const _BLOCKSIZE: usize;

    /// The output size of the hash function.
    const _OUTSIZE: usize;

    /// Create a new instance of the hash function.
    fn _new() -> Self;

    /// Update the internal state with `data`.
    fn _update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError>;

    /// Finalize the hash and put the final digest into `dest`.
    fn _finalize(&mut self, dest: &mut [u8]) -> Result<(), UnknownCryptoError>;

    /// Compute a digest of `data` and copy it into `dest`.
    fn _digest(data: &[u8], dest: &mut [u8]) -> Result<(), UnknownCryptoError>;

    #[cfg(test)]
    /// Compare two Sha2 state objects to check if their fields
    /// are the same.
    fn compare_state_to_other(&self, other: &Self);
}

/// A trait used to define an HMAC function.
pub(crate) trait HmacFunction {
    /// The output size of the internal hash function used.
    const HASH_FUNC_OUTSIZE: usize;

    /// Create a new instance of the HMAC function, using a `secret_key` that may or may not be padded.
    fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError>
    where
        Self: Sized;

    /// Update the internal state with `data`.
    fn _update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError>;

    /// Finalize the MAC and put the final tag into `dest`.
    ///
    /// NOTE: `dest` may be less than the complete output size of the hash function
    /// (Self::HASH_FUNC_OUTSIZE). If that is the case, `dest.len()` bytes will be copied,
    /// but `dest` should NEVER be empty.
    fn _finalize(&mut self, dest: &mut [u8]) -> Result<(), UnknownCryptoError>;

    /// Reset the state.
    fn _reset(&mut self);
}

const IPAD: u8 = 0x36;
const OPAD: u8 = 0x5C;

#[derive(Clone)]
pub(crate) struct Hmac<S: HmacHashFunction, const BLOCKSIZE: usize> {
    working_hasher: S,
    opad_hasher: S,
    ipad_hasher: S,
    is_finalized: bool,
}

impl<S: HmacHashFunction, const BLOCKSIZE: usize> core::fmt::Debug for Hmac<S, BLOCKSIZE> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "Hmac {{ working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: {:?} }}",
            self.is_finalized
        )
    }
}

impl<S: HmacHashFunction, const BLOCKSIZE: usize> Hmac<S, BLOCKSIZE> {
    /// Construct a state from a `secret_key`. The `secret_key` may be pre-padded or not.
    ///
    /// Ref: https://brycx.github.io/2018/08/06/hmac-and-precomputation-optimization.html
    fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
        debug_assert_eq!(S::_BLOCKSIZE, BLOCKSIZE);
        let mut ipad = [IPAD; BLOCKSIZE];

        if secret_key.len() > BLOCKSIZE {
            // SK is NOT pre-padded.
            debug_assert!(BLOCKSIZE > S::_OUTSIZE);
            S::_digest(secret_key, &mut ipad[..S::_OUTSIZE])?;
            for elem in ipad.iter_mut().take(S::_OUTSIZE) {
                *elem ^= IPAD;
            }
        } else {
            // SK has been pre-padded or SK.len() <= BLOCKSIZE.
            // Because 0x00 xor IPAD = IPAD, the existence of padding bytes (0x00)
            // within SK, during this operation, is inconsequential.
            xor_slices!(secret_key, &mut ipad);
        }

        let mut ih = S::_new();
        ih._update(&ipad)?;

        // Transform ipad into OPAD xor SK
        for elem in ipad.iter_mut() {
            *elem ^= IPAD ^ OPAD;
        }

        let mut oh = S::_new();
        oh._update(&ipad)?;

        #[cfg(feature = "zeroize")]
        ipad.iter_mut().zeroize();

        Ok(Self {
            working_hasher: ih.clone(),
            opad_hasher: oh,
            ipad_hasher: ih,
            is_finalized: false,
        })
    }

    fn _update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
        if self.is_finalized {
            Err(UnknownCryptoError)
        } else {
            self.working_hasher._update(data)
        }
    }

    fn _finalize(&mut self, dest: &mut [u8]) -> Result<(), UnknownCryptoError> {
        debug_assert!(!dest.is_empty());
        if self.is_finalized {
            return Err(UnknownCryptoError);
        }

        self.is_finalized = true;
        let mut outer_hasher = self.opad_hasher.clone();
        self.working_hasher._finalize(dest)?;
        outer_hasher._update(dest)?;
        outer_hasher._finalize(dest)
    }

    fn _reset(&mut self) {
        self.working_hasher = self.ipad_hasher.clone();
        self.is_finalized = false;
    }

    #[cfg(test)]
    /// Compare two Hmac state objects to check if their fields
    /// are the same.
    pub(crate) fn compare_state_to_other(&self, other: &Self) {
        self.working_hasher
            .compare_state_to_other(&other.working_hasher);
        self.opad_hasher.compare_state_to_other(&other.opad_hasher);
        self.ipad_hasher.compare_state_to_other(&other.ipad_hasher);
        assert_eq!(self.is_finalized, other.is_finalized);
    }
}

/// HMAC-SHA256 (Hash-based Message Authentication Code) as specified in the [RFC 2104](https://tools.ietf.org/html/rfc2104).
pub mod sha256 {
    use super::*;
    use crate::hazardous::hash::sha2::sha256::{self, Sha256};

    construct_hmac_key! {
        /// A type to represent the `SecretKey` that HMAC uses for authentication.
        ///
        /// # Note:
        /// `SecretKey` pads the secret key for use with HMAC to a length of 64, when initialized.
        ///
        /// Using `unprotected_as_bytes()` will return the secret key with padding.
        ///
        /// `len()` will return the length with padding (always 64).
        ///
        /// # Panics:
        /// A panic will occur if:
        /// - Failure to generate random bytes securely.
        (SecretKey, Sha256, sha256::SHA256_OUTSIZE, test_hmac_key, sha256::SHA256_BLOCKSIZE)
    }

    construct_tag! {
        /// A type to represent the `Tag` that HMAC returns.
        ///
        /// # Errors:
        /// An error will be returned if:
        /// - `slice` is not 32 bytes.
        (Tag, test_tag, sha256::SHA256_OUTSIZE, sha256::SHA256_OUTSIZE)
    }

    impl_from_trait!(Tag, sha256::SHA256_OUTSIZE);

    use super::Hmac;

    #[derive(Clone, Debug)]
    /// HMAC-SHA256 streaming state.
    pub struct HmacSha256 {
        _state: Hmac<Sha256, { sha256::SHA256_BLOCKSIZE }>,
    }

    impl HmacSha256 {
        fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
            Ok(Self {
                _state: Hmac::<Sha256, { sha256::SHA256_BLOCKSIZE }>::_new(secret_key)?,
            })
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Initialize `HmacSha256` struct with a given key.
        pub fn new(secret_key: &SecretKey) -> Self {
            // NOTE: `secret_key` has been pre-padded so .unwrap() is OK.
            Self::_new(secret_key.unprotected_as_bytes()).unwrap()
        }

        /// Reset to `new()` state.
        pub fn reset(&mut self) {
            self._state._reset()
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Update state with `data`. This can be called multiple times.
        pub fn update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
            self._state._update(data)
        }

        /// Return an HMAC-SHA256 tag.
        pub(crate) fn _finalize_internal(
            &mut self,
            dest: &mut [u8],
        ) -> Result<(), UnknownCryptoError> {
            self._state._finalize(dest)
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Return an HMAC-SHA256 tag.
        pub fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
            let mut dest = [0u8; sha256::SHA256_OUTSIZE];
            self._finalize_internal(&mut dest)?;

            Ok(Tag::from(dest))
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// One-shot function for generating an HMAC-SHA256 tag of `data`.
        pub fn hmac(secret_key: &SecretKey, data: &[u8]) -> Result<Tag, UnknownCryptoError> {
            let mut ctx = Self::new(secret_key);
            ctx.update(data)?;
            ctx.finalize()
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Verify an HMAC-SHA256 tag in constant time.
        pub fn verify(
            expected: &Tag,
            secret_key: &SecretKey,
            data: &[u8],
        ) -> Result<(), UnknownCryptoError> {
            if &Self::hmac(secret_key, data)? == expected {
                Ok(())
            } else {
                Err(UnknownCryptoError)
            }
        }
    }

    impl HmacFunction for HmacSha256 {
        /// The output size of the internal hash function used.
        const HASH_FUNC_OUTSIZE: usize = sha256::SHA256_OUTSIZE;

        /// Create a new instance of the HMAC function, using a `secret_key` that may or may not be padded.
        fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
            Self::_new(secret_key)
        }

        /// Update the internal state with `data`.
        fn _update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
            self._state._update(data)
        }

        /// Finalize the MAC and put the final tag into `dest`.
        ///
        /// NOTE: `dest` may be less than the complete output size of the hash function
        /// (Self::HASH_FUNC_OUTSIZE). If that is the case, `dest.len()` bytes will be copied,
        /// but `dest` should NEVER be empty.
        fn _finalize(&mut self, dest: &mut [u8]) -> Result<(), UnknownCryptoError> {
            self._state._finalize(dest)
        }

        /// Reset the state.
        fn _reset(&mut self) {
            self._state._reset()
        }
    }

    #[cfg(test)]
    mod public {
        use super::*;

        #[test]
        #[cfg(feature = "safe_api")]
        fn test_debug_impl() {
            let secret_key = SecretKey::generate();
            let initial_state = HmacSha256::new(&secret_key);
            let debug = format!("{initial_state:?}");
            let expected = "HmacSha256 { _state: Hmac { working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: false } }";
            assert_eq!(debug, expected);
        }

        #[cfg(feature = "safe_api")]
        mod test_verify {
            use super::*;

            #[quickcheck]
            #[cfg(feature = "safe_api")]
            /// When using a different key, verify() should always yield an error.
            /// NOTE: Using different and same input data is tested with TestableStreamingContext.
            fn prop_verify_diff_key_false(data: Vec<u8>) -> bool {
                let sk = SecretKey::generate();
                let mut state = HmacSha256::new(&sk);
                state.update(&data[..]).unwrap();
                let tag = state.finalize().unwrap();
                let bad_sk = SecretKey::generate();

                HmacSha256::verify(&tag, &bad_sk, &data[..]).is_err()
            }
        }

        mod test_streaming_interface {
            use super::*;
            use crate::test_framework::incremental_interface::*;

            const KEY: [u8; 32] = [0u8; 32];

            impl TestableStreamingContext<Tag> for HmacSha256 {
                fn reset(&mut self) -> Result<(), UnknownCryptoError> {
                    self.reset();
                    Ok(())
                }

                fn update(&mut self, input: &[u8]) -> Result<(), UnknownCryptoError> {
                    self.update(input)
                }

                fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
                    self.finalize()
                }

                fn one_shot(input: &[u8]) -> Result<Tag, UnknownCryptoError> {
                    HmacSha256::hmac(&SecretKey::from_slice(&KEY).unwrap(), input)
                }

                fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> {
                    // This will only run verification tests on differing input. They do not
                    // include tests for different secret keys.
                    HmacSha256::verify(expected, &SecretKey::from_slice(&KEY).unwrap(), input)
                }

                fn compare_states(state_1: &HmacSha256, state_2: &HmacSha256) {
                    state_1._state.compare_state_to_other(&state_2._state);
                }
            }

            #[test]
            fn default_consistency_tests() {
                let initial_state = HmacSha256::new(&SecretKey::from_slice(&KEY).unwrap());

                let test_runner = StreamingContextConsistencyTester::<Tag, HmacSha256>::new(
                    initial_state,
                    sha256::SHA256_BLOCKSIZE,
                );
                test_runner.run_all_tests();
            }

            #[quickcheck]
            #[cfg(feature = "safe_api")]
            /// Related bug: https://github.com/orion-rs/orion/issues/46
            /// Test different streaming state usage patterns.
            fn prop_input_to_consistency(data: Vec<u8>) -> bool {
                let initial_state = HmacSha256::new(&SecretKey::from_slice(&KEY).unwrap());

                let test_runner = StreamingContextConsistencyTester::<Tag, HmacSha256>::new(
                    initial_state,
                    sha256::SHA256_BLOCKSIZE,
                );
                test_runner.run_all_tests_property(&data);
                true
            }
        }
    }
}

/// HMAC-SHA384 (Hash-based Message Authentication Code) as specified in the [RFC 2104](https://tools.ietf.org/html/rfc2104).
pub mod sha384 {
    use super::*;
    use crate::hazardous::hash::sha2::sha384::{self, Sha384};

    construct_hmac_key! {
        /// A type to represent the `SecretKey` that HMAC uses for authentication.
        ///
        /// # Note:
        /// `SecretKey` pads the secret key for use with HMAC to a length of 128, when initialized.
        ///
        /// Using `unprotected_as_bytes()` will return the secret key with padding.
        ///
        /// `len()` will return the length with padding (always 128).
        ///
        /// # Panics:
        /// A panic will occur if:
        /// - Failure to generate random bytes securely.
        (SecretKey, Sha384, sha384::SHA384_OUTSIZE, test_hmac_key, sha384::SHA384_BLOCKSIZE)
    }

    construct_tag! {
        /// A type to represent the `Tag` that HMAC returns.
        ///
        /// # Errors:
        /// An error will be returned if:
        /// - `slice` is not 48 bytes.
        (Tag, test_tag, sha384::SHA384_OUTSIZE, sha384::SHA384_OUTSIZE)
    }

    impl_from_trait!(Tag, sha384::SHA384_OUTSIZE);

    use super::Hmac;

    #[derive(Clone, Debug)]
    /// HMAC-SHA384 streaming state.
    pub struct HmacSha384 {
        _state: Hmac<Sha384, { sha384::SHA384_BLOCKSIZE }>,
    }

    impl HmacSha384 {
        fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
            Ok(Self {
                _state: Hmac::<Sha384, { sha384::SHA384_BLOCKSIZE }>::_new(secret_key)?,
            })
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Initialize `HmacSha384` struct with a given key.
        pub fn new(secret_key: &SecretKey) -> Self {
            // NOTE: `secret_key` has been pre-padded so .unwrap() is OK.
            Self::_new(secret_key.unprotected_as_bytes()).unwrap()
        }

        /// Reset to `new()` state.
        pub fn reset(&mut self) {
            self._state._reset()
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Update state with `data`. This can be called multiple times.
        pub fn update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
            self._state._update(data)
        }

        /// Return an HMAC-SHA384 tag.
        pub(crate) fn _finalize_internal(
            &mut self,
            dest: &mut [u8],
        ) -> Result<(), UnknownCryptoError> {
            self._state._finalize(dest)
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Return an HMAC-SHA384 tag.
        pub fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
            let mut dest = [0u8; sha384::SHA384_OUTSIZE];
            self._finalize_internal(&mut dest)?;

            Ok(Tag::from(dest))
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// One-shot function for generating an HMAC-SHA384 tag of `data`.
        pub fn hmac(secret_key: &SecretKey, data: &[u8]) -> Result<Tag, UnknownCryptoError> {
            let mut ctx = Self::new(secret_key);
            ctx.update(data)?;
            ctx.finalize()
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Verify an HMAC-SHA384 tag in constant time.
        pub fn verify(
            expected: &Tag,
            secret_key: &SecretKey,
            data: &[u8],
        ) -> Result<(), UnknownCryptoError> {
            if &Self::hmac(secret_key, data)? == expected {
                Ok(())
            } else {
                Err(UnknownCryptoError)
            }
        }
    }

    impl HmacFunction for HmacSha384 {
        /// The output size of the internal hash function used.
        const HASH_FUNC_OUTSIZE: usize = sha384::SHA384_OUTSIZE;

        /// Create a new instance of the HMAC function, using a `secret_key` that may or may not be padded.
        fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
            Self::_new(secret_key)
        }

        /// Update the internal state with `data`.
        fn _update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
            self._state._update(data)
        }

        /// Finalize the MAC and put the final tag into `dest`.
        ///
        /// NOTE: `dest` may be less than the complete output size of the hash function
        /// (Self::HASH_FUNC_OUTSIZE). If that is the case, `dest.len()` bytes will be copied,
        /// but `dest` should NEVER be empty.
        fn _finalize(&mut self, dest: &mut [u8]) -> Result<(), UnknownCryptoError> {
            self._state._finalize(dest)
        }

        /// Reset the state.
        fn _reset(&mut self) {
            self._state._reset()
        }
    }

    #[cfg(test)]
    mod public {
        use super::*;

        #[test]
        #[cfg(feature = "safe_api")]
        fn test_debug_impl() {
            let secret_key = SecretKey::generate();
            let initial_state = HmacSha384::new(&secret_key);
            let debug = format!("{initial_state:?}");
            let expected = "HmacSha384 { _state: Hmac { working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: false } }";
            assert_eq!(debug, expected);
        }

        #[cfg(feature = "safe_api")]
        mod test_verify {
            use super::*;

            #[quickcheck]
            #[cfg(feature = "safe_api")]
            /// When using a different key, verify() should always yield an error.
            /// NOTE: Using different and same input data is tested with TestableStreamingContext.
            fn prop_verify_diff_key_false(data: Vec<u8>) -> bool {
                let sk = SecretKey::generate();
                let mut state = HmacSha384::new(&sk);
                state.update(&data[..]).unwrap();
                let tag = state.finalize().unwrap();
                let bad_sk = SecretKey::generate();

                HmacSha384::verify(&tag, &bad_sk, &data[..]).is_err()
            }
        }

        mod test_streaming_interface {
            use super::*;
            use crate::test_framework::incremental_interface::*;

            const KEY: [u8; 32] = [0u8; 32];

            impl TestableStreamingContext<Tag> for HmacSha384 {
                fn reset(&mut self) -> Result<(), UnknownCryptoError> {
                    self.reset();
                    Ok(())
                }

                fn update(&mut self, input: &[u8]) -> Result<(), UnknownCryptoError> {
                    self.update(input)
                }

                fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
                    self.finalize()
                }

                fn one_shot(input: &[u8]) -> Result<Tag, UnknownCryptoError> {
                    HmacSha384::hmac(&SecretKey::from_slice(&KEY).unwrap(), input)
                }

                fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> {
                    // This will only run verification tests on differing input. They do not
                    // include tests for different secret keys.
                    HmacSha384::verify(expected, &SecretKey::from_slice(&KEY).unwrap(), input)
                }

                fn compare_states(state_1: &HmacSha384, state_2: &HmacSha384) {
                    state_1._state.compare_state_to_other(&state_2._state);
                }
            }

            #[test]
            fn default_consistency_tests() {
                let initial_state = HmacSha384::new(&SecretKey::from_slice(&KEY).unwrap());

                let test_runner = StreamingContextConsistencyTester::<Tag, HmacSha384>::new(
                    initial_state,
                    sha384::SHA384_BLOCKSIZE,
                );
                test_runner.run_all_tests();
            }

            #[quickcheck]
            #[cfg(feature = "safe_api")]
            /// Related bug: https://github.com/orion-rs/orion/issues/46
            /// Test different streaming state usage patterns.
            fn prop_input_to_consistency(data: Vec<u8>) -> bool {
                let initial_state = HmacSha384::new(&SecretKey::from_slice(&KEY).unwrap());

                let test_runner = StreamingContextConsistencyTester::<Tag, HmacSha384>::new(
                    initial_state,
                    sha384::SHA384_BLOCKSIZE,
                );
                test_runner.run_all_tests_property(&data);
                true
            }
        }
    }
}

/// HMAC-SHA512 (Hash-based Message Authentication Code) as specified in the [RFC 2104](https://tools.ietf.org/html/rfc2104).
pub mod sha512 {
    use super::*;
    use crate::hazardous::hash::sha2::sha512::{self, Sha512};

    construct_hmac_key! {
        /// A type to represent the `SecretKey` that HMAC uses for authentication.
        ///
        /// # Note:
        /// `SecretKey` pads the secret key for use with HMAC to a length of 128, when initialized.
        ///
        /// Using `unprotected_as_bytes()` will return the secret key with padding.
        ///
        /// `len()` will return the length with padding (always 128).
        ///
        /// # Panics:
        /// A panic will occur if:
        /// - Failure to generate random bytes securely.
        (SecretKey, Sha512, sha512::SHA512_OUTSIZE, test_hmac_key, sha512::SHA512_BLOCKSIZE)
    }

    construct_tag! {
        /// A type to represent the `Tag` that HMAC returns.
        ///
        /// # Errors:
        /// An error will be returned if:
        /// - `slice` is not 64 bytes.
        (Tag, test_tag, sha512::SHA512_OUTSIZE, sha512::SHA512_OUTSIZE)
    }

    impl_from_trait!(Tag, sha512::SHA512_OUTSIZE);

    use super::Hmac;

    #[derive(Clone, Debug)]
    /// HMAC-SHA512 streaming state.
    pub struct HmacSha512 {
        _state: Hmac<Sha512, { sha512::SHA512_BLOCKSIZE }>,
    }

    impl HmacSha512 {
        fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
            Ok(Self {
                _state: Hmac::<Sha512, { sha512::SHA512_BLOCKSIZE }>::_new(secret_key)?,
            })
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Initialize `HmacSha512` struct with a given key.
        pub fn new(secret_key: &SecretKey) -> Self {
            // NOTE: `secret_key` has been pre-padded so .unwrap() is OK.
            Self::_new(secret_key.unprotected_as_bytes()).unwrap()
        }

        /// Reset to `new()` state.
        pub fn reset(&mut self) {
            self._state._reset()
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Update state with `data`. This can be called multiple times.
        pub fn update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
            self._state._update(data)
        }

        /// Return an HMAC-SHA512 tag.
        pub(crate) fn _finalize_internal(
            &mut self,
            dest: &mut [u8],
        ) -> Result<(), UnknownCryptoError> {
            self._state._finalize(dest)
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Return an HMAC-SHA512 tag.
        pub fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
            let mut dest = [0u8; sha512::SHA512_OUTSIZE];
            self._finalize_internal(&mut dest)?;

            Ok(Tag::from(dest))
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// One-shot function for generating an HMAC-SHA512 tag of `data`.
        pub fn hmac(secret_key: &SecretKey, data: &[u8]) -> Result<Tag, UnknownCryptoError> {
            let mut ctx = Self::new(secret_key);
            ctx.update(data)?;
            ctx.finalize()
        }

        #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
        /// Verify an HMAC-SHA512 tag in constant time.
        pub fn verify(
            expected: &Tag,
            secret_key: &SecretKey,
            data: &[u8],
        ) -> Result<(), UnknownCryptoError> {
            if &Self::hmac(secret_key, data)? == expected {
                Ok(())
            } else {
                Err(UnknownCryptoError)
            }
        }
    }

    impl HmacFunction for HmacSha512 {
        /// The output size of the internal hash function used.
        const HASH_FUNC_OUTSIZE: usize = sha512::SHA512_OUTSIZE;

        /// Create a new instance of the HMAC function, using a `secret_key` that may or may not be padded.
        fn _new(secret_key: &[u8]) -> Result<Self, UnknownCryptoError> {
            Self::_new(secret_key)
        }

        /// Update the internal state with `data`.
        fn _update(&mut self, data: &[u8]) -> Result<(), UnknownCryptoError> {
            self._state._update(data)
        }

        /// Finalize the MAC and put the final tag into `dest`.
        ///
        /// NOTE: `dest` may be less than the complete output size of the hash function
        /// (Self::HASH_FUNC_OUTSIZE). If that is the case, `dest.len()` bytes will be copied,
        /// but `dest` should NEVER be empty.
        fn _finalize(&mut self, dest: &mut [u8]) -> Result<(), UnknownCryptoError> {
            self._state._finalize(dest)
        }

        /// Reset the state.
        fn _reset(&mut self) {
            self._state._reset()
        }
    }

    #[cfg(test)]
    mod public {
        use super::*;

        #[test]
        #[cfg(feature = "safe_api")]
        fn test_debug_impl() {
            let secret_key = SecretKey::generate();
            let initial_state = HmacSha512::new(&secret_key);
            let debug = format!("{initial_state:?}");
            let expected = "HmacSha512 { _state: Hmac { working_hasher: [***OMITTED***], opad_hasher: [***OMITTED***], ipad_hasher: [***OMITTED***], is_finalized: false } }";
            assert_eq!(debug, expected);
        }

        #[cfg(feature = "safe_api")]
        mod test_verify {
            use super::*;

            #[quickcheck]
            #[cfg(feature = "safe_api")]
            /// When using a different key, verify() should always yield an error.
            /// NOTE: Using different and same input data is tested with TestableStreamingContext.
            fn prop_verify_diff_key_false(data: Vec<u8>) -> bool {
                let sk = SecretKey::generate();
                let mut state = HmacSha512::new(&sk);
                state.update(&data[..]).unwrap();
                let tag = state.finalize().unwrap();
                let bad_sk = SecretKey::generate();

                HmacSha512::verify(&tag, &bad_sk, &data[..]).is_err()
            }
        }

        mod test_streaming_interface {
            use super::*;
            use crate::test_framework::incremental_interface::*;

            const KEY: [u8; 32] = [0u8; 32];

            impl TestableStreamingContext<Tag> for HmacSha512 {
                fn reset(&mut self) -> Result<(), UnknownCryptoError> {
                    self.reset();
                    Ok(())
                }

                fn update(&mut self, input: &[u8]) -> Result<(), UnknownCryptoError> {
                    self.update(input)
                }

                fn finalize(&mut self) -> Result<Tag, UnknownCryptoError> {
                    self.finalize()
                }

                fn one_shot(input: &[u8]) -> Result<Tag, UnknownCryptoError> {
                    HmacSha512::hmac(&SecretKey::from_slice(&KEY).unwrap(), input)
                }

                fn verify_result(expected: &Tag, input: &[u8]) -> Result<(), UnknownCryptoError> {
                    // This will only run verification tests on differing input. They do not
                    // include tests for different secret keys.
                    HmacSha512::verify(expected, &SecretKey::from_slice(&KEY).unwrap(), input)
                }

                fn compare_states(state_1: &HmacSha512, state_2: &HmacSha512) {
                    state_1._state.compare_state_to_other(&state_2._state);
                }
            }

            #[test]
            fn default_consistency_tests() {
                let initial_state = HmacSha512::new(&SecretKey::from_slice(&KEY).unwrap());

                let test_runner = StreamingContextConsistencyTester::<Tag, HmacSha512>::new(
                    initial_state,
                    sha512::SHA512_BLOCKSIZE,
                );
                test_runner.run_all_tests();
            }

            #[quickcheck]
            #[cfg(feature = "safe_api")]
            /// Related bug: https://github.com/orion-rs/orion/issues/46
            /// Test different streaming state usage patterns.
            fn prop_input_to_consistency(data: Vec<u8>) -> bool {
                let initial_state = HmacSha512::new(&SecretKey::from_slice(&KEY).unwrap());

                let test_runner = StreamingContextConsistencyTester::<Tag, HmacSha512>::new(
                    initial_state,
                    sha512::SHA512_BLOCKSIZE,
                );
                test_runner.run_all_tests_property(&data);
                true
            }
        }
    }
}
