// Copyright 2025 Simo Sorce
// See LICENSE.txt file for terms

//! This package provides common utilities, wrappers, and constants for interacting
//! with the OpenSSL library (`libcrypto`) via its C API, primarily focusing on
//! the EVP (high-level) interface and parameter handling (`OSSL_PARAM`).

/// Part of this module is automatically generated by bindgen from the OpenSSL
/// Headers and includes a selection of functions and other items needed to
/// access the libcrypto/libfips functions needed.
pub mod bindings {
    #![allow(non_upper_case_globals)]
    #![allow(non_camel_case_types)]
    #![allow(dead_code)]
    #![allow(non_snake_case)]
    #![allow(unnecessary_transmutes)]
    include!(concat!(env!("OUT_DIR"), "/ossl_bindings.rs"));
}

use std::borrow::Cow;
use std::ffi::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong, c_void, CStr};

use crate::bindings::*;

pub mod asymcipher;
pub mod cipher;
pub mod derive;
pub mod digest;
pub mod mac;
pub mod pkey;
pub mod rand;
pub mod signature;

#[cfg(feature = "fips")]
pub mod fips;

/// Export the API level of the OpenSSL library these bindings
/// were built against. This should be used only when probing
/// for behavior is difficult as it is only a proxy for what was
/// available at compile time, which may be different from what is
/// available at runtime when dynamically linking to shared libraries.
///
/// This returns a triplet of u8 values representing Major, Minor, and
/// Patch version.
pub fn api_level() -> (u8, u8, u8) {
    let patch: u8 = (OPENSSL_API_LEVEL & 0xff) as u8;
    let minor: u8 = ((OPENSSL_API_LEVEL & 0xff00) >> 8) as u8;
    let major: u8 = ((OPENSSL_API_LEVEL & 0xff0000) >> 16) as u8;
    (major, minor, patch)
}

/// Securely zeroizes a memory slice using `OPENSSL_cleanse`.
pub fn zeromem(mem: &mut [u8]) {
    unsafe {
        OPENSSL_cleanse(void_ptr!(mem.as_mut_ptr()), mem.len());
    }
}

/// Convenience macro to type cast any pointer into a mutable void
/// NOTE(1): bindgen always turns void pointers to mutable ones, but in most
/// cases the pointed data nor the pointer itself are mutated, so this casts
/// any pointer regardless of its original mutability.
/// NOTE(2): we do not wrap in unsafe{} because often this macro is invoked
/// from an unsafe{} code block.
///
/// This macro is UNSAFE, use carefully.
macro_rules! void_ptr {
    ($ptr:expr) => {
        $ptr as *const _ as *mut ::std::ffi::c_void
    };
}
pub(crate) use void_ptr;

macro_rules! cstr {
    ($str:expr) => {
        unsafe {
            ::std::ffi::CStr::from_ptr(
                $str.as_ptr() as *const ::std::ffi::c_char
            )
        }
    };
}
pub(crate) use cstr;

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum ErrorKind {
    /// OpenSSL returned a NULL ptr as an error
    NullPtr,
    /// OpenSSL returned a 0 c_int as an error
    OsslError,
    /// A falure resulting from wrong key usage
    KeyError,
    /// A wrapper error
    WrapperError,
    /// A buffer is not of the correct size
    BufferSize,
    /// An optional argument is required or has a bad value
    BadArg,
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            ErrorKind::NullPtr => "OpenSSL returned a NULL ptr as an error",
            ErrorKind::OsslError => "OpenSSL returned a 0 c_int as an error",
            ErrorKind::KeyError => "A falure resulting from wrong key usage",
            ErrorKind::WrapperError => "A wrapper error",
            ErrorKind::BufferSize => "A buffer is not of the correct size",
            ErrorKind::BadArg => {
                "An optional argument is required or has a bad value"
            }
        })
    }
}

#[derive(Debug, Clone)]
pub struct Error {
    kind: ErrorKind,
}

impl Error {
    pub fn new(k: ErrorKind) -> Error {
        Error { kind: k }
    }

    pub fn kind(&self) -> ErrorKind {
        self.kind
    }
}

impl std::error::Error for Error {}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.kind())
    }
}

impl From<std::num::TryFromIntError> for Error {
    /// Maps an integer conversion error to a generic error
    fn from(_error: std::num::TryFromIntError) -> Error {
        Error::new(ErrorKind::WrapperError)
    }
}

impl From<std::io::Error> for Error {
    /// Maps an io error to a generic error
    fn from(_error: std::io::Error) -> Error {
        Error::new(ErrorKind::WrapperError)
    }
}

#[cfg(all(feature = "log", feature = "fips"))]
pub fn ossl_err_stack() -> String {
    /* there is no external error management with fips builds */
    "".to_string()
}

#[cfg(all(feature = "log", not(feature = "fips")))]
pub fn ossl_err_stack() -> String {
    // Use a mem bio to "print out" the error stack
    let mut bio = std::ptr::null_mut();
    let bio_method = unsafe { BIO_s_mem() };
    if !bio_method.is_null() {
        bio = unsafe { BIO_new(bio_method) };
    }
    if bio.is_null() {
        return "Failed to fetch OpenSSL Error Stack".to_string();
    }
    unsafe { ERR_print_errors(bio) };

    /* retrieve the mem bio data as a long string, with embedded \n */
    let mut raw_mem: *mut c_char = std::ptr::null_mut();
    let raw_len = unsafe {
        BIO_ctrl(bio, BIO_CTRL_INFO as c_int, 0, void_ptr!(&mut raw_mem))
    };

    if raw_mem.is_null() || raw_len == 0 {
        return "Failed to get error from OpenSSL's Error Stack".to_string();
    }

    /* copy this buffer to a vector so we can turn it into a Rust String */
    let mut vec = vec![0u8; raw_len as usize];
    unsafe {
        std::ptr::copy_nonoverlapping(
            raw_mem as *const u8,
            vec.as_mut_ptr(),
            vec.len(),
        );
    }

    // remove final newline if any
    while vec[vec.len() - 1] == b'\n' {
        let _ = vec.pop();
    }
    match String::from_utf8(vec) {
        Ok(s) => s,
        Err(e) => format!("Failed to parse OpenSSL Error Stack: [{:?}]", e),
    }
}

macro_rules! trace_ossl {
    ($name:expr) => {
        #[cfg(feature = "log")]
        {
            use log::error;
            error!(
                "{}:{}: {} failed: [{}]",
                file!(),
                line!(),
                $name,
                crate::ossl_err_stack()
            );
        }
    };
}
pub(crate) use trace_ossl;

/// A structure representing the main crypto library context
pub struct OsslContext {
    context: *mut OSSL_LIB_CTX,
    providers: Vec<*mut OSSL_PROVIDER>,
    #[cfg(feature = "fips")]
    is_fips: bool,
}

static LEGACY_PROVIDER_NAME: &CStr = c"legacy";

impl OsslContext {
    pub fn new_lib_ctx() -> OsslContext {
        OsslContext {
            context: unsafe { OSSL_LIB_CTX_new() },
            providers: Vec::new(),
            #[cfg(feature = "fips")]
            is_fips: false,
        }
    }

    #[allow(dead_code)]
    pub fn from_ctx(ctx: *mut OSSL_LIB_CTX) -> OsslContext {
        OsslContext {
            context: ctx,
            providers: Vec::new(),
            #[cfg(feature = "fips")]
            is_fips: false,
        }
    }

    #[cfg(feature = "fips")]
    pub fn from_fips(ctx: *mut OSSL_LIB_CTX, prov: *mut c_void) -> OsslContext {
        OsslContext {
            context: ctx,
            providers: vec![prov as *mut OSSL_PROVIDER],
            is_fips: true,
        }
    }

    #[cfg(feature = "fips")]
    pub(crate) fn fips_provider(&self) -> *const c_void {
        if let Some(p) = self.providers.last() {
            return *p as *const c_void;
        }
        return std::ptr::null();
    }

    pub fn load_configuration_file(
        &mut self,
        fname: Option<&::std::path::Path>,
    ) -> Result<(), Error> {
        if cfg!(feature = "fips") {
            return Err(Error::new(ErrorKind::OsslError));
        }

        let filename: *const c_char = match fname {
            Some(f) => {
                f.as_os_str().as_encoded_bytes().as_ptr() as *const c_char
            }
            None => std::ptr::null(),
        };
        let ret = unsafe { OSSL_LIB_CTX_load_config(self.ptr(), filename) };
        if ret != 1 {
            trace_ossl!("OSSL_LIB_CTX_load_config()");
            Err(Error::new(ErrorKind::OsslError))
        } else {
            Ok(())
        }
    }

    pub fn load_default_configuration(&mut self) -> Result<(), Error> {
        self.load_configuration_file(None)
    }

    pub fn load_legacy_provider(&mut self) -> Result<(), Error> {
        if cfg!(feature = "fips") {
            return Err(Error::new(ErrorKind::OsslError));
        }

        if unsafe {
            OSSL_PROVIDER_available(self.ptr(), LEGACY_PROVIDER_NAME.as_ptr())
        } == 1
        {
            return Ok(());
        }

        let provider = unsafe {
            OSSL_PROVIDER_load(self.ptr(), LEGACY_PROVIDER_NAME.as_ptr())
        };
        if provider.is_null() {
            Err(Error::new(ErrorKind::OsslError))
        } else {
            self.providers.push(provider);
            Ok(())
        }
    }

    pub fn ptr(&self) -> *mut OSSL_LIB_CTX {
        self.context
    }
}

impl Drop for OsslContext {
    fn drop(&mut self) {
        #[cfg(feature = "fips")]
        if self.is_fips {
            /* When from_fips is used we get a context from the caller that we
             * are not supposed to free directly */
            return;
        }
        unsafe {
            while let Some(provider) = self.providers.pop() {
                OSSL_PROVIDER_unload(provider);
            }
            OSSL_LIB_CTX_free(self.context);
        }
    }
}

unsafe impl Send for OsslContext {}
unsafe impl Sync for OsslContext {}

/// Wrapper around OpenSSL's `BIGNUM` type for handling large numbers.
/// Manages the lifecycle and provides conversion methods.
#[derive(Debug)]
struct BigNum {
    bn: *mut BIGNUM,
}

impl BigNum {
    /// Returns a const pointer to the underlying `BIGNUM`.
    #[allow(dead_code)]
    pub fn as_ptr(&self) -> *const BIGNUM {
        self.bn
    }

    /// Returns a mutable pointer to the underlying `BIGNUM`.
    pub fn as_mut_ptr(&mut self) -> *mut BIGNUM {
        self.bn
    }

    /// Allocates a new BIGNUM.
    pub fn new() -> Result<BigNum, Error> {
        let bn = unsafe { BN_secure_new() };

        if bn.is_null() {
            trace_ossl!("BN_secure_new()");
            Err(Error::new(ErrorKind::NullPtr))
        } else {
            Ok(BigNum { bn })
        }
    }

    /// Allocates a new BIGNUM from a slice of bytes with the binary
    /// representation of the number in big endian byte order (most
    /// significant byte first).
    ///
    /// Returns a wrapped `BigNum` or an error if the import fails.
    pub fn from_bigendian_slice(v: &[u8]) -> Result<BigNum, Error> {
        let mut bn = BigNum::new()?;

        let ret = unsafe {
            BN_bin2bn(
                v.as_ptr() as *mut u8,
                c_int::try_from(v.len())?,
                bn.as_mut_ptr(),
            )
        };
        if ret.is_null() {
            trace_ossl!("BN_bin2bn()");
            return Err(Error::new(ErrorKind::NullPtr));
        }

        Ok(bn)
    }

    /// Calculates the minimum number of bytes needed to represent the `BIGNUM`.
    pub fn len(&self) -> Result<usize, Error> {
        let x = unsafe { (BN_num_bits(self.as_ptr()) + 7) / 8 };
        Ok(usize::try_from(x)?)
    }

    /// Creates a `BigNum` by extracting it from an `OSSL_PARAM`.
    pub fn from_param(p: *const OSSL_PARAM) -> Result<BigNum, Error> {
        let mut bn: *mut BIGNUM = std::ptr::null_mut();
        if unsafe { OSSL_PARAM_get_BN(p, &mut bn) } != 1 {
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(BigNum { bn })
    }

    /// Converts the `BIGNUM` to a byte vector in native-endian format, padded
    /// to the required length. Primarily used internally for constructing
    /// `OSSL_PARAM`s.
    pub fn to_native_vec(&self) -> Result<Vec<u8>, Error> {
        let mut v = vec![0u8; self.len()?];
        if v.len() == 0 {
            v.push(0);
        }
        let ret = unsafe {
            BN_bn2nativepad(
                self.as_ptr(),
                v.as_mut_ptr(),
                c_int::try_from(v.len())?,
            )
        };
        if ret < 1 {
            trace_ossl!("BN_bn2nativepad()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(v)
    }

    /// Converts the `BIGNUM` to a byte vector in big-endian format (standard
    /// external representation).
    pub fn to_bigendian_vec(&self) -> Result<Vec<u8>, Error> {
        let len = self.len()?;
        let mut v = vec![0u8; self.len()?];
        let ret = unsafe { BN_bn2bin(self.as_ptr(), v.as_mut_ptr()) };
        if usize::try_from(ret)? != len {
            return Err(Error::new(ErrorKind::WrapperError));
        }
        Ok(v)
    }
}

impl Drop for BigNum {
    fn drop(&mut self) {
        unsafe {
            BN_free(self.as_mut_ptr());
        }
    }
}

/// Helper container to keep references around in structure that deal with
/// FFI structures that reference pointers, like arrays of CK_ATTRIBUTEs and
/// OSSL_PARAMs
#[derive(Debug)]
#[allow(dead_code)]
pub enum BorrowedReference<'a> {
    CharBool(&'a c_uchar),
    Int(&'a c_int),
    Slice(&'a [u8]),
    Vector(&'a Vec<u8>),
    Uint(&'a c_uint),
    Ulong(&'a c_ulong),
    Usize(&'a usize),
}

/// A safe builder and manager for OpenSSL `OSSL_PARAM` arrays.
///
/// `OSSL_PARAM` is the primary way to pass detailed parameters (like key
/// components, algorithm settings) to many OpenSSL 3+ EVP functions.
///
/// This struct handles memory management (including optional zeroization) and
/// lifetime complexities when constructing these arrays from Rust types.
#[derive(Debug)]
pub struct OsslParamBuilder<'a> {
    /// Storage for owned byte buffers backing some parameters.
    v: Vec<Vec<u8>>,
    /// The actual `OSSL_PARAM` array, potentially borrowed or owned.
    p: Cow<'a, [OSSL_PARAM]>,
    /// Flag indicating the storage buffer should be zeroized on drop
    zeroize: bool,
    /// Flag indicating `p` contains an owned pointer we are responsible
    /// for freeing
    freeptr: bool,
    /// Use an enum to hold references to data we need to keep around as
    /// a pointer to their data is stored in the OSSL_PARAM array
    br: Vec<BorrowedReference<'a>>,
}

/// A finalized OpenSSL `OSSL_PARAM` array.
#[derive(Debug)]
pub struct OsslParam<'a>(OsslParamBuilder<'a>);

impl Drop for OsslParamBuilder<'_> {
    fn drop(&mut self) {
        if self.freeptr {
            #[cfg(param_clear_free)]
            unsafe {
                OSSL_PARAM_clear_free(
                    self.p.as_ref().as_ptr() as *mut OSSL_PARAM
                );
            }
            #[cfg(not(param_clear_free))]
            unsafe {
                OSSL_PARAM_free(self.p.as_ref().as_ptr() as *mut OSSL_PARAM);
            }
        }
        if self.zeroize {
            while let Some(mut v) = self.v.pop() {
                unsafe {
                    OPENSSL_cleanse(void_ptr!(v.as_mut_ptr()), v.len());
                }
            }
        }
    }
}

impl<'a> OsslParamBuilder<'a> {
    /// Creates a new, empty `OsslParam` builder.
    #[allow(dead_code)]
    pub fn new() -> OsslParamBuilder<'a> {
        Self::with_capacity(0)
    }

    /// Creates a new, empty `OsslParam` builder with a specific initial
    /// capacity.
    pub fn with_capacity(capacity: usize) -> OsslParamBuilder<'a> {
        OsslParamBuilder {
            v: Vec::new(),
            p: Cow::Owned(Vec::with_capacity(capacity + 1)),
            zeroize: false,
            freeptr: false,
            br: Vec::new(),
        }
    }

    /// Adds a BIGNUM parameter from a big-endian byte slice.
    ///
    /// Handles the necessary conversions for OpenSSL's native-endian BIGNUM
    /// representation within `OSSL_PARAM`.
    pub fn add_bn(&mut self, key: &CStr, v: &[u8]) -> Result<(), Error> {
        /* need to go through all these functions because,
         * BN_bin2bn() takes a Big Endian number,
         * but BN_bn2nativepad() later will convert it to
         * native endianness, ensuring the buffer we pass in
         * is in the correct order for openssl ...
         */
        let bn = BigNum::from_bigendian_slice(v)?;
        let container = bn.to_native_vec()?;
        let param = unsafe {
            OSSL_PARAM_construct_BN(
                key.as_ptr(),
                void_ptr!(container.as_ptr()) as *mut u8,
                container.len(),
            )
        };
        self.v.push(container);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds a UTF-8 string parameter using a borrowed byte vector reference.
    #[allow(dead_code)]
    pub fn add_utf8_string(
        &mut self,
        key: &CStr,
        v: &'a Vec<u8>,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_utf8_string(
                key.as_ptr(),
                void_ptr!(v.as_ptr()) as *mut c_char,
                0,
            )
        };
        self.p.to_mut().push(param);
        self.br.push(BorrowedReference::Vector(v));
        Ok(())
    }

    /// Adds a UTF-8 string parameter using an owned byte vector.
    #[allow(dead_code)]
    pub fn add_owned_utf8_string(
        &mut self,
        key: &CStr,
        mut v: Vec<u8>,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_utf8_string(
                key.as_ptr(),
                void_ptr!(v.as_mut_ptr()) as *mut c_char,
                0,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds an empty sized string to receive values from queries like
    /// get_params()
    pub fn add_empty_utf8_string(
        &mut self,
        key: &CStr,
        len: usize,
    ) -> Result<(), Error> {
        let mut v = vec![0u8; len];
        let param = unsafe {
            OSSL_PARAM_construct_utf8_string(
                key.as_ptr(),
                void_ptr!(v.as_mut_ptr()) as *mut c_char,
                len,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds a UTF-8 string parameter using a borrowed C string pointer.
    ///
    /// Assumes `key` and `val` point to valid, null-terminated C strings.
    /// The caller must ensure their lifetimes exceed the `OsslParam`'s usage.
    ///
    /// Should only be used with actual const strings.
    #[allow(dead_code)]
    pub fn add_const_c_string(
        &mut self,
        key: &CStr,
        val: &CStr,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_utf8_string(
                key.as_ptr(),
                val.as_ptr() as *mut c_char,
                0,
            )
        };
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds an octet string (byte array) parameter using a borrowed byte
    /// vector reference.
    ///
    /// The caller must ensure their lifetimes exceed the `OsslParam`'s usage.
    #[allow(dead_code)]
    pub fn add_octet_string(
        &mut self,
        key: &CStr,
        v: &'a Vec<u8>,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_octet_string(
                key.as_ptr(),
                void_ptr!(v.as_ptr()),
                v.len(),
            )
        };
        self.p.to_mut().push(param);
        self.br.push(BorrowedReference::Vector(v));
        Ok(())
    }

    pub fn add_octet_slice(
        &mut self,
        key: &CStr,
        s: &'a [u8],
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_octet_string(
                key.as_ptr(),
                void_ptr!(s.as_ptr()),
                s.len(),
            )
        };
        self.p.to_mut().push(param);
        self.br.push(BorrowedReference::Slice(s));
        Ok(())
    }

    /// Adds an octet string (byte array) parameter using an owned byte vector.
    #[allow(dead_code)]
    pub fn add_owned_octet_string(
        &mut self,
        key: &CStr,
        v: Vec<u8>,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_octet_string(
                key.as_ptr(),
                void_ptr!(v.as_ptr()),
                v.len(),
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds a `size_t` parameter using a borrowed reference.
    ///
    /// The caller must ensure the lifetime of `val` exceeds the `OsslParam`'s
    /// usage.
    #[allow(dead_code)]
    pub fn add_size_t(
        &mut self,
        key: &CStr,
        val: &'a usize,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_size_t(
                key.as_ptr(),
                val as *const _ as *mut usize,
            )
        };
        self.p.to_mut().push(param);
        self.br.push(BorrowedReference::Usize(val));
        Ok(())
    }

    /// Adds a `c_uint` parameter using a borrowed reference.
    ///
    /// The caller must ensure the lifetime of `val` exceeds the `OsslParam`'s
    /// usage.
    #[allow(dead_code)]
    pub fn add_uint(
        &mut self,
        key: &CStr,
        val: &'a c_uint,
    ) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_uint(
                key.as_ptr(),
                val as *const _ as *mut c_uint,
            )
        };
        self.p.to_mut().push(param);
        self.br.push(BorrowedReference::Uint(val));
        Ok(())
    }

    /// Adds a `c_int` parameter using a borrowed reference.
    ///
    /// The caller must ensure the lifetime of `val` exceeds the `OsslParam`'s
    /// usage.
    #[allow(dead_code)]
    pub fn add_int(&mut self, key: &CStr, val: &'a c_int) -> Result<(), Error> {
        let param = unsafe {
            OSSL_PARAM_construct_int(
                key.as_ptr(),
                val as *const _ as *mut c_int,
            )
        };
        self.p.to_mut().push(param);
        self.br.push(BorrowedReference::Int(val));
        Ok(())
    }

    /// Adds an `usize` parameter using an owned value.
    pub fn add_owned_size_t(
        &mut self,
        key: &CStr,
        val: usize,
    ) -> Result<(), Error> {
        let v = val.to_ne_bytes().to_vec();

        let param = unsafe {
            OSSL_PARAM_construct_size_t(
                key.as_ptr(),
                v.as_ptr() as *const _ as *mut usize,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds an `c_uint` parameter using an owned value.
    pub fn add_owned_uint(
        &mut self,
        key: &CStr,
        val: c_uint,
    ) -> Result<(), Error> {
        let v = val.to_ne_bytes().to_vec();

        let param = unsafe {
            OSSL_PARAM_construct_uint(
                key.as_ptr(),
                v.as_ptr() as *const _ as *mut c_uint,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds an `c_int` parameter using an owned value.
    pub fn add_owned_int(
        &mut self,
        key: &CStr,
        val: c_int,
    ) -> Result<(), Error> {
        let v = val.to_ne_bytes().to_vec();

        let param = unsafe {
            OSSL_PARAM_construct_int(
                key.as_ptr(),
                v.as_ptr() as *const _ as *mut c_int,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds an `i64` parameter using an owned value.
    pub fn add_owned_i64(&mut self, key: &CStr, val: i64) -> Result<(), Error> {
        let v = val.to_ne_bytes().to_vec();
        let param = unsafe {
            OSSL_PARAM_construct_int64(
                key.as_ptr(),
                v.as_ptr() as *const _ as *mut i64,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Adds a `u64` parameter using an owned value.
    pub fn add_owned_u64(&mut self, key: &CStr, val: u64) -> Result<(), Error> {
        let v = val.to_ne_bytes().to_vec();
        let param = unsafe {
            OSSL_PARAM_construct_uint64(
                key.as_ptr(),
                v.as_ptr() as *const _ as *mut u64,
            )
        };
        self.v.push(v);
        self.p.to_mut().push(param);
        Ok(())
    }

    /// Copies an existing `OSSL_PARAM` array into the builder.
    ///
    /// This method performs a deep copy of each parameter, including its key
    /// and data. This is useful for persisting parameters that may have been
    /// returned by OpenSSL and point to temporary internal buffers.
    pub fn copy_params(&mut self, params: &[OSSL_PARAM]) -> Result<(), Error> {
        for p in params {
            if p.data.is_null() {
                return Err(Error::new(ErrorKind::NullPtr));
            }
            let k = unsafe { CStr::from_ptr(p.key as *const c_char) };
            let key = k.to_bytes_with_nul().to_vec();
            let data_size = if p.data_type == OSSL_PARAM_UTF8_STRING {
                /* For OSSL_PARAM_UTF8_STRING, OpenSSL gives the strlen() as
                 * size instead of the actual allocated size which include the
                 * terminating zero byte */
                p.data_size + 1
            } else {
                p.data_size
            };
            let val = unsafe {
                std::slice::from_raw_parts(p.data as *const u8, data_size)
            }
            .to_vec();
            let param = OSSL_PARAM {
                key: key.as_ptr() as *const c_char,
                data_type: p.data_type,
                data: val.as_ptr() as *mut c_void,
                data_size: p.data_size,
                return_size: 0,
            };
            self.v.push(key);
            self.v.push(val);
            self.p.to_mut().push(param);
        }
        Ok(())
    }

    /// Finalizes the `OSSL_PARAM` array by adding the end marker.
    pub fn finalize(mut self) -> OsslParam<'a> {
        self.p.to_mut().push(unsafe { OSSL_PARAM_construct_end() });
        OsslParam(self)
    }
}

impl<'a> OsslParam<'a> {
    /// Creates an empty, finalized `OsslParam` array (contains only the end
    /// marker).
    #[allow(dead_code)]
    pub fn empty() -> OsslParam<'static> {
        let p = OsslParamBuilder {
            v: Vec::new(),
            p: Cow::Owned(Vec::with_capacity(1)),
            zeroize: false,
            freeptr: false,
            br: Vec::new(),
        };
        p.finalize()
    }

    /// Creates an `OsslParam` instance by borrowing an existing `OSSL_PARAM`
    /// array from OpenSSL. Takes ownership of the pointer and marks it to be
    /// freed on drop.
    pub fn from_ptr(ptr: *mut OSSL_PARAM) -> Result<OsslParam<'static>, Error> {
        if ptr.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        /* get num of elements */
        let mut nelem = 0;
        let mut counter = ptr;
        unsafe {
            while !(*counter).key.is_null() {
                nelem += 1;
                counter = counter.offset(1);
            }
        }
        /* Mark as finalized as no changes are allowed to imported params */
        Ok(OsslParam(OsslParamBuilder {
            v: Vec::new(),
            p: Cow::Borrowed(unsafe {
                std::slice::from_raw_parts(ptr, nelem + 1)
            }),
            zeroize: false,
            freeptr: true,
            br: Vec::new(),
        }))
    }

    /// Returns a const pointer to the finalized `OSSL_PARAM` array.
    #[allow(dead_code)]
    pub fn as_ptr(&self) -> *const OSSL_PARAM {
        self.0.p.as_ref().as_ptr()
    }

    /// Returns a mutable pointer to the finalized `OSSL_PARAM` array.
    #[allow(dead_code)]
    pub fn as_mut_ptr(&mut self) -> *mut OSSL_PARAM {
        self.0.p.to_mut().as_mut_ptr()
    }

    /// Internal functions to convert an immutable reference to a mutable
    /// pointer. This is only used for interfaces that bindgen automatically
    /// mark as mutable but we know the interface contract means the pointer
    /// is effectively a const.
    unsafe fn int_mut_ptr(&self) -> *mut OSSL_PARAM {
        self.as_ptr() as *mut OSSL_PARAM
    }

    /// Gets the value of an integer parameter by its key name.
    #[allow(dead_code)]
    pub fn get_int(&self, key: &CStr) -> Result<c_int, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut val: c_int = 0;
        let res = unsafe { OSSL_PARAM_get_int(p, &mut val) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_int()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(val)
    }

    /// Gets the value of an unsigend integer parameter by its key name.
    pub fn get_uint(&self, key: &CStr) -> Result<c_uint, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut val: c_uint = 0;
        let res = unsafe { OSSL_PARAM_get_uint(p, &mut val) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_uint()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(val)
    }

    /// Gets the value of a size_t parameter by its key name.
    pub fn get_size_t(&self, key: &CStr) -> Result<usize, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut val: usize = 0;
        let res = unsafe { OSSL_PARAM_get_size_t(p, &mut val) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_size_t()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(val)
    }

    /// Gets the value of an i64 parameter by its key name.
    pub fn get_i64(&self, key: &CStr) -> Result<i64, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut val: i64 = 0;
        let res = unsafe { OSSL_PARAM_get_int64(p, &mut val) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_int64()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(val)
    }

    /// Gets the value of a u64 parameter by its key name.
    pub fn get_u64(&self, key: &CStr) -> Result<u64, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut val: u64 = 0;
        let res = unsafe { OSSL_PARAM_get_uint64(p, &mut val) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_uint64()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(val)
    }

    /// Gets the value of a long parameter by its key name.
    #[allow(dead_code)]
    pub fn get_long(&self, key: &CStr) -> Result<c_long, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut val: c_long = 0;
        let res = unsafe { OSSL_PARAM_get_long(p, &mut val) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_long()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(val)
    }

    /// Gets the value of a BIGNUM parameter by its key name as a big-endian
    /// byte vector.
    pub fn get_bn(&self, key: &CStr) -> Result<Vec<u8>, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let bn = BigNum::from_param(p)?;
        bn.to_bigendian_vec()
    }

    /// Gets the value of an octet string parameter by its key name as a byte
    /// slice.
    #[allow(dead_code)]
    pub fn get_octet_string(&self, key: &CStr) -> Result<&'a [u8], Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut buf: *const c_void = std::ptr::null_mut();
        let mut buf_len: usize = 0;
        let res = unsafe {
            OSSL_PARAM_get_octet_string_ptr(p, &mut buf, &mut buf_len)
        };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_octet_string_ptr()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        let octet =
            unsafe { std::slice::from_raw_parts(buf as *const u8, buf_len) };
        Ok(octet)
    }

    /// Gets a UTF8 String as a &CStr
    #[allow(dead_code)]
    pub fn get_utf8_string(&self, key: &CStr) -> Result<&'a CStr, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        if p.is_null() {
            return Err(Error::new(ErrorKind::NullPtr));
        }
        let mut ptr: *const c_char = std::ptr::null_mut();
        let res = unsafe { OSSL_PARAM_get_utf8_string_ptr(p, &mut ptr) };
        if res != 1 {
            trace_ossl!("OSSL_PARAM_get_utf8_string_ptr()");
            return Err(Error::new(ErrorKind::OsslError));
        }
        Ok(unsafe { CStr::from_ptr(ptr) })
    }

    /// Checks if a parameter with the given key name exists in the array.
    #[allow(dead_code)]
    pub fn has_param(&self, key: &CStr) -> Result<bool, Error> {
        let p = unsafe { OSSL_PARAM_locate(self.int_mut_ptr(), key.as_ptr()) };
        Ok(!p.is_null())
    }

    /// Returns the number of elements in the array, excluding the terminating
    /// null element
    #[allow(dead_code)]
    pub fn len(&self) -> usize {
        self.0.p.as_ref().len() - 1
    }
}

/// A container for sensitive data that is securely zeroized on drop.
///
/// `OsslSecret` provides a fixed-size, vector-like interface to a byte buffer.
/// It is designed to hold cryptographic keys, passwords, or any other secret
/// material that should not persist in memory longer than necessary.
///
/// Once created, the capacity of an `OsslSecret` cannot be changed, preventing
/// accidental reallocations that might leave copies of the secret data in
/// unmanaged memory.
#[derive(Debug)]
pub struct OsslSecret {
    data: Vec<u8>,
}

impl OsslSecret {
    /// Creates a new `OsslSecret` of a specified size, initialized with zeros.
    ///
    /// The buffer is allocated with a fixed capacity and cannot be resized.
    pub fn new(size: usize) -> OsslSecret {
        OsslSecret {
            data: vec![0u8; size],
        }
    }

    /// Creates a new `OsslSecret` by copying data from a slice.
    ///
    /// The new buffer will have a fixed capacity equal to the length of the slice.
    pub fn from_slice(secret: &[u8]) -> OsslSecret {
        OsslSecret {
            data: secret.to_vec(),
        }
    }

    /// Creates a new `OsslSecret` from an existing vector.
    ///
    /// This method takes ownership of the provided `Vec<u8>`, avoiding an
    /// unnecessary copy of the secret data.
    pub fn from_vec(secret: Vec<u8>) -> OsslSecret {
        OsslSecret { data: secret }
    }

    /// Returns the number of bytes in the secret.
    pub fn len(&self) -> usize {
        self.data.len()
    }

    /// Returns `true` if the secret has a length of 0.
    #[allow(dead_code)]
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    /// Returns a raw pointer to the secret's buffer.
    ///
    /// The caller must ensure that the `OsslSecret` outlives the pointer.
    pub fn as_ptr(&self) -> *const u8 {
        self.data.as_ptr()
    }

    /// Returns a raw, mutable pointer to the secret's buffer.
    ///
    /// The caller must ensure that the `OsslSecret` outlives the pointer.
    pub fn as_mut_ptr(&mut self) -> *mut u8 {
        self.data.as_mut_ptr()
    }

    /// Reduces the size of the secret, securely clearing the discarded portion.
    ///
    /// It copies `new_size` bytes starting from `starting_point` from the
    /// original secret into a new, smaller buffer. The original buffer is
    /// then securely cleared.
    ///
    /// # Arguments
    ///
    /// * `new_size` - The new size of the secret.
    /// * `starting_point` - The offset within the original secret to start
    ///   copying from.
    ///
    /// # Errors
    ///
    /// Returns `ErrorKind::BufferSize` if the range specified by
    /// `starting_point` and `new_size` is out of bounds of the current
    /// secret's length.
    pub fn reduce(
        &mut self,
        new_size: usize,
        starting_point: usize,
    ) -> Result<(), Error> {
        let end_point = match starting_point.checked_add(new_size) {
            Some(end) => end,
            None => return Err(Error::new(ErrorKind::BufferSize)),
        };

        if end_point > self.len() {
            return Err(Error::new(ErrorKind::BufferSize));
        }

        // no-op if the requested slice is the same as the current data
        if new_size == self.len() && starting_point == 0 {
            return Ok(());
        }

        let mut new_data = vec![0u8; new_size];
        new_data.copy_from_slice(&self.data[starting_point..end_point]);

        unsafe {
            OPENSSL_cleanse(void_ptr!(self.data.as_mut_ptr()), self.data.len());
        }

        std::mem::swap(&mut self.data, &mut new_data);

        Ok(())
    }
}

impl Drop for OsslSecret {
    /// Securely zeroizes the secret data when the object is dropped.
    fn drop(&mut self) {
        unsafe {
            OPENSSL_cleanse(void_ptr!(self.data.as_mut_ptr()), self.data.len());
        }
    }
}

impl AsRef<[u8]> for OsslSecret {
    fn as_ref(&self) -> &[u8] {
        &self.data
    }
}

impl AsMut<[u8]> for OsslSecret {
    fn as_mut(&mut self) -> &mut [u8] {
        &mut self.data
    }
}

impl std::ops::Deref for OsslSecret {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        &self.data
    }
}

impl std::ops::DerefMut for OsslSecret {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.data
    }
}

#[cfg(test)]
mod tests;
