//
// Syd: rock-solid application kernel
// tui/src/rng.rs: OS Random Number Generator (RNG) interface
//
// Copyright (c) 2023, 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

//! Set of functions to manage the OS Random Number Generator (RNG)

use std::convert::TryInto;

use libc::{c_int, GRND_RANDOM};
use nix::errno::Errno;

/// RAII guard that disables pthread cancellation for the current thread
/// and restores the previous state on drop. Uses pthread_setcancelstate(3).
#[must_use = "hold the guard to keep cancellation disabled"]
pub struct CancelGuard(c_int);

const _PTHREAD_CANCEL_ENABLE: c_int = 0;
const PTHREAD_CANCEL_DISABLE: c_int = 1;

// Libc crate does not define this symbol explicitly yet.
extern "C" {
    fn pthread_setcancelstate(state: c_int, oldstate: *mut c_int) -> c_int;
}

impl CancelGuard {
    /// Acquire the guard by disabling pthread cancellation for this thread.
    ///
    /// Returns a guard that will restore the previous state when dropped.
    pub fn acquire() -> Result<Self, Errno> {
        let mut old: c_int = 0;

        // SAFETY: We call pthread_setcancelstate(3) for the current thread.
        // - PTHREAD_CANCEL_DISABLE is a valid constant.
        // - Second arg is a valid, writable pointer to store the previous state.
        // - This does not move or alias Rust values; it only flips the thread-local flag.
        let err = unsafe { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &raw mut old) };

        // POSIX returns error code directly (not via errno).
        if err == 0 {
            Ok(Self(old))
        } else {
            Err(Errno::from_raw(err))
        }
    }
}

impl Drop for CancelGuard {
    fn drop(&mut self) {
        // SAFETY: Restore the exact state captured at construction
        // for the current thread. The second parameter can be NULL
        // when we don't care about the previous value.
        unsafe {
            pthread_setcancelstate(self.0, std::ptr::null_mut());
        }
    }
}

// Fill the given buffer using the OS random number generator.
pub(crate) fn fillrandom(buf: &mut [u8]) -> Result<(), Errno> {
    // SAFETY: Ensure buffer is not empty,
    // which is a common case of error.
    let siz = buf.len();
    if siz == 0 {
        return Err(Errno::EINVAL);
    }

    // Disable pthread cancellation within this critical section.
    // Restored automatically when guard is dropped.
    let guard = CancelGuard::acquire()?;

    let mut n = 0;
    while n < siz {
        let ptr = &mut buf[n..];
        let ptr = ptr.as_mut_ptr().cast();
        let siz = siz.checked_sub(n).ok_or(Errno::EOVERFLOW)?;

        n = n
            .checked_add(
                retry_on_eintr(|| {
                    // SAFETY: `ptr` points into `buf[n..]` and `siz` is the
                    // remaining byte count; both are within bounds.
                    Errno::result(unsafe { libc::getrandom(ptr, siz, GRND_RANDOM) })
                })?
                .try_into()
                .or(Err(Errno::EINVAL))?,
            )
            .ok_or(Errno::EOVERFLOW)?;
    }

    // End of critical section.
    drop(guard);

    Ok(())
}

// Retries a closure on `EINTR` errors.
//
// This function will call the provided closure, and if the closure
// returns `EINTR` error, it will retry the operation until it
// succeeds or fails with a different error.
fn retry_on_eintr<F, T>(mut f: F) -> Result<T, Errno>
where
    F: FnMut() -> Result<T, Errno>,
{
    loop {
        match f() {
            Err(Errno::EINTR) => continue,
            result => return result,
        }
    }
}
