lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// LCPFS: x86_64 Platform Implementation
// PURPOSE: Hardware entropy and timestamp for Intel/AMD CPUs
// INSTRUCTIONS: RDRAND, RDSEED, RDTSC, syscall

use super::ArchError;

/// Fill buffer with hardware entropy using RDRAND instruction.
///
/// # Arguments
/// * `buf` - Buffer to fill with random bytes
///
/// # Returns
/// * `Ok(())` - Buffer filled successfully
/// * `Err(ArchError::HardwareUnavailable)` - RDRAND not supported or failed
///
/// # Safety
/// This function uses inline assembly but is safe to call.
pub fn fill_hardware_entropy(buf: &mut [u8]) -> Result<(), ArchError> {
    for chunk in buf.chunks_mut(8) {
        let mut value: u64 = 0;
        let mut success: u8 = 0;

        // Retry up to 10 times per RDRAND spec
        for _ in 0..10 {
            // SAFETY INVARIANTS:
            // 1. RDRAND is unprivileged x86_64 instruction (ring 3 allowed)
            // 2. Outputs: {0} = random u64, {1} = carry flag (success indicator)
            // 3. nomem: instruction does not access memory
            // 4. nostack: instruction does not touch stack
            // 5. CPU internal RNG state managed safely by hardware
            // 6. Instruction may fail (CF=0), handled by retry loop
            //
            // VERIFICATION: RDRAND available on Intel Ivy Bridge+ (2012), AMD Excavator+ (2015)
            //
            // JUSTIFICATION:
            // Hardware RNG provides high-quality entropy for ChaCha20 seeding.
            unsafe {
                core::arch::asm!(
                    "rdrand {0}",
                    "setc {1}",
                    out(reg) value,
                    out(reg_byte) success,
                    options(nomem, nostack),
                );
            }

            if success != 0 {
                let bytes = value.to_le_bytes();
                let len = chunk.len().min(8);
                chunk[..len].copy_from_slice(&bytes[..len]);
                break;
            }
        }

        if success == 0 {
            return Err(ArchError::HardwareUnavailable);
        }
    }

    Ok(())
}

/// Fill buffer with seed entropy using RDSEED instruction.
///
/// RDSEED provides higher-quality entropy than RDRAND (directly from
/// entropy source rather than DRBG). Use for seeding other PRNGs.
///
/// # Returns
/// * `Ok(())` - Buffer filled successfully
/// * `Err(ArchError::HardwareUnavailable)` - RDSEED not supported or entropy depleted
pub fn fill_seed_entropy(buf: &mut [u8]) -> Result<(), ArchError> {
    for chunk in buf.chunks_mut(8) {
        let mut value: u64 = 0;
        let mut success: u8 = 0;

        // RDSEED can fail more often than RDRAND (entropy depletion)
        // More retries needed
        for _ in 0..100 {
            // SAFETY INVARIANTS:
            // 1. RDSEED is unprivileged x86_64 instruction (ring 3 allowed)
            // 2. Outputs: {0} = seed u64, {1} = carry flag (success indicator)
            // 3. nomem: instruction does not access memory
            // 4. nostack: instruction does not touch stack
            // 5. May fail frequently due to entropy depletion (CF=0)
            //
            // VERIFICATION: RDSEED available on Intel Broadwell+ (2014), AMD Zen+ (2018)
            //
            // JUSTIFICATION:
            // RDSEED provides true entropy (not DRBG output) for seeding PRNGs.
            unsafe {
                core::arch::asm!(
                    "rdseed {0}",
                    "setc {1}",
                    out(reg) value,
                    out(reg_byte) success,
                    options(nomem, nostack),
                );
            }

            if success != 0 {
                let bytes = value.to_le_bytes();
                let len = chunk.len().min(8);
                chunk[..len].copy_from_slice(&bytes[..len]);
                break;
            }

            // Small pause to allow entropy accumulation
            core::hint::spin_loop();
        }

        if success == 0 {
            return Err(ArchError::HardwareUnavailable);
        }
    }

    Ok(())
}

/// Get CPU timestamp counter (TSC) value.
///
/// Returns a monotonically increasing counter that increments at CPU frequency.
/// Useful for timing and entropy mixing (NOT cryptographically secure alone).
///
/// # Returns
/// 64-bit timestamp counter value
#[inline]
pub fn get_timestamp() -> u64 {
    let low: u32;
    let high: u32;

    // SAFETY INVARIANTS:
    // 1. RDTSC is unprivileged x86_64 instruction (ring 3 allowed)
    // 2. Outputs: eax = low 32 bits, edx = high 32 bits of TSC
    // 3. nomem: instruction does not access memory
    // 4. nostack: instruction does not touch stack
    // 5. preserves_flags: EFLAGS unchanged
    // 6. TSC guaranteed monotonically increasing (per-CPU)
    //
    // VERIFICATION: RDTSC available on all x86_64 CPUs
    //
    // JUSTIFICATION:
    // High-resolution timestamp for entropy mixing and timing.
    unsafe {
        core::arch::asm!(
            "rdtsc",
            out("eax") low,
            out("edx") high,
            options(nomem, nostack, preserves_flags),
        );
    }

    ((high as u64) << 32) | (low as u64)
}

/// Perform a Linux syscall with 3 arguments.
///
/// # Safety
/// Caller must ensure:
/// - `num` is a valid syscall number
/// - Arguments are valid for the specific syscall
/// - This is only called on Linux
#[cfg(target_os = "linux")]
pub unsafe fn syscall3(num: i64, arg1: usize, arg2: usize, arg3: usize) -> i64 {
    let ret: i64;

    // SAFETY INVARIANTS:
    // 1. Linux x86_64 syscall ABI: num->rax, arg1->rdi, arg2->rsi, arg3->rdx
    // 2. Caller ensures arguments are valid for specific syscall number
    // 3. syscall instruction clobbers rcx and r11 (marked as outputs)
    // 4. Return value in rax (lateout ensures after syscall executes)
    // 5. nostack: syscall does not modify user stack
    // 6. preserves_flags: safe for non-signal syscalls
    //
    // VERIFICATION: Linux syscall ABI documented in syscall(2)
    //
    // JUSTIFICATION:
    // Direct syscall required for getrandom(2) in no_std environment.
    core::arch::asm!(
        "syscall",
        in("rax") num,
        in("rdi") arg1,
        in("rsi") arg2,
        in("rdx") arg3,
        out("rcx") _,
        out("r11") _,
        lateout("rax") ret,
        options(nostack, preserves_flags),
    );

    ret
}

/// Check if RDRAND instruction is available.
///
/// Uses CPUID to check for RDRAND support.
#[inline]
pub fn has_rdrand() -> bool {
    // CPUID leaf 1, ECX bit 30
    let ecx: u32;

    // SAFETY: CPUID is always available on x86_64.
    // We must save/restore rbx because LLVM reserves it.
    unsafe {
        core::arch::asm!(
            "push rbx",       // Save rbx (LLVM reserved)
            "mov eax, 1",
            "cpuid",
            "pop rbx",        // Restore rbx
            out("ecx") ecx,
            out("eax") _,
            out("edx") _,
            options(nomem, preserves_flags),
        );
    }

    (ecx & (1 << 30)) != 0
}

/// Check if AES-NI instruction set is available.
///
/// Uses CPUID to check for AES-NI support (Intel AES New Instructions).
/// AES-NI provides hardware acceleration for AES encryption/decryption.
#[inline]
pub fn has_aesni() -> bool {
    // CPUID leaf 1, ECX bit 25
    let ecx: u32;

    // SAFETY: CPUID is always available on x86_64.
    // We must save/restore rbx because LLVM reserves it.
    unsafe {
        core::arch::asm!(
            "push rbx",       // Save rbx (LLVM reserved)
            "mov eax, 1",
            "cpuid",
            "pop rbx",        // Restore rbx
            out("ecx") ecx,
            out("eax") _,
            out("edx") _,
            options(nomem, preserves_flags),
        );
    }

    (ecx & (1 << 25)) != 0
}

/// Check if PCLMULQDQ instruction is available.
///
/// PCLMULQDQ (carry-less multiplication) is used by GCM mode for
/// GHASH computation. Required for efficient AES-GCM.
#[inline]
pub fn has_pclmulqdq() -> bool {
    // CPUID leaf 1, ECX bit 1
    let ecx: u32;

    // SAFETY: CPUID is always available on x86_64.
    unsafe {
        core::arch::asm!(
            "push rbx",
            "mov eax, 1",
            "cpuid",
            "pop rbx",
            out("ecx") ecx,
            out("eax") _,
            out("edx") _,
            options(nomem, preserves_flags),
        );
    }

    (ecx & (1 << 1)) != 0
}

/// Check if RDSEED instruction is available.
///
/// Uses CPUID to check for RDSEED support.
#[inline]
pub fn has_rdseed() -> bool {
    // CPUID leaf 7, EBX bit 18
    let ebx: u32;

    // SAFETY: CPUID is always available on x86_64.
    // We must save/restore rbx because LLVM reserves it.
    // We use a temporary register to capture ebx's value.
    unsafe {
        core::arch::asm!(
            "push rbx",       // Save rbx (LLVM reserved)
            "mov eax, 7",
            "xor ecx, ecx",
            "cpuid",
            "mov {0:e}, ebx", // Copy ebx to output before restore
            "pop rbx",        // Restore rbx
            out(reg) ebx,
            out("eax") _,
            out("ecx") _,
            out("edx") _,
            options(nomem, preserves_flags),
        );
    }

    (ebx & (1 << 18)) != 0
}

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

    #[test]
    fn test_rdrand_available() {
        // Most modern x86_64 CPUs have RDRAND
        let _available = has_rdrand();
        // Don't assert - old CPUs may not have it
    }

    #[test]
    fn test_rdseed_available() {
        let _available = has_rdseed();
        // Don't assert - old CPUs may not have it
    }

    #[test]
    fn test_fill_entropy() {
        if has_rdrand() {
            let mut buf = [0u8; 32];
            let result = fill_hardware_entropy(&mut buf);
            assert!(result.is_ok());
            assert_ne!(buf, [0u8; 32]); // Should not be all zeros
        }
    }

    #[test]
    fn test_timestamp() {
        let t1 = get_timestamp();
        let t2 = get_timestamp();
        assert!(t2 >= t1); // Monotonic
    }

    #[test]
    fn test_entropy_uniqueness() {
        if has_rdrand() {
            let mut buf1 = [0u8; 32];
            let mut buf2 = [0u8; 32];
            fill_hardware_entropy(&mut buf1).unwrap();
            fill_hardware_entropy(&mut buf2).unwrap();
            assert_ne!(buf1, buf2); // Should be different
        }
    }
}