//! FFI bridge for the `randombytes` symbol expected by `mldsa-native`.
//!
//! Provides a configurable Rust-backed implementation and a convenience
//! macro to export a C-compatible `randombytes` function with the
//! correct symbol name for linking with the backend C code.
//!
//! **All notes [about `randombytes`](crate#about-randombytes)
//! documented at the top of the crate apply to this module.**
//!
//! # Feature flags
//!
//! - `rand`: Enables the default Rust-backed CSPRNG implementation.
//! - `extern-C-randombytes`: Automatically exports the C symbol.

/// The internal name for the `randombytes` function symbol expected
/// by the mldsa-native C code, to bridge the C and Rust
/// implementations.
///
/// NOTE: We use a custom symbol name when compiling both the C code
/// and this crate to avoid global symbol collisions at link time.
// NOTE: This is set via env variable in our build.rs file.
pub const RANDOMBYTES_INTERNAL_NAME: &str = env!("RANDOMBYTES_INTERNAL_NAME");

/// Generates random bytes and writes them into the provided buffer.
///
/// # Parameters
///
/// - `out`: A mutable slice where the random bytes will be written.
///
/// # Returns
///
/// Returns `Ok(())` on success. This function cannot fail as it uses
/// `rand::fill`, which is infallible.
#[cfg(any(test, feature = "rand"))]
pub fn default_randombytes(out: &mut [u8]) -> Result<(), ::core::convert::Infallible> {
    rand::fill(out);
    Ok(())
}

// NOTE: See
// <https://internals.rust-lang.org/t/pub-on-macro-rules/19358/16>
// on why we need this dance to make the macro available to
// external crates, without polluting the crate's top namespace.
#[doc(hidden)]
#[macro_export]
macro_rules! __khbfVNyeQk_make_randombytes_fn {
    () => {
        #[cfg(any(test, feature = "rand"))]
        $crate::randombytes::make_randombytes_fn!($crate::randombytes::default_randombytes);

        #[cfg(not(feature = "rand"))]
        $crate::randombytes::make_randombytes_fn!(|_slice| {
            Err("stub implementation that always fails")
        });
    };
    ($f:expr) => {
        /// This function provides a C-compatible interface for
        /// generating random bytes, as expected by the underlying C
        /// code of
        /// [mldsa-native](https://github.com/pq-code-package/mldsa-native/blob/0b1c5364dc468a726aab4adc12a9385ba55f0306/mldsa/src/randombytes.h).
        ///
        /// # Parameters
        ///
        /// - `out`: A raw pointer to the output buffer where random
        ///   bytes will be written.
        /// - `outlen`: The number of random bytes to generate.
        ///
        /// # Returns
        ///
        /// Returns `::std::os::raw::c_int` indicating success (`0`)
        /// or failure (`-1`).
        ///
        /// # Safety
        ///
        /// - `out` must be either null or point to a valid, writable memory
        ///   region of at least `outlen` bytes.
        /// - The memory referenced by `out` must not alias any immutable data.
        /// - The caller must uphold all invariants required by
        ///   `std::slice::from_raw_parts_mut`.
        #[unsafe(export_name = env!("RANDOMBYTES_INTERNAL_NAME"))]
        pub unsafe extern "C" fn randombytes(out: *mut u8, outlen: usize) -> ::std::os::raw::c_int {
            use $crate::ffi::{FAILURE, SUCCESS};

            if out.is_null() {
                return FAILURE;
            }

            let slice = unsafe { std::slice::from_raw_parts_mut(out, outlen) };

            match ($f)(slice) {
                Ok(()) => SUCCESS,
                Err(_) => FAILURE,
            }
        }
    };
}

/// Convenience macro to generate the extern "C" "randombytes"
/// function expected by the underlying C code of
/// [mldsa-native](https://github.com/pq-code-package/mldsa-native/blob/0b1c5364dc468a726aab4adc12a9385ba55f0306/mldsa/src/randombytes.h).
///
/// The exported name of the function symbol will be set to
/// [crate::randombytes::RANDOMBYTES_INTERNAL_NAME], as required to
/// bridge the C and Rust implementations.
///
/// # Invocation Forms
///
/// ## Without arguments
///
/// ```rust, ignore
/// make_randombytes_fn!()
/// ```
///
/// Use this crate's default implementation:
///
/// - If the `rand` feature is enabled, it forwards to
///   [`crate::randombytes::default_randombytes`].
/// - Otherwise, it uses a stub implementation that always fails.
///
/// ## With a custom implementation
///
/// ```rust, ignore
/// make_randombytes_fn!(EXPR)
/// ```
///
/// `EXPR` must evaluate to either:
///
/// - A function with the following signature:
///
///   ```rust, ignore
///   fn f(out: &mut [u8]) -> Result<(), E>
///   ```
///
/// - A closure of the form:
///
///   ```rust, ignore
///   |out: &mut [u8]| -> Result<(), E>
///   ```
///
/// ### Requirements
///
/// The provided function or closure must:
///
/// - Fill `out` completely with fresh, unique, unpredictable bytes
/// - Use a cryptographically secure pseudorandom number generator (CSPRNG)
/// - Return `Ok(())` on success
/// - Return `Err(_)` if randomness generation fails
///
/// ### Security
///
/// This function is a critical cryptographic dependency. Any weakness
/// in the provided randomness source directly compromises the security
/// of all non-deterministic operations performed by `mldsa-native`.
///
/// # Examples
///
/// Provide a custom closure:
///
/// ```rust
/// use mldsa_native_rs::randombytes::make_randombytes_fn;
///
/// make_randombytes_fn!(|_out| {
///     Err("this stub always fails")
/// });
/// ```
///
/// Provide a function:
///
/// ```rust
/// use mldsa_native_rs::randombytes::make_randombytes_fn;
///
/// make_randombytes_fn!(
///     mldsa_native_rs::randombytes::default_randombytes
/// );
/// ```
///
/// The zero-argument form:
///
/// ```rust
/// use mldsa_native_rs::randombytes::make_randombytes_fn;
///
/// make_randombytes_fn!();
/// ```
///
/// is equivalent to either of the previous 2 examples, depending on
/// the `rand` feature being enabled.
#[doc(inline)]
pub use __khbfVNyeQk_make_randombytes_fn as make_randombytes_fn;

#[cfg(any(test, feature = "extern-C-randombytes"))]
make_randombytes_fn!();
