//
// Syd: rock-solid application kernel
// src/hash.rs: Utilities for hashing
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    hash::{BuildHasher, Hasher},
    io::{IoSlice, Read},
    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
};

use constant_time_eq::constant_time_eq;
use crc::{Crc, CRC_32_ISO_HDLC, CRC_64_ECMA_182};
use hex::{DisplayHex, FromHex};
use lexis::ToName;
use memchr::arch::all::is_equal;
use nix::{
    errno::Errno,
    fcntl::{splice, tee, OFlag, SpliceFFlags},
    sys::socket::{
        accept4, bind, send, sendmsg, setsockopt, socket, sockopt::AlgSetKey, AddressFamily,
        AlgAddr, ControlMessage, MsgFlags, SockFlag, SockType,
    },
    unistd::{lseek64, pipe2, read, write, Whence},
};
use secure_string::SecureBytes;
use sha1::Sha1;
use sha3::{Digest, Sha3_256, Sha3_384, Sha3_512};

use crate::{
    config::*,
    err::SydResult,
    fs::{
        create_memfd_raw, fillrandom, mkstempat, retry_on_eintr, set_append, set_nonblock,
        MFD_ALLOW_SEALING, MFD_NOEXEC_SEAL,
    },
    path::XPath,
};

/// Defines hash functions supported by Syd.
#[derive(Debug, Clone, Copy)]
pub enum HashAlgorithm {
    /// Crc32
    Crc32,
    /// Crc64
    Crc64,
    /// Md5
    Md5,
    /// SHA-1
    Sha1,
    /// SHA3-256
    Sha256,
    /// SHA3-384
    Sha384,
    /// SHA3-512
    Sha512,
}

impl TryFrom<usize> for HashAlgorithm {
    type Error = Errno;

    fn try_from(len: usize) -> Result<Self, Self::Error> {
        match len {
            4 => Ok(HashAlgorithm::Crc32),
            8 => Ok(HashAlgorithm::Crc64),
            16 => Ok(HashAlgorithm::Md5),
            20 => Ok(HashAlgorithm::Sha1),
            32 => Ok(HashAlgorithm::Sha256),
            48 => Ok(HashAlgorithm::Sha384),
            64 => Ok(HashAlgorithm::Sha512),
            _ => Err(Errno::EINVAL),
        }
    }
}

/// AES-CTR encryption key size
pub const KEY_SIZE: usize = 32;

/// AES-CTR IV size
pub const IV_SIZE: usize = 16;

/// AES-CTR block size
pub const BLOCK_SIZE: usize = 16;

/// SHA256 digest size
pub const SHA256_DIGEST_SIZE: usize = 32;

/// SHA256 block size
pub const SHA256_BLOCK_SIZE: usize = 64;

/// HMAC tag size
pub const HMAC_TAG_SIZE: usize = SHA256_DIGEST_SIZE;

/// nix does not define MSG_MORE yet.
pub(crate) const MSG_MORE: MsgFlags = MsgFlags::from_bits_retain(0x8000);

/// Maximum bytes sendfile(2) can transfer at a time.
pub const SENDFILE_MAX: usize = 0x7ffff000;

/// Key holds the AES encryption key.
///
/// This struct ensures that the key doesn't get swapped out and is
/// securely zeroized when it is dropped.
#[derive(Debug)]
pub struct Key(SecureBytes);

impl Key {
    /// Creates a new Key with the given key data.
    pub fn new(key: [u8; KEY_SIZE]) -> Self {
        Self(SecureBytes::from(key))
    }

    /// Creates a random Key using the OS random number generator.
    pub fn random() -> Result<Self, Errno> {
        let mut bytes = SecureBytes::new(vec![0; KEY_SIZE]);
        fillrandom(bytes.unsecure_mut())?;
        Ok(Self(bytes))
    }

    /// Creates an IV from a hex-encoded string.
    pub fn from_hex(hex: &[u8]) -> Result<Self, Errno> {
        let key = <[u8; KEY_SIZE]>::from_hex(std::str::from_utf8(hex).or(Err(Errno::EINVAL))?)
            .or(Err(Errno::EINVAL))?;
        Ok(Self::new(key))
    }

    /// Returns a hex-encoded string of the KEY.
    pub fn as_hex(&self) -> String {
        self.0.unsecure().to_lower_hex_string()
    }

    /// Check if the KEY is all zeros.
    pub fn is_zero(&self) -> bool {
        self.as_ref().iter().all(|&byte| byte == 0)
    }

    /// Derive a key from this key using HKDF and the given salt and info string.
    pub fn derive(&self, salt: Option<&[u8]>, info: &[u8]) -> Self {
        let hk = hkdf::Hkdf::<Sha3_256>::new(salt, self.as_ref());
        let mut key = Self::new([0u8; KEY_SIZE]);
        #[allow(clippy::disallowed_methods)]
        hk.expand(info, key.as_mut())
            .expect("BUG: invalid HKDF output!");
        key
    }
}

impl AsRef<[u8]> for Key {
    fn as_ref(&self) -> &[u8] {
        self.0.unsecure()
    }
}

impl AsMut<[u8]> for Key {
    fn as_mut(&mut self) -> &mut [u8] {
        self.0.unsecure_mut()
    }
}

/// Key holds the AES IV
///
/// This struct ensures that the IV doesn't get swapped out and is
/// securely zeroized when it is dropped.
#[derive(Debug)]
pub struct IV(SecureBytes);

impl IV {
    /// Creates a new IV with the given key data.
    pub fn new(iv: [u8; IV_SIZE]) -> Self {
        Self(SecureBytes::from(iv))
    }

    /// Creates a random IV using the OS random number generator.
    ///
    /// This call never fails. If getrandom(2) returns error,
    /// random bytes from AT_RANDOM is used instead.
    pub fn random() -> Self {
        let atrnd = get_at_random();
        let mut bytes = SecureBytes::new(atrnd[..IV_SIZE].to_vec());
        let _ = fillrandom(bytes.unsecure_mut());
        Self(bytes)
    }

    /// Creates an IV from a hex-encoded string.
    pub fn from_hex(hex: &[u8]) -> Result<Self, Errno> {
        let iv = <[u8; IV_SIZE]>::from_hex(std::str::from_utf8(hex).or(Err(Errno::EINVAL))?)
            .or(Err(Errno::EINVAL))?;
        Ok(Self::new(iv))
    }

    /// Returns a hex-encoded string of the IV.
    pub fn as_hex(&self) -> String {
        self.0.unsecure().to_lower_hex_string()
    }

    /// Check if the IV is all zeros.
    pub fn is_zero(&self) -> bool {
        self.as_ref().iter().all(|&byte| byte == 0)
    }

    /// Add the given counter to the IV in AES-CTR mode.
    ///
    /// In AES-CTR (Counter) mode, encryption and decryption are done by
    /// generating a keystream using the AES block cipher and a counter
    /// value. The IV (Initialization Vector) is combined with a counter
    /// to generate unique input blocks for encryption. This function
    /// updates the IV by adding a given counter value, effectively
    /// updating the nonce for the next encryption block. The counter is
    /// incremented in a block-aligned manner.
    ///
    /// # Parameters
    /// - `ctr`: The counter value to be added to the IV. This counter
    ///   is divided by the block size to ensure correct block-aligned
    ///   increments.
    #[allow(clippy::arithmetic_side_effects)]
    pub fn add_counter(&mut self, ctr: u64) {
        // Return if counter is zero: No need to update IV.
        if ctr == 0 {
            return;
        }

        // Convert the counter to a u128 and divide by the block size.
        // This aligns the counter to the size of an AES block (16 bytes).
        let mut ctr = ctr / BLOCK_SIZE as u64;

        // Access the IV bytes for modification.
        let val = self.0.unsecure_mut();

        // Process each byte of the IV from least significant to most
        // significant. This is because we are effectively treating the
        // IV as a large integer counter.
        for i in (0..IV_SIZE).rev() {
            // Add the least significant byte of the counter to the
            // current byte of the IV. `overflowing_add` handles byte
            // overflow, which is equivalent to a carry in multi-byte
            // addition.
            let (new_byte, overflow) = val[i].overflowing_add((ctr & 0xFF) as u8);

            // Update the IV byte with the new value.
            val[i] = new_byte;

            // Shift the counter right by 8 bits to process the next
            // byte. If there was an overflow, carry the overflow to
            // the next byte.
            ctr = (ctr >> 8) + if overflow { 1 } else { 0 };

            // Return if counter is zero and there is no overflow.
            if ctr == 0 {
                break;
            }
        }
    }
}

impl Clone for IV {
    fn clone(&self) -> Self {
        IV(SecureBytes::from(self.0.unsecure()))
    }
}

impl AsRef<[u8]> for IV {
    fn as_ref(&self) -> &[u8] {
        self.0.unsecure()
    }
}

impl AsMut<[u8]> for IV {
    fn as_mut(&mut self) -> &mut [u8] {
        self.0.unsecure_mut()
    }
}

/// Represents crypt secrets.
///
/// `Key` is the master key in secure memory pre-startup.
/// `Alg` are two sockets:
///   0: AF_ALG skcipher aes(ctr)
///   1: AF_ALG hash hmac(sha256)
///
/// `Key` turns into `Alg` and is wiped from memory at startup.
pub enum Secret {
    /// Encryption & Authentication sockets
    Alg(OwnedFd, OwnedFd),
    /// Uninitialized master key
    Key(Key),
}

impl Secret {
    /// Generate a new secret from a master key.
    pub fn new(key: Key) -> Self {
        Self::Key(key)
    }

    /// Turns a `Key` into an `Alg`.
    pub fn init(
        &mut self,
        salt: Option<&[u8]>,
        info_enc: &[u8],
        info_tag: &[u8],
    ) -> Result<(), Errno> {
        let key = if let Secret::Key(key) = self {
            key
        } else {
            // Nothing to do
            return Ok(());
        };
        let enc_fd = {
            let enc_key = key.derive(salt, info_enc);
            aes_ctr_setup(&enc_key)
        }?;
        let tag_fd = {
            let tag_key = key.derive(salt, info_tag);
            hmac_sha256_setup(&tag_key.0)
        }?;
        *self = Self::Alg(enc_fd, tag_fd);
        Ok(())
    }
}

/// Calculate sha{1,256,512} of the given buffered reader.
/// Returns a byte array.
pub fn hash<R: Read>(mut reader: R, func: HashAlgorithm) -> SydResult<Vec<u8>> {
    // Enum for incremental hashing.
    enum HashState<'a> {
        Crc32(crc::Digest<'a, u32>),
        Crc64(crc::Digest<'a, u64>),
        Md5(md5::Context),
        Sha1(Sha1),
        Sha3_256(Sha3_256),
        Sha3_384(Sha3_384),
        Sha3_512(Sha3_512),
    }

    // We use CRC32 as defined in IEEE 802.3.
    let crc32 = Crc::<u32>::new(&CRC_32_ISO_HDLC);
    // We use CRC64 as defined in ECMA-182.
    let crc64 = Crc::<u64>::new(&CRC_64_ECMA_182);

    let mut hasher_state = match func {
        HashAlgorithm::Crc32 => HashState::Crc32(crc32.digest()),
        HashAlgorithm::Crc64 => HashState::Crc64(crc64.digest()),
        HashAlgorithm::Md5 => HashState::Md5(md5::Context::new()),
        HashAlgorithm::Sha1 => HashState::Sha1(Sha1::new()),
        HashAlgorithm::Sha256 => HashState::Sha3_256(Sha3_256::new()),
        HashAlgorithm::Sha384 => HashState::Sha3_384(Sha3_384::new()),
        HashAlgorithm::Sha512 => HashState::Sha3_512(Sha3_512::new()),
    };

    let mut buffer = [0u8; 0x10000];
    loop {
        let read_count = match reader.read(&mut buffer) {
            Ok(0) => break,
            Ok(n) => n,
            Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
            Err(e) => return Err(e.into()),
        };
        match &mut hasher_state {
            HashState::Crc32(d) => d.update(&buffer[..read_count]),
            HashState::Crc64(d) => d.update(&buffer[..read_count]),
            HashState::Md5(c) => c.consume(&buffer[..read_count]),
            HashState::Sha1(s) => s.update(&buffer[..read_count]),
            HashState::Sha3_256(s) => s.update(&buffer[..read_count]),
            HashState::Sha3_384(s) => s.update(&buffer[..read_count]),
            HashState::Sha3_512(s) => s.update(&buffer[..read_count]),
        }
    }

    let digest = match hasher_state {
        HashState::Crc32(d) => d.finalize().to_be_bytes().to_vec(),
        HashState::Crc64(d) => d.finalize().to_be_bytes().to_vec(),
        HashState::Md5(c) => {
            let dg = c.compute();
            dg.0.to_vec()
        }
        HashState::Sha1(s) => s.finalize().to_vec(),
        HashState::Sha3_256(s) => s.finalize().to_vec(),
        HashState::Sha3_384(s) => s.finalize().to_vec(),
        HashState::Sha3_512(s) => s.finalize().to_vec(),
    };

    Ok(digest)
}

/// Sets up the HMAC-SHA256 authentication using the Kernel crypto API.
///
/// # Arguments
///
/// * `key` - A reference to the master key.
///
/// # Returns
///
/// * `Result<OwnedFd, Errno>` - The file descriptor for the socket on success, or an error.
pub fn hmac_sha256_setup(key: &SecureBytes) -> Result<OwnedFd, Errno> {
    // SAFETY: The key must be hashed with sha256 if its larger than the
    // block size, see Test Case 6 of RFC4231. We simply validate here,
    // and expect the caller to handle it.
    if key.unsecure().len() > SHA256_BLOCK_SIZE {
        return Err(Errno::EINVAL);
    }

    // Create the socket for the AF_ALG interface.
    let sock = socket(
        AddressFamily::Alg,
        SockType::SeqPacket,
        SockFlag::empty(),
        None,
    )?;

    // Set up the sockaddr_alg structure.
    let addr = AlgAddr::new("hash", "hmac(sha256)");

    // Bind the socket.
    bind(sock.as_raw_fd(), &addr)?;

    // Set the encryption key.
    setsockopt(&sock, AlgSetKey::default(), &key.unsecure())?;

    Ok(sock)
}

/// Initializes the HMAC-SHA256 authentication using an existing socket.
///
/// # Arguments
///
/// * `fd`       - The file descriptor of the existing socket.
/// * `nonblock` - True if socket should be set non-blocking.
///
/// # Returns
///
/// * `Result<OwnedFd, Errno>` - The file descriptor for the new socket on success, or an error.
pub fn hmac_sha256_init<F: AsRawFd>(fd: &F, nonblock: bool) -> Result<OwnedFd, Errno> {
    let mut flags = SockFlag::SOCK_CLOEXEC;
    if nonblock {
        flags |= SockFlag::SOCK_NONBLOCK;
    }

    let fd = retry_on_eintr(|| accept4(fd.as_raw_fd(), flags))?;

    // SAFETY: accept4 returns a valid FD.
    Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}

/// Feeds a chunk of data to the HMAC-SHA256 socket.
pub fn hmac_sha256_feed<S: AsRawFd>(sock: &S, chunk: &[u8], more: bool) -> Result<usize, Errno> {
    // Prepare the IoSlice for the data
    let iov = [IoSlice::new(chunk)];

    // Determine the flags for the sendmsg operation.
    let flags = if more { MSG_MORE } else { MsgFlags::empty() };

    // Send the message with the IV and data
    retry_on_eintr(|| sendmsg::<AlgAddr>(sock.as_raw_fd(), &iov, &[], flags, None))
}

/// Finishes the HMAC-SHA256 authentication and reads authentication tag.
pub fn hmac_sha256_fini<S: AsRawFd>(sock: &S) -> Result<SecureBytes, Errno> {
    let mut data = SecureBytes::new(vec![0u8; SHA256_DIGEST_SIZE]);
    let buf = data.unsecure_mut();

    let mut nread = 0;
    while nread < SHA256_DIGEST_SIZE {
        #[allow(clippy::arithmetic_side_effects)]
        match read(sock.as_raw_fd(), &mut buf[nread..]) {
            Ok(0) => return Err(Errno::EINVAL),
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(errno) => return Err(errno),
        }
    }

    Ok(data)
}

/// Sets up the AES-CTR encryption/decryption using the Kernel crypto API.
pub fn aes_ctr_setup(key: &Key) -> Result<OwnedFd, Errno> {
    // Create the socket for the AF_ALG interface
    let sock = socket(
        AddressFamily::Alg,
        SockType::SeqPacket,
        SockFlag::empty(),
        None,
    )?;

    // Set up the sockaddr_alg structure
    let addr = AlgAddr::new("skcipher", "ctr(aes)");

    // Bind the socket
    bind(sock.as_raw_fd(), &addr)?;

    // Set the encryption key.
    setsockopt(&sock, AlgSetKey::default(), &key.as_ref())?;

    Ok(sock)
}

/// Initializes the AES-CTR encryption/decryption using an existing socket.
///
/// # Arguments
///
/// * `fd`       - The file descriptor of the existing socket.
/// * `nonblock` - True if socket should be set non-blocking.
///
/// # Returns
///
/// * `Result<OwnedFd, Errno>` - The file descriptor for the new socket on success, or an error.
pub fn aes_ctr_init<F: AsRawFd>(fd: &F, nonblock: bool) -> Result<OwnedFd, Errno> {
    let mut flags = SockFlag::SOCK_CLOEXEC;
    if nonblock {
        flags |= SockFlag::SOCK_NONBLOCK;
    }

    let fd = retry_on_eintr(|| accept4(fd.as_raw_fd(), flags))?;

    // SAFETY: accept4 returns a valid FD.
    Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}

/// Encrypts a chunk of data using the initialized AES-CTR socket.
pub fn aes_ctr_enc<S: AsRawFd>(
    sock: &S,
    chunk: &[u8],
    iv: Option<&IV>,
    more: bool,
) -> Result<usize, Errno> {
    // Prepare the IoSlice for the data
    let iov = [IoSlice::new(chunk)];

    // Prepare the control message for the IV.
    let mut cmsgs = vec![ControlMessage::AlgSetOp(&nix::libc::ALG_OP_ENCRYPT)];
    if let Some(iv) = iv {
        cmsgs.push(ControlMessage::AlgSetIv(iv.as_ref()));
    }

    // Determine the flags for the sendmsg operation.
    let flags = if more { MSG_MORE } else { MsgFlags::empty() };

    // Send the message with the IV and data
    retry_on_eintr(|| sendmsg::<AlgAddr>(sock.as_raw_fd(), &iov, cmsgs.as_slice(), flags, None))
}

/// Decrypts a chunk of data using the initialized AES-CTR socket.
pub fn aes_ctr_dec<S: AsRawFd>(
    sock: &S,
    chunk: &[u8],
    iv: Option<&IV>,
    more: bool,
) -> Result<usize, Errno> {
    // Prepare the IoSlice for the data
    let iov = [IoSlice::new(chunk)];

    // Prepare the control message for IV.
    let mut cmsgs = vec![ControlMessage::AlgSetOp(&nix::libc::ALG_OP_DECRYPT)];
    if let Some(iv) = iv {
        cmsgs.push(ControlMessage::AlgSetIv(iv.as_ref()));
    }

    // Determine the flags for the sendmsg operation
    let flags = if more { MSG_MORE } else { MsgFlags::empty() };

    // Send the message with the IV and data
    retry_on_eintr(|| sendmsg::<AlgAddr>(sock.as_raw_fd(), &iov, cmsgs.as_slice(), flags, None))
}

/// Finishes the AES-CTR {en,de}cryption and reads the {de,en}crypted data.
pub fn aes_ctr_fini<S: AsRawFd>(sock: &S, size: usize) -> Result<SecureBytes, Errno> {
    let mut data = SecureBytes::new(vec![0u8; size]);
    let buf = data.unsecure_mut();

    let mut nread = 0;
    while nread < size {
        #[allow(clippy::arithmetic_side_effects)]
        match read(sock.as_raw_fd(), &mut buf[nread..]) {
            Ok(0) => return Err(Errno::EINVAL),
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(errno) => return Err(errno),
        }
    }

    Ok(data)
}

/// Decrypt the given file into a temporary fd with zero-copy.
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::type_complexity)]
pub fn aes_ctr_tmp<F: AsFd>(
    setup_fds: (RawFd, RawFd),
    fd: &F,
    flags: OFlag,
    tmp: Option<RawFd>,
    restrict_memfd: bool,
) -> Result<Option<(RawFd, IV)>, Errno> {
    let (aes_fd, mac_fd) = setup_fds;

    // Check if this is a Syd encrypted file.
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_sign_loss)]
    let size = lseek64(fd.as_fd().as_raw_fd(), 0, Whence::SeekEnd)? as usize;
    #[allow(clippy::arithmetic_side_effects)]
    let iv_and_tag = if size == 0 {
        // Encrypting new file.
        None
    } else if size <= CRYPT_MAGIC.len() + HMAC_TAG_SIZE + IV_SIZE {
        // SAFETY: Not a Syd file, do nothing.
        return Ok(None);
    } else {
        // Read and verify file magic.
        lseek64(fd.as_fd().as_raw_fd(), 0, Whence::SeekSet)?;
        let mut magic = [0u8; CRYPT_MAGIC.len()];
        let mut nread = 0;
        while nread < magic.len() {
            #[allow(clippy::arithmetic_side_effects)]
            match read(fd.as_fd().as_raw_fd(), &mut magic[nread..]) {
                Ok(0) => {
                    // SAFETY: Not a Syd file, do nothing.
                    return Ok(None);
                }
                Ok(n) => nread += n,
                Err(Errno::EINTR) => continue,
                Err(errno) => return Err(errno),
            }
        }
        if !is_equal(&magic, CRYPT_MAGIC) {
            // SAFETY: Not a Syd file, do nothing.
            return Ok(None);
        }

        // Read HMAC tag.
        // SAFETY: We don't swap HMAC out!
        let mut hmac_tag = SecureBytes::from([0u8; HMAC_TAG_SIZE]);
        let buf = hmac_tag.unsecure_mut();
        let mut nread = 0;
        while nread < buf.len() {
            #[allow(clippy::arithmetic_side_effects)]
            match read(fd.as_fd().as_raw_fd(), &mut buf[nread..]) {
                Ok(0) => {
                    // SAFETY: Corrupt HMAC tag, return error.
                    return Err(Errno::EBADMSG);
                }
                Ok(n) => nread += n,
                Err(Errno::EINTR) => continue,
                Err(errno) => return Err(errno),
            }
        }

        // Read IV.
        // SAFETY: We don't swap IV out!
        let mut iv = IV::new([0u8; IV_SIZE]);
        let buf = iv.as_mut();
        let mut nread = 0;
        while nread < buf.len() {
            #[allow(clippy::arithmetic_side_effects)]
            match read(fd.as_fd().as_raw_fd(), &mut buf[nread..]) {
                Ok(0) => {
                    // SAFETY: Corrupt IV, return error.
                    return Err(Errno::EBADMSG);
                }
                Ok(n) => nread += n,
                Err(Errno::EINTR) => continue,
                Err(errno) => return Err(errno),
            }
        }

        Some((iv, hmac_tag))
    };

    let dst_fd = if let Some(tmp) = tmp {
        // SAFETY: `tmp' is alive for the duration of the Syd sandbox.
        let tmp = unsafe { BorrowedFd::borrow_raw(tmp) };
        mkstempat(&tmp, XPath::from_bytes(b""))
    } else {
        // Note, MFD_ALLOW_SEALING is implied for MFD_NOEXEC_SEAL.
        create_memfd_raw(
            b"syd-aes\0",
            if restrict_memfd {
                MFD_NOEXEC_SEAL
            } else {
                MFD_ALLOW_SEALING
            },
        )
    }
    .map(|fd| {
        // SAFETY: syscall returned success, we have a valid FD.
        unsafe { BorrowedFd::borrow_raw(fd) }
    })?;

    let iv = if let Some((iv, hmac_tag)) = iv_and_tag {
        // Initialize HMAC socket and feed magic header and IV.
        let sock_mac = hmac_sha256_init(&mac_fd, false)?;
        hmac_sha256_feed(&sock_mac, CRYPT_MAGIC, true)?;
        hmac_sha256_feed(&sock_mac, iv.as_ref(), true)?;

        // Initialize decryption socket and set IV.
        let sock_dec = aes_ctr_init(&aes_fd, false)?;
        aes_ctr_dec(&sock_dec, &[], Some(&iv), true)?;

        // SAFETY: Prepare pipes for zero-copy.
        // We do not read plaintext into Syd's memory!
        let (pipe_rd_dec, pipe_wr_dec) = pipe2(OFlag::O_CLOEXEC)?;
        let (pipe_rd_mac, pipe_wr_mac) = pipe2(OFlag::O_CLOEXEC)?;

        // Feed encrypted data to the kernel.
        // File offset is right past the IV here.
        #[allow(clippy::arithmetic_side_effects)]
        let mut datasz = size - CRYPT_MAGIC.len() - HMAC_TAG_SIZE - IV_SIZE;
        let mut nflush = 0;
        while datasz > 0 {
            let len = datasz.min(PIPE_BUF_ALG);

            let n = retry_on_eintr(|| {
                splice(
                    fd,
                    None,
                    &pipe_wr_dec,
                    None,
                    len,
                    SpliceFFlags::SPLICE_F_MORE,
                )
            })?;
            if n == 0 {
                break;
            }

            // Duplicate data from pipe_rd_dec to pipe_wr_mac using tee(2).
            let mut ntee = n;
            #[allow(clippy::arithmetic_side_effects)]
            while ntee > 0 {
                let n_tee = retry_on_eintr(|| {
                    tee(&pipe_rd_dec, &pipe_wr_mac, ntee, SpliceFFlags::empty())
                })?;
                if n_tee == 0 {
                    return Err(Errno::EBADMSG);
                }
                ntee -= n_tee;
            }

            // Feed data from pipe_rd_dec into AES decryption socket.
            let mut ncopy = n;
            #[allow(clippy::arithmetic_side_effects)]
            while ncopy > 0 {
                let n = retry_on_eintr(|| {
                    splice(
                        &pipe_rd_dec,
                        None,
                        &sock_dec,
                        None,
                        ncopy,
                        SpliceFFlags::SPLICE_F_MORE,
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }
                ncopy -= n;
                datasz -= n;
                nflush += n;
            }

            // Feed duplicated data from pipe_rd_mac into HMAC socket.
            let mut ncopy = n;
            #[allow(clippy::arithmetic_side_effects)]
            while ncopy > 0 {
                let n = retry_on_eintr(|| {
                    splice(
                        &pipe_rd_mac,
                        None,
                        &sock_mac,
                        None,
                        ncopy,
                        SpliceFFlags::SPLICE_F_MORE,
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }
                ncopy -= n;
            }

            #[allow(clippy::arithmetic_side_effects)]
            while nflush > BLOCK_SIZE {
                let len = nflush - (nflush % BLOCK_SIZE);
                let n = retry_on_eintr(|| {
                    splice(
                        &sock_dec,
                        None,
                        &pipe_wr_dec,
                        None,
                        len,
                        SpliceFFlags::empty(),
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }

                let mut ncopy = n;
                while ncopy > 0 {
                    let n = retry_on_eintr(|| {
                        splice(
                            &pipe_rd_dec,
                            None,
                            dst_fd,
                            None,
                            ncopy,
                            SpliceFFlags::empty(),
                        )
                    })?;
                    if n == 0 {
                        return Err(Errno::EBADMSG);
                    }
                    ncopy -= n;
                    nflush -= n;
                }
            }
        }

        // Finalize decryption with `false`.
        aes_ctr_dec(&sock_dec, &[], None, false)?;

        // Flush the final batch.
        while nflush > 0 {
            let len = nflush.min(PIPE_BUF_ALG);

            let n = retry_on_eintr(|| {
                splice(
                    &sock_dec,
                    None,
                    &pipe_wr_dec,
                    None,
                    len,
                    SpliceFFlags::empty(),
                )
            })?;
            if n == 0 {
                return Err(Errno::EBADMSG);
            }

            let mut ncopy = n;
            #[allow(clippy::arithmetic_side_effects)]
            while ncopy > 0 {
                let n = retry_on_eintr(|| {
                    splice(
                        &pipe_rd_dec,
                        None,
                        dst_fd,
                        None,
                        ncopy,
                        SpliceFFlags::empty(),
                    )
                })?;
                if n == 0 {
                    return Err(Errno::EBADMSG);
                }
                ncopy -= n;
                nflush -= n;
            }
        }

        // Finalize HMAC computation and retrieve the computed tag.
        let computed_hmac = hmac_sha256_fini(&sock_mac)?;

        // Compare computed HMAC with the HMAC tag read from the file.
        // SAFETY: Compare in constant time!
        if !constant_time_eq(computed_hmac.unsecure(), hmac_tag.unsecure()) {
            // HMAC verification failed.
            return Err(Errno::EBADMSG);
        }

        iv
    } else {
        IV::random()
    };

    // Make the file append only or seek to the beginning.
    if flags.contains(OFlag::O_APPEND) {
        set_append(&dst_fd, true)?
    } else if size > 0 {
        lseek64(dst_fd.as_raw_fd(), 0, Whence::SeekSet)?;
    }

    // Set non-blocking as necessary.
    if flags.contains(OFlag::O_NONBLOCK) || flags.contains(OFlag::O_NDELAY) {
        set_nonblock(&dst_fd, true)?;
    }

    Ok(Some((dst_fd.as_raw_fd(), iv)))
}

/// Feed data into the AF_ALG socket from the given file descriptor.
pub fn aes_ctr_feed<S: AsFd, F: AsFd>(sock: &S, fd: &F, buf: &mut [u8]) -> Result<usize, Errno> {
    // Read from the file descriptor.
    let mut nread = 0;
    while nread < buf.len() {
        #[allow(clippy::arithmetic_side_effects)]
        match read(fd.as_fd().as_raw_fd(), &mut buf[nread..]) {
            Ok(0) => break, // EOF
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(errno) => return Err(errno),
        }
    }

    // Write output data to the socket.
    let mut nwrite = 0;
    while nwrite < nread {
        #[allow(clippy::arithmetic_side_effects)]
        match send(sock.as_fd().as_raw_fd(), &buf[nwrite..nread], MSG_MORE) {
            Ok(0) => return Err(Errno::EINVAL),
            Ok(n) => nwrite += n,
            Err(Errno::EINTR) => continue,
            Err(errno) => return Err(errno),
        }
    }

    Ok(nwrite)
}

/// Flush data in the AF_ALG socket into the given file descriptor.
pub fn aes_ctr_flush<S: AsFd, F: AsFd>(
    sock: &S,
    fd: &F,
    buf: &mut [u8],
    size: usize,
) -> Result<usize, Errno> {
    debug_assert!(buf.len() >= size);

    // Read from the socket.
    let mut nread = 0;
    while nread < size {
        #[allow(clippy::arithmetic_side_effects)]
        match read(sock.as_fd().as_raw_fd(), &mut buf[nread..size]) {
            Ok(0) => return Err(Errno::EINVAL),
            Ok(n) => nread += n,
            Err(Errno::EINTR) => continue,
            Err(errno) => return Err(errno),
        }
    }

    // Write output data to the file descriptor.
    let mut nwrite = 0;
    while nwrite < nread {
        #[allow(clippy::arithmetic_side_effects)]
        match write(fd, &buf[nwrite..nread]) {
            Ok(0) => return Err(Errno::EINVAL),
            Ok(n) => nwrite += n,
            Err(Errno::EINTR) => continue,
            Err(errno) => return Err(errno),
        }
    }

    Ok(nwrite)
}

/// Avoid duplicate hashing while using `HashSet` with u64 keys.
pub struct NoHasher {
    value: u64,
}

impl Hasher for NoHasher {
    fn write(&mut self, _bytes: &[u8]) {
        unreachable!("NoHasher should only be used for u64 keys");
    }

    fn write_u64(&mut self, i: u64) {
        self.value = i;
    }

    fn finish(&self) -> u64 {
        self.value
    }
}

/// A builder for creating instances of `NoHasher`.
#[derive(Clone)]
pub struct NoHasherBuilder;

impl Default for NoHasherBuilder {
    fn default() -> Self {
        Self
    }
}

impl BuildHasher for NoHasherBuilder {
    type Hasher = NoHasher;

    fn build_hasher(&self) -> Self::Hasher {
        NoHasher { value: 0 }
    }
}

// A `HashSet` with no hashers.
// pub type NoHashSet = HashSet<u64, NoHasherBuilder>;

/// Returns a reference to the AT_RANDOM buffer, which is 16 bytes long.
pub fn get_at_random() -> &'static [u8; 16] {
    // SAFETY: In libc we trust.
    unsafe {
        let ptr = nix::libc::getauxval(nix::libc::AT_RANDOM) as *const u8;
        assert!(!ptr.is_null(), "AT_RANDOM not found");
        &*(ptr as *const [u8; 16])
    }
}

/// Returns a pair of u64s derived from the AT_RANDOM buffer.
pub fn get_at_random_u64() -> (u64, u64) {
    let rnd = get_at_random();
    #[allow(clippy::disallowed_methods)]
    (
        u64::from_ne_bytes(rnd[..8].try_into().unwrap()),
        u64::from_ne_bytes(rnd[8..].try_into().unwrap()),
    )
}

/// Returns AT_RANDOM bytes in hexadecimal form.
pub fn get_at_random_hex(upper: bool) -> String {
    let rnd = get_at_random();
    if upper {
        rnd.to_upper_hex_string()
    } else {
        rnd.to_lower_hex_string()
    }
}

/// Returns a name generated from AT_RANDOM bytes.
pub fn get_at_random_name(idx: usize) -> String {
    assert!(idx == 0 || idx == 1, "BUG: invalid AT_RANDOM index!");
    let (rnd0, rnd1) = get_at_random_u64();
    match idx {
        0 => rnd0.to_name(),
        1 => rnd1.to_name(),
        _ => unreachable!("BUG: invalid AT_RANDOM index"),
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use hex::DisplayHex;
    use nix::{fcntl::open, sys::stat::Mode};

    use super::*;
    use crate::fs::create_memfd;

    struct HashTestCase(&'static [u8], &'static str, HashAlgorithm);
    struct HmacTestCase(&'static [u8], &'static [u8], &'static str);

    // Source:
    // - https://www.di-mgt.com.au/sha_testvectors.html
    // - https://www.febooti.com/products/filetweak/members/hash-and-crc/test-vectors/
    // MD5 test vectors were calculated with python-3.11.8's hashlib.md5
    const HASH_TEST_CASES: &[HashTestCase] = &[
    HashTestCase(
        b"The quick brown fox jumps over the lazy dog",
        "414FA339",
        HashAlgorithm::Crc32,
    ),
    HashTestCase(
        b"",
        "00000000",
        HashAlgorithm::Crc32,
    ),
    HashTestCase(
        b"",
        "0000000000000000",
        HashAlgorithm::Crc64,
    ),
    HashTestCase(
        b"",
        "D41D8CD98F00B204E9800998ECF8427E",
        HashAlgorithm::Md5,
    ),
    HashTestCase(
        b"",
        "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
        HashAlgorithm::Sha1,
    ),
    HashTestCase(
        b"",
        "A7FFC6F8BF1ED76651C14756A061D662F580FF4DE43B49FA82D80A4B80F8434A",
        HashAlgorithm::Sha256,
    ),
    HashTestCase(
        b"",
        "0C63A75B845E4F7D01107D852E4C2485C51A50AAAA94FC61995E71BBEE983A2AC3713831264ADB47FB6BD1E058D5F004",
        HashAlgorithm::Sha384,
    ),
    HashTestCase(
        b"",
        "A69F73CCA23A9AC5C8B567DC185A756E97C982164FE25859E0D1DCC1475C80A615B2123AF1F5F94C11E3E9402C3AC558F500199D95B6D3E301758586281DCD26",
        HashAlgorithm::Sha512,
    ),
    HashTestCase(
        b"abc",
        "900150983CD24FB0D6963F7D28E17F72",
        HashAlgorithm::Md5,
    ),
    HashTestCase(
        b"abc",
        "A9993E364706816ABA3E25717850C26C9CD0D89D",
        HashAlgorithm::Sha1,
    ),
    HashTestCase(
        b"abc",
        "3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532",
        HashAlgorithm::Sha256,
    ),
    HashTestCase(
        b"abc",
        "EC01498288516FC926459F58E2C6AD8DF9B473CB0FC08C2596DA7CF0E49BE4B298D88CEA927AC7F539F1EDF228376D25",
        HashAlgorithm::Sha384,
    ),
    HashTestCase(
        b"abc",
        "B751850B1A57168A5693CD924B6B096E08F621827444F70D884F5D0240D2712E10E116E9192AF3C91A7EC57647E3934057340B4CF408D5A56592F8274EEC53F0",
        HashAlgorithm::Sha512
    ),
    HashTestCase(
        b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
        "8215EF0796A20BCAAAE116D3876C664A",
        HashAlgorithm::Md5,
    ),
    HashTestCase(
        b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
        "84983E441C3BD26EBAAE4AA1F95129E5E54670F1",
        HashAlgorithm::Sha1,
    ),
    HashTestCase(
        b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
        "41C0DBA2A9D6240849100376A8235E2C82E1B9998A999E21DB32DD97496D3376",
        HashAlgorithm::Sha256,
    ),
    HashTestCase(
        b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
        "991C665755EB3A4B6BBDFB75C78A492E8C56A22C5C4D7E429BFDBC32B9D4AD5AA04A1F076E62FEA19EEF51ACD0657C22",
        HashAlgorithm::Sha384,
    ),
    HashTestCase(
        b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
        "04A371E84ECFB5B8B77CB48610FCA8182DD457CE6F326A0FD3D7EC2F1E91636DEE691FBE0C985302BA1B0D8DC78C086346B533B49C030D99A27DAF1139D6E75E",
        HashAlgorithm::Sha512,
    ),
    HashTestCase(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "03DD8807A93175FB062DFB55DC7D359C",
        HashAlgorithm::Md5,
    ),
    HashTestCase(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "A49B2446A02C645BF419F995B67091253A04A259",
        HashAlgorithm::Sha1,
    ),
    HashTestCase(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "916F6061FE879741CA6469B43971DFDB28B1A32DC36CB3254E812BE27AAD1D18",
        HashAlgorithm::Sha256,
    ),
    HashTestCase(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "79407D3B5916B59C3E30B09822974791C313FB9ECC849E406F23592D04F625DC8C709B98B43B3852B337216179AA7FC7",
        HashAlgorithm::Sha384,
    ),
    HashTestCase(
        b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
        "AFEBB2EF542E6579C50CAD06D2E578F9F8DD6881D7DC824D26360FEEBF18A4FA73E3261122948EFCFD492E74E82E2189ED0FB440D187F382270CB455F21DD185",
        HashAlgorithm::Sha512,
    ),
    ];

    // Source: RFC4231: https://datatracker.ietf.org/doc/html/rfc4231
    const HMAC_TEST_CASES: &[HmacTestCase] = &[
    // Test Case 1
    HmacTestCase(
        &[0x0b; 20], // Key: 20 bytes of 0x0b
        b"Hi There",  // Data: "Hi There"
        "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
    ),

    // Test Case 2
    HmacTestCase(
        b"Jefe", // Key: "Jefe"
        b"what do ya want for nothing?", // Data: "what do ya want for nothing?"
        "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
    ),

    // Test Case 3
    HmacTestCase(
        &[0xaa; 20], // Key: 20 bytes of 0xaa
        &[0xdd; 50], // Data: 50 bytes of 0xdd
        "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe",
    ),

    // Test Case 4
    HmacTestCase(
        &[
            0x01, 0x02, 0x03, 0x04, 0x05,
            0x06, 0x07, 0x08, 0x09, 0x0a,
            0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
            0x10, 0x11, 0x12, 0x13, 0x14,
            0x15, 0x16, 0x17, 0x18, 0x19,
        ], // Key: 25 bytes from 0x01 to 0x19
        &[0xcd; 50], // Data: 50 bytes of 0xcd
        "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b",
    ),

    // Test Case 5
    HmacTestCase(
        &[0x0c; 20], // Key: 20 bytes of 0x0c
        b"Test With Truncation", // Data: "Test With Truncation"
        "a3b6167473100ee06e0c796c2955552b", // Truncated HMAC-SHA256 (128 bits)
    ),

    // Test Case 6
    HmacTestCase(
        &[0xaa; 131], // Key: 131 bytes of 0xaa
        b"Test Using Larger Than Block Size Key - Hash Key First", // Data
        "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54",
    ),

    // Test Case 7
    HmacTestCase(
        &[0xaa; 131], // Key: 131 bytes of 0xaa
        b"This is a test using a larger than block-size key and a larger than block-size data. \
          The key needs to be hashed before being used by the HMAC algorithm.", // Data
        "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2",
    ),
    ];

    #[test]
    fn test_hash_simple() {
        let mut errors = Vec::new();

        for case in HASH_TEST_CASES {
            let input_cursor = Cursor::new(case.0);
            let result = match hash(input_cursor, case.2) {
                Ok(hash) => hash.to_upper_hex_string(),
                Err(e) => {
                    errors.push(format!(
                        "Hashing failed for {:?} with error: {:?}",
                        case.2, e
                    ));
                    continue;
                }
            };

            if result != case.1 {
                errors.push(format!(
                    "Mismatch for {:?}: expected {}, got {}",
                    case.2, case.1, result
                ));
            }
        }

        assert!(errors.is_empty(), "Errors encountered: {:?}", errors);
    }

    #[test]
    fn test_hash_long() {
        let mut errors = Vec::new();

        let input = b"a".repeat(1_000_000);
        let cases = &[
            (HashAlgorithm::Md5, "7707D6AE4E027C70EEA2A935C2296F21"),
            (HashAlgorithm::Sha1, "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"),
            (HashAlgorithm::Sha256, "5C8875AE474A3634BA4FD55EC85BFFD661F32ACA75C6D699D0CDCB6C115891C1"),
            (HashAlgorithm::Sha384, "EEE9E24D78C1855337983451DF97C8AD9EEDF256C6334F8E948D252D5E0E76847AA0774DDB90A842190D2C558B4B8340"),
            (HashAlgorithm::Sha512, "3C3A876DA14034AB60627C077BB98F7E120A2A5370212DFFB3385A18D4F38859ED311D0A9D5141CE9CC5C66EE689B266A8AA18ACE8282A0E0DB596C90B0A7B87"),
        ];

        for case in cases {
            let input_cursor = Cursor::new(input.clone());
            let result = match hash(input_cursor, case.0) {
                Ok(hash) => hash.to_upper_hex_string(),
                Err(e) => {
                    errors.push(format!(
                        "Hashing failed for {:?} with error: {:?}",
                        case.0, e
                    ));
                    continue;
                }
            };

            if result != case.1 {
                errors.push(format!(
                    "Mismatch for {:?}: expected {}, got {}",
                    case.0, case.1, result
                ));
            }
        }

        assert!(errors.is_empty(), "Errors encountered: {:?}", errors);
    }

    #[test]
    #[ignore] // it is too expensive.
    fn test_hash_extremely_long() {
        let mut errors = Vec::new();

        let input =
            b"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno".repeat(16_777_216);
        let cases = &[
            (HashAlgorithm::Md5, "D338139169D50F55526194C790EC0448"),
            (HashAlgorithm::Sha1, "7789F0C9EF7BFC40D93311143DFBE69E2017F592"),
            (HashAlgorithm::Sha256, "ECBBC42CBF296603ACB2C6BC0410EF4378BAFB24B710357F12DF607758B33E2B"),
            (HashAlgorithm::Sha384, "A04296F4FCAAE14871BB5AD33E28DCF69238B04204D9941B8782E816D014BCB7540E4AF54F30D578F1A1CA2930847A12"),
            (HashAlgorithm::Sha512, "235FFD53504EF836A1342B488F483B396EABBFE642CF78EE0D31FEEC788B23D0D18D5C339550DD5958A500D4B95363DA1B5FA18AFFC1BAB2292DC63B7D85097C"),
        ];

        for case in cases {
            let input_cursor = Cursor::new(input.clone());
            let result = match hash(input_cursor, case.0) {
                Ok(hash) => hash.to_upper_hex_string(),
                Err(e) => {
                    errors.push(format!(
                        "Hashing failed for {:?} with error: {:?}",
                        case.0, e
                    ));
                    continue;
                }
            };

            if result != case.1 {
                errors.push(format!(
                    "Mismatch for {:?}: expected {}, got {}",
                    case.0, case.1, result
                ));
            }
        }

        assert!(errors.is_empty(), "Errors encountered: {:?}", errors);
    }

    #[test]
    fn test_hmac_sha256_simple() {
        let mut errors = Vec::new();

        for (i, test_case) in HMAC_TEST_CASES.iter().enumerate() {
            let key_bytes = test_case.0;
            let data = test_case.1;
            let expected_hmac = test_case.2.to_lowercase();

            // Step 1: Prepare the key as SecureBytes
            let key = SecureBytes::new(key_bytes.to_vec());

            if key_bytes.len() > SHA256_BLOCK_SIZE {
                // Expect hmac_sha256_setup to return EINVAL for keys longer than block size
                match hmac_sha256_setup(&key) {
                    Err(Errno::EINVAL) => {
                        // Expected error, test passes for this case
                    }
                    Ok(_) => {
                        errors.push(format!(
                            "Test case {}: Expected EINVAL for key length {}, but setup succeeded.",
                            i + 1,
                            key_bytes.len()
                        ));
                    }
                    Err(e) => {
                        errors.push(format!(
                            "Test case {}: Expected EINVAL, but got different error: {:?}",
                            i + 1,
                            e
                        ));
                    }
                }
                continue; // Skip further steps for this test case
            }

            // Step 2: Setup HMAC-SHA256
            let setup_fd = match hmac_sha256_setup(&key) {
                Ok(fd) => fd,
                Err(e) => {
                    errors.push(format!(
                        "Test case {}: hmac_sha256_setup failed with error: {:?}",
                        i + 1,
                        e
                    ));
                    continue;
                }
            };

            // Step 3: Initialize HMAC-SHA256
            let init_sock = match hmac_sha256_init(&setup_fd, false) {
                Ok(sock) => sock,
                Err(e) => {
                    errors.push(format!(
                        "Test case {}: hmac_sha256_init failed with error: {:?}",
                        i + 1,
                        e
                    ));
                    continue;
                }
            };

            // Step 4: Feed the data
            let feed_result = hmac_sha256_feed(&init_sock, data, false);
            if let Err(e) = feed_result {
                errors.push(format!(
                    "Test case {}: hmac_sha256_feed failed with error: {:?}",
                    i + 1,
                    e
                ));
                continue;
            }

            // Step 5: Finalize and retrieve the HMAC tag
            let hmac_result = match hmac_sha256_fini(&init_sock) {
                Ok(hmac) => hmac,
                Err(e) => {
                    errors.push(format!(
                        "Test case {}: hmac_sha256_fini failed with error: {:?}",
                        i + 1,
                        e
                    ));
                    continue;
                }
            };

            // Step 6: Convert the HMAC tag to a hex string
            let computed_hex = hmac_result.unsecure().to_lower_hex_string();

            // Step 7: Compare with the expected output
            if expected_hmac.len() < 64 {
                // Truncated HMAC, compare only the necessary part
                if !computed_hex.starts_with(&expected_hmac) {
                    errors.push(format!(
                        "Test case {}: Mismatch.\nExpected (prefix): {}\nGot: {}",
                        i + 1,
                        expected_hmac,
                        &computed_hex[..expected_hmac.len()]
                    ));
                }
            } else {
                // Full HMAC, compare entirely
                if computed_hex != expected_hmac {
                    errors.push(format!(
                        "Test case {}: Mismatch.\nExpected: {}\nGot: {}",
                        i + 1,
                        expected_hmac,
                        computed_hex
                    ));
                }
            }
        }

        // Assert that no errors were collected
        assert!(
            errors.is_empty(),
            "HMAC-SHA256 Test failures:\n{}",
            errors.join("\n")
        );
    }

    // Helper function to flip a bit in a byte array
    fn flip_bit(bytes: &mut [u8], bit_index: usize) {
        let byte_index = bit_index / 8;
        let bit_in_byte = bit_index % 8;
        bytes[byte_index] ^= 1 << bit_in_byte;
    }

    #[test]
    fn test_key_derive_info() {
        // Test 1: Deriving with the same key and info gives the same derived key
        let master_key = Key::new([0u8; KEY_SIZE]);
        let info = b"Test Info";
        let derived_key1 = master_key.derive(None, info);
        let derived_key2 = master_key.derive(None, info);
        assert_eq!(
            derived_key1.as_ref(),
            derived_key2.as_ref(),
            "Derived keys should be the same when using the same master key and info"
        );

        // Test 2: Small change in master key leads to different derived key
        let mut master_key_modified = [0u8; KEY_SIZE];
        master_key_modified.copy_from_slice(master_key.as_ref());
        flip_bit(&mut master_key_modified, 0); // Flip the first bit
        let master_key2 = Key::new(master_key_modified);
        let derived_key3 = master_key2.derive(None, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key3.as_ref(),
            "Derived keys should be different when master key changes by one bit"
        );

        // Test 3: Small change in info leads to different derived key
        let info_modified = b"Test Info!";
        let derived_key4 = master_key.derive(None, info_modified);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key4.as_ref(),
            "Derived keys should be different when info changes"
        );

        // Test 4: Empty info parameter
        let empty_info = b"";
        let derived_key5 = master_key.derive(None, empty_info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key5.as_ref(),
            "Derived keys should be different when using empty info"
        );

        // Test 5: Derived key is not all zeros
        assert!(
            !derived_key1.is_zero(),
            "Derived key should not be all zeros"
        );

        // Test 6: Derived key length is correct
        assert_eq!(
            derived_key1.as_ref().len(),
            KEY_SIZE,
            "Derived key length should be KEY_SIZE"
        );

        // Test 7: Different master keys produce different derived keys
        let master_key_diff = Key::new([1u8; KEY_SIZE]);
        let derived_key6 = master_key_diff.derive(None, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key6.as_ref(),
            "Derived keys should be different when master keys are different"
        );

        // Test 8: Minimal change in info leads to different key
        let info_min_change = b"Test InfoX";
        let derived_key7 = master_key.derive(None, info_min_change);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key7.as_ref(),
            "Derived keys should be different when info changes slightly"
        );

        // Test 9: Consistency check with flipped bit in info
        let mut info_flipped = info.to_vec();
        if !info_flipped.is_empty() {
            flip_bit(&mut info_flipped, 0);
        }
        let derived_key8 = master_key.derive(None, &info_flipped);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key8.as_ref(),
            "Derived keys should be different when a bit in info is flipped"
        );
    }

    #[test]
    fn test_key_derive_salt() {
        let master_key = Key::new([0u8; KEY_SIZE]);
        let info = b"Test Info";

        // Test 1: Same key, same salt, same info should give same derived key
        let salt = Some(b"Test Salt" as &[u8]);
        let derived_key1 = master_key.derive(salt, info);
        let derived_key2 = master_key.derive(salt, info);
        assert_eq!(
            derived_key1.as_ref(),
            derived_key2.as_ref(),
            "Derived keys should be the same when using the same master key, salt, and info"
        );

        // Test 2: Different salts should produce different derived keys
        let salt_modified = Some(b"Test Salt Modified" as &[u8]);
        let derived_key3 = master_key.derive(salt_modified, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key3.as_ref(),
            "Derived keys should be different when salt changes"
        );

        // Test 3: Empty salt (Some with empty slice)
        let empty_salt = Some(b"" as &[u8]);
        let derived_key4 = master_key.derive(empty_salt, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key4.as_ref(),
            "Derived keys should be different when using an empty salt"
        );

        // Test 4: None salt
        let derived_key5 = master_key.derive(None, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key5.as_ref(),
            "Derived keys should be different when salt is None"
        );

        // Test 5: Small change in salt leads to different derived key
        let mut salt_flipped = b"Test Salt".to_vec();
        flip_bit(&mut salt_flipped, 0); // Flip the first bit
        let derived_key6 = master_key.derive(Some(&salt_flipped), info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key6.as_ref(),
            "Derived keys should be different when a bit in salt is flipped"
        );

        // Test 6: Different salt lengths
        let short_salt = Some(b"S" as &[u8]);
        let derived_key7 = master_key.derive(short_salt, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key7.as_ref(),
            "Derived keys should be different when using a short salt"
        );

        let long_salt = Some(&[0u8; 100][..]);
        let derived_key8 = master_key.derive(long_salt, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key8.as_ref(),
            "Derived keys should be different when using a long salt"
        );

        // Test 7: Derived key is not all zeros
        assert!(
            !derived_key1.is_zero(),
            "Derived key should not be all zeros"
        );

        // Test 8: Derived key length is correct
        assert_eq!(
            derived_key1.as_ref().len(),
            KEY_SIZE,
            "Derived key length should be KEY_SIZE"
        );

        // Test 9: Same salt with different info produces different keys
        let info_modified = b"Test Info Modified";
        let derived_key9 = master_key.derive(salt, info_modified);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key9.as_ref(),
            "Derived keys should be different when info changes even if salt is the same"
        );

        // Test 10: Different master key with same salt and info
        let mut master_key_modified = [0u8; KEY_SIZE];
        master_key_modified.copy_from_slice(master_key.as_ref());
        flip_bit(&mut master_key_modified, 0); // Flip the first bit
        let master_key2 = Key::new(master_key_modified);
        let derived_key10 = master_key2.derive(salt, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key10.as_ref(),
            "Derived keys should be different when master key changes"
        );

        // Test 11: Max allowed salt length (e.g., 255*hash_len for HKDF)
        let max_salt_length = 255 * 32; // For SHA3-256, hash_len is 32 bytes
        let max_salt = vec![0u8; max_salt_length];
        let max_salt = Some(&max_salt[..]);
        let derived_key11 = master_key.derive(max_salt, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key11.as_ref(),
            "Derived keys should be different when using maximum salt length"
        );

        // Test 12: Consistency check with same long salt
        let derived_key12 = master_key.derive(long_salt, info);
        assert_eq!(
            derived_key8.as_ref(),
            derived_key12.as_ref(),
            "Derived keys should be the same when using the same long salt"
        );

        // Test 13: Using None salt and empty salt should produce the same key
        assert_eq!(
            derived_key4.as_ref(),
            derived_key5.as_ref(),
            "Derived keys should be the same when salt is None vs empty"
        );

        // Test 14: Using empty info and None salt
        let derived_key13 = master_key.derive(None, b"");
        assert_ne!(
            derived_key1.as_ref(),
            derived_key13.as_ref(),
            "Derived keys should be different when both salt and info are different"
        );

        // Test 15: Reusing derived key as master key should produce different key
        let derived_key14 = derived_key1.derive(salt, info);
        assert_ne!(
            derived_key1.as_ref(),
            derived_key14.as_ref(),
            "Derived key used as master key should produce different key"
        );
    }

    #[test]
    fn test_aes_ctr_setup() {
        let key = Key::random().unwrap();
        assert!(!key.is_zero(), "key is all zeros!");

        match aes_ctr_setup(&key).map(drop) {
            Ok(()) => {}
            Err(Errno::EAFNOSUPPORT) => {
                // KCAPI not supported, skip.
                return;
            }
            Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
        };
    }

    #[test]
    fn test_aes_ctr_init() {
        let key = Key::random().unwrap();
        assert!(!key.is_zero(), "key is all zeros!");

        let setup_fd = match aes_ctr_setup(&key) {
            Ok(fd) => fd,
            Err(Errno::EAFNOSUPPORT) => {
                // KCAPI not supported, skip.
                return;
            }
            Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
        };

        let result = aes_ctr_init(&setup_fd, false);
        assert!(result.is_ok());
    }

    #[test]
    fn test_aes_ctr_enc_and_dec() {
        let key = Key::random().unwrap();
        assert!(!key.is_zero(), "key is all zeros!");

        let iv = IV::random();
        assert!(!iv.is_zero(), "iv is all zeros!");

        let setup_fd = match aes_ctr_setup(&key) {
            Ok(fd) => fd,
            Err(Errno::EAFNOSUPPORT) => {
                // KCAPI not supported, skip.
                return;
            }
            Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
        };

        let sock_enc = aes_ctr_init(&setup_fd, false).unwrap();
        aes_ctr_enc(&sock_enc, &[], Some(&iv), true).unwrap();

        let data =
            b"Change return success. Going and coming without error. Action brings good fortune.";
        let encrypted_size = aes_ctr_enc(&sock_enc, data, None, false).unwrap();
        assert_eq!(encrypted_size, data.len());

        let encrypted_data = aes_ctr_fini(&sock_enc, encrypted_size).unwrap();
        assert_eq!(encrypted_data.unsecure().len(), encrypted_size,);
        drop(sock_enc);

        let sock_dec = aes_ctr_init(&setup_fd, false).unwrap();
        aes_ctr_dec(&sock_dec, &[], Some(&iv), true).unwrap();
        let decrypted_size =
            aes_ctr_dec(&sock_dec, &encrypted_data.unsecure(), None, false).unwrap();
        assert_eq!(decrypted_size, encrypted_size);

        let decrypted_data = aes_ctr_fini(&sock_dec, encrypted_size).unwrap();
        assert_eq!(decrypted_data.unsecure(), data);
    }

    #[test]
    fn test_aes_ctr_enc_with_more_flag() {
        let key = Key::random().unwrap();
        assert!(!key.is_zero(), "key is all zeros!");

        let iv = IV::random();
        assert!(!iv.is_zero(), "iv is all zeros!");

        let setup_fd = match aes_ctr_setup(&key) {
            Ok(fd) => fd,
            Err(Errno::EAFNOSUPPORT) => {
                // KCAPI not supported, skip.
                return;
            }
            Err(errno) => panic!("aes_ctr_setup failed with error: {errno}"),
        };

        eprintln!("INITIALIZING ENCRYPTION");
        let sock = aes_ctr_init(&setup_fd, false).unwrap();
        eprintln!("SETTING IV");
        aes_ctr_enc(&sock, &[], Some(&iv), true).unwrap();

        let data_chunks = vec![
            b"Heavy is ".to_vec(),
            b"the root of light. ".to_vec(),
            b"Still is ".to_vec(),
            b"the master of moving.".to_vec(),
        ];

        let mut total_encrypted_size = 0;
        for (i, chunk) in data_chunks.iter().enumerate() {
            let more = if i < data_chunks.len() - 1 {
                true
            } else {
                false
            };
            eprintln!("ENCRYPTING CHUNK {i}");
            let enc_result = aes_ctr_enc(&sock, chunk, None, more);
            assert!(enc_result.is_ok(), "{enc_result:?}");
            total_encrypted_size += enc_result.unwrap();
        }

        eprintln!("FINALIZING ENCRYPTION");
        let encrypted_data = aes_ctr_fini(&sock, total_encrypted_size).unwrap();
        drop(sock);

        eprintln!("STARTING DECRYPTION");
        let sock_dec = aes_ctr_init(&setup_fd, false).unwrap();
        eprintln!("SETTING IV");
        aes_ctr_dec(&sock_dec, &[], Some(&iv), true).unwrap();
        eprintln!("WRITING ENCRYPTED DATA");
        let dec_result = aes_ctr_dec(&sock_dec, &encrypted_data.unsecure(), None, false).unwrap();
        assert_eq!(dec_result, total_encrypted_size);

        eprintln!("FINALIZING DECRYPTION");
        let decrypted_data = aes_ctr_fini(&sock_dec, total_encrypted_size).unwrap();
        assert_eq!(
            decrypted_data.unsecure().len(),
            total_encrypted_size,
            "{:?}",
            decrypted_data.unsecure()
        );
        let original_data: Vec<u8> = data_chunks.concat();
        assert_eq!(decrypted_data.unsecure(), original_data.as_slice());
    }

    #[test]
    fn test_aes_ctr_enc_and_dec_tmp() {
        let key = Key::random().unwrap();
        assert!(!key.is_zero(), "key is all zeros!");

        let iv = IV::random();
        assert!(!iv.is_zero(), "iv is all zeros!");

        let mut secret = Secret::new(key);
        if let Err(errno) = secret.init(None, b"SYD-ENC", b"SYD-TAG") {
            if errno == Errno::EAFNOSUPPORT {
                // KCAPI not supported, skip.
                return;
            }
            panic!("Secret::init failed with error: {errno}");
        };
        let (setup_enc, setup_mac) = if let Secret::Alg(setup_enc, setup_mac) = secret {
            (setup_enc, setup_mac)
        } else {
            panic!("Secret::init failed to mutate key!");
        };

        let sock_enc = aes_ctr_init(&setup_enc, false).unwrap();
        aes_ctr_enc(&sock_enc, &[], Some(&iv), true).unwrap();

        let data =
            b"Change return success. Going and coming without error. Action brings good fortune.";
        let total_size = data.len();
        let encrypted_size = aes_ctr_enc(&sock_enc, data, None, false).unwrap();
        assert_eq!(encrypted_size, total_size);
        let encrypted_data = aes_ctr_fini(&sock_enc, encrypted_size).unwrap();
        drop(sock_enc);

        let sock_mac = hmac_sha256_init(&setup_mac, false).unwrap();
        hmac_sha256_feed(&sock_mac, &CRYPT_MAGIC, true).unwrap();
        hmac_sha256_feed(&sock_mac, iv.as_ref(), true).unwrap();
        hmac_sha256_feed(&sock_mac, data, false).unwrap();
        let hmac_tag = hmac_sha256_fini(&sock_mac).unwrap();

        // Use a memfd to hold the encrypted data.
        let encrypted_memfd = create_memfd(b"syd\0", 0).unwrap();
        let nwrite = write(encrypted_memfd.as_fd(), CRYPT_MAGIC).unwrap();
        assert_eq!(nwrite, CRYPT_MAGIC.len());
        let nwrite = write(encrypted_memfd.as_fd(), hmac_tag.unsecure()).unwrap();
        assert_eq!(nwrite, HMAC_TAG_SIZE);
        let nwrite = write(encrypted_memfd.as_fd(), iv.as_ref()).unwrap();
        assert_eq!(nwrite, IV_SIZE);
        let nwrite = write(encrypted_memfd.as_fd(), &encrypted_data.unsecure()).unwrap();
        assert_eq!(nwrite, encrypted_data.unsecure().len());

        // Decrypt the data directly into a memfd with zero-copy.
        let sock_dec = aes_ctr_init(&setup_enc, false).unwrap();
        let tmp_dir = open("/tmp", OFlag::O_RDONLY, Mode::empty()).unwrap();
        let (decrypted_memfd, _) = match aes_ctr_tmp(
            (sock_dec.as_raw_fd(), sock_mac.as_raw_fd()),
            &encrypted_memfd,
            OFlag::empty(),
            Some(tmp_dir),
            false,
        ) {
            Ok(fd) => fd.unwrap(),
            Err(Errno::EOPNOTSUPP) => {
                // /tmp does not support O_TMPFILE.
                return;
            }
            Err(errno) => {
                panic!("aes_ctr_tmp failed: {errno}");
            }
        };
        drop(sock_dec);

        // Verify the decrypted data matches the original data.
        let mut decrypted_data = vec![0u8; total_size];
        lseek64(
            decrypted_memfd.as_raw_fd(),
            (CRYPT_MAGIC.len() + IV_SIZE) as i64,
            Whence::SeekSet,
        )
        .unwrap();
        read(decrypted_memfd.as_raw_fd(), &mut decrypted_data).unwrap();
        assert_eq!(
            decrypted_data,
            data,
            "mismatch: {decrypted_data:?} != {data:?} ({} != {}, {} != {})",
            String::from_utf8_lossy(&decrypted_data),
            String::from_utf8_lossy(data),
            decrypted_data.len(),
            data.len()
        );
    }
}
