/*-
 * Scram-rs - a SCRAM authentification authorization library
 * 
 * Copyright (C) 2021  Aleksandr Morozov
 * Copyright (C) 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

 
use core::{ffi::c_void, ptr::null};
#[cfg(feature = "std")]
use std::ffi::CString;

#[cfg(not(feature = "std"))]
use core::num::NonZeroUsize;

#[cfg(feature = "std")]
use std::num::NonZeroUsize;

#[cfg(not(feature = "std"))]
use alloc::ffi::CString;


#[cfg(feature = "std")]
use std::ffi::c_char;
#[cfg(not(feature = "std"))]
use core::ffi::c_char;


#[cfg(feature = "std")]
use std::str;
#[cfg(not(feature = "std"))]
use core::str;


#[cfg(not(feature = "std"))]
use alloc::boxed::Box;

#[cfg(feature = "use_ring")]
use crate::{ScramSha1Ring, ScramSha256Ring, ScramSha512Ring};

use crate::
{
    SCRAM_TYPES, ScramAuthServer, ScramCbHelper, ScramErrorCode, ScramNonce, ScramPassword, ScramResult, ScramResultServer, ScramServerDyn, ScramServerError, ScramSha1RustNative, ScramSha3_512RustNative, ScramSha256RustNative, ScramSha512RustNative, ScramTypeAlias, capi_scram_err, capi_scram_err_map, scram_sync::SyncScramServer
};

use super::
{
    common, 
    error::{CApiScramResult, CApiScramRuntimeError}, 
    nonce::CApiNonce, 
    CApiScramHasherProvider, 
    CApiScramTypeAlias
};

const SERVER_MARKER: u64 = 0xc1FFEECC_9ABCDEF0;

/// An alias. Should return the Box<ScramPassword> as pointer
type FuncPasswordForUser = 
    unsafe extern "C" 
    fn(auth_db: *const c_void, hash_prov: CApiScramHasherProvider, hash_type: CApiScramTypeAlias, 
        username: *const c_char, authzid: *const c_char) -> *mut ScramResult<*mut ScramPassword>;

#[derive(Debug)]
struct AuthDB
{
    /// A pointer to the C auth DB instance
    auth_db_inst: *const c_void,

    /// A pointer to function which handles search by username.
    func_password_for_user: FuncPasswordForUser,
}

impl AuthDB
{
    pub 
    fn new(auth_db_inst: *const c_void, func_password_for_user: Option<FuncPasswordForUser>) -> CApiScramResult<Self>
    {
        if auth_db_inst.is_null() == true
        {
            capi_scram_err!(ScramErrorCode::InternalError, ScramServerError::None, "auth_db_inst is null")
        }
        else if func_password_for_user.is_none() == true
        {
            capi_scram_err!(ScramErrorCode::InternalError, ScramServerError::None, "func_password_for_user is null")
        }

        return Ok(
            AuthDB
            {
                auth_db_inst: auth_db_inst,
                func_password_for_user: func_password_for_user.unwrap()
            }
        );
    }
}



#[cfg(not(feature = "exclude_sha1"))]
impl ScramAuthServer<ScramSha1RustNative> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, 
                    CApiScramHasherProvider::RustNative, CApiScramTypeAlias::Sha1, 
                    c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }   
    }
}


impl ScramAuthServer<ScramSha256RustNative> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, CApiScramHasherProvider::RustNative, 
                    CApiScramTypeAlias::Sha256, c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }    
    }
}

impl ScramAuthServer<ScramSha512RustNative> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, CApiScramHasherProvider::RustNative, 
                    CApiScramTypeAlias::Sha512, c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }   
    }
}


impl ScramAuthServer<ScramSha3_512RustNative> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, CApiScramHasherProvider::RustNative, 
                    CApiScramTypeAlias::Sha3_512, c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }   
    }
}
#[cfg(all(feature = "use_ring", not(feature = "exclude_sha1")))]
impl ScramAuthServer<ScramSha1Ring> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, CApiScramHasherProvider::RustRing, 
                    CApiScramTypeAlias::Sha1, c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }    
    }
}

#[cfg(feature = "use_ring")]
impl ScramAuthServer<ScramSha256Ring> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, CApiScramHasherProvider::RustRing, 
                    CApiScramTypeAlias::Sha256, c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }   
    }
}

#[cfg(feature = "use_ring")]
impl ScramAuthServer<ScramSha512Ring> for AuthDB
{
    fn get_password_for_user(&self, username: &str, authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let c_username = CString::new(username).unwrap();
        let c_authzid = 
            authzid.map(|v| CString::new(v).unwrap());

        let scr_pas_res = 
            unsafe 
            {
                (self.func_password_for_user)(self.auth_db_inst, CApiScramHasherProvider::RustRing, 
                    CApiScramTypeAlias::Sha512, c_username.as_ptr(), c_authzid.map_or(null(), |f| f.as_ptr()))
            };

        match * unsafe { Box::from_raw(scr_pas_res) }
        {
            Ok(r) => 
                return Ok(* unsafe {Box::from_raw(r)}),
            Err(e) => 
                return Err(e)
        }     
    }
}


#[derive(Debug)]
struct AuthDBCb
{

}

impl AuthDBCb
{
    pub 
    fn new() -> Self
    {
        return Self{};
    }
}

impl ScramCbHelper for AuthDBCb
{
    
}


#[repr(C)]
pub struct CApiScramServer
{
    dyn_server: Box<dyn ScramServerDyn>,
    marker: u64,
}

impl CApiScramServer
{
    pub 
    fn new(server: Box<dyn ScramServerDyn>) -> *mut Self
    {
        let api_client = 
            CApiScramServer
            {
                dyn_server: server,
                marker: SERVER_MARKER,
            };

        return Box::into_raw(Box::new(api_client));
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_init(
    id: usize,
    hash_provider: CApiScramHasherProvider, 
    client_nonce: *mut CApiNonce,
    auth_db_inst: *const c_void,
    func_password_for_user: Option<FuncPasswordForUser>,
    scram_type: *const c_char,
    ignore_exts: bool,
    serv_out: *mut *mut CApiScramServer,
    error_out: *mut *mut CApiScramRuntimeError
) -> i32
{
    if serv_out.is_null() == true
    {
        return -8;
    }
    else if hash_provider.validate() == false
    {
        return -2;
    }

    let Some(nonzero_id) = NonZeroUsize::new(id)
        else
        {
            return -1;
        };

    let nonce = 
        if client_nonce.is_null() == true
        {
            match ScramNonce::none().map_err(|e| capi_scram_err_map!(e))
            {
                Ok(r) => r,
                Err(e) =>
                {
                    if error_out.is_null() == false
                    {
                        unsafe { *error_out.as_mut().unwrap() = e.into_capi() };
                    }

                    return 1;
                }
            }
        }
        else
        {
            match unsafe { client_nonce.as_mut().unwrap().take() }
            {
                Ok(r) => r,
                Err(e) =>
                {
                    if error_out.is_null() == false
                    {
                        unsafe { *error_out.as_mut().unwrap() = e.into_capi() };
                    }

                    return 1;
                }
            }
        };

    let authdb = 
        match AuthDB::new(auth_db_inst, func_password_for_user)
        {
            Ok(r) => r,
            Err(e) => 
            {
                unsafe 
                { 
                    if let Some(err) = error_out.as_mut()
                    {
                        *err = e.into_capi();
                    }
                }

                return 1;
            }
        };
    
    let authcb = AuthDBCb::new();

    let scram_type = 
        match common::cstr_to_string(scram_type, "scram_type is not valid")
        {
            Ok(r) => r,
            Err(e) => 
            {
                unsafe 
                { 
                    if let Some(err) = error_out.as_mut()
                    {
                        *err = e.into_capi();
                    }
                }

                return 1;
            }
        };

    let scramtype = 
        match SCRAM_TYPES.get_scramtype(scram_type).map_err(|e|capi_scram_err_map!(e))
        {
            Ok(r) => r,
            Err(e) => 
            {
                unsafe 
                { 
                    if let Some(err) = error_out.as_mut()
                    {
                        *err = e.into_capi();
                    }
                }

                return 1;
            }
        };



    let res = 
        match (hash_provider, scramtype.scram_alias)
        {
            #[cfg(not(feature = "exclude_sha1"))]
            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha1) =>
            {
                SyncScramServer
                    ::<ScramSha1RustNative, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },

            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha256 | ScramTypeAlias::Sha256Plus) =>
            {
                SyncScramServer
                    ::<ScramSha256RustNative, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },
            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha512 | ScramTypeAlias::Sha512Plus) =>
            {
                SyncScramServer
                    ::<ScramSha512RustNative, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },
            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha3_512 | ScramTypeAlias::Sha3_512Plus) =>
            {
                SyncScramServer
                    ::<ScramSha3_512RustNative, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },

            #[cfg(all(feature = "use_ring", not(feature = "exclude_sha1")))]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha1) =>
            {
                SyncScramServer
                    ::<ScramSha1Ring, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },

            #[cfg(all(not(feature = "use_ring"), not(feature = "exclude_sha1")))]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha1) =>
            {
                crate::capi_scram_err_res!(ScramErrorCode::FeatureNotSupported, ScramServerError::None, "Sha1Ring not available")
            },

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha256 | ScramTypeAlias::Sha256Plus) =>
            {            
                SyncScramServer
                    ::<ScramSha256Ring, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha256 | ScramTypeAlias::Sha256Plus) =>
            {
                crate::capi_scram_err_res!(ScramErrorCode::FeatureNotSupported, ScramServerError::None, "Sha256Ring not available")
            },

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha512 | ScramTypeAlias::Sha512Plus) =>
            {            
                SyncScramServer
                    ::<ScramSha512Ring, AuthDB, AuthDBCb>
                    ::new(authdb, authcb, nonce, scramtype, ignore_exts)
                        .map_err(|e|
                            capi_scram_err_map!(e)
                        )
                        .map(|c| c.make_dyn(nonzero_id))
            },

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha512 | ScramTypeAlias::Sha512Plus) =>
            {
                crate::capi_scram_err_res!(ScramErrorCode::FeatureNotSupported, ScramServerError::None, "Sha512Ring not available")
            },

            #[cfg(feature = "use_ring")]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha3_512 | ScramTypeAlias::Sha3_512Plus) =>
            {            
                crate::capi_scram_err_res!(ScramErrorCode::FeatureNotSupported, ScramServerError::None, "RING does not support SHA3")
            },

            #[cfg(not(feature = "use_ring"))]
            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha3_512 | ScramTypeAlias::Sha3_512Plus) =>
            {            
                crate::capi_scram_err_res!(ScramErrorCode::FeatureNotSupported, ScramServerError::None, "RING does not support SHA3")
            },

        };

    if res.is_ok() == true
    {
        unsafe { *serv_out.as_mut().unwrap() = CApiScramServer::new(res.ok().unwrap()) };

        return 0;
    }
    else
    {
        unsafe 
        { 
            if let Some(err) = error_out.as_mut()
            {
                *err = res.err().unwrap().into_capi();
            }
        }

        return 1;
    };
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_free(server: *mut CApiScramServer)
{
    if server.is_null() == true
    {
        return;
    }

    let marker = unsafe { server.as_ref().unwrap().marker };
    if marker != SERVER_MARKER
    {
        panic!("Assertion trap: invalid server marker: {:X} {:X}", marker, SERVER_MARKER);
    }

    drop(unsafe { Box::from_raw(server) });

    return;
}


#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_get_username(server: *mut CApiScramServer, username: *mut *const c_char) -> i32
{
    if server.is_null() == true
    {
        return -1;
    }
    else if username.is_null() == true
    {
        return -2;
    }

    let username_opt = 
        unsafe 
        { 
            server
                .as_mut()
                .unwrap()
                .dyn_server
                .get_auth_username()
        };

    match username_opt
    {
        Some(u) =>
        {
            let c_username = CString::new(u.as_bytes()).unwrap().into_raw();

            unsafe { *username.as_mut().unwrap() = c_username };

            return 0;
        },
        None =>
            return 1
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_get_username_free(username: *mut c_char)
{
    if username.is_null() == true
    {
        return;
    }

    let _ = unsafe { CString::from_raw(username) };

    return;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_parse_resp_base64(server: *mut CApiScramServer, resp_b64: *const u8, resp_len: usize, 
    serv_res: *mut *mut CApiScramResultServer, error_out: *mut *mut CApiScramRuntimeError) -> i32
{
    if server.is_null() == true
    {
        return -1;
    }
    else if serv_res.is_null() == true
    {
        return -4;
    }

    let resp = 
        match common::carr_to_slice(resp_b64, resp_len, "resp_b64 is not valid")
        {
            Ok(r) => r,
            Err(e) => 
            {
                unsafe 
                { 
                    if let Some(err) = error_out.as_mut()
                    {
                        *err = e.into_capi();
                    }
                }

                return 1;
            }
        };

    let msg: CApiScramResultServer = 
        unsafe 
        { 
            server
                .as_mut()
                .unwrap()
                .dyn_server
                .parse_response_base64(resp)
                .into()
        };

    unsafe { *serv_res.as_mut().unwrap() = Box::into_raw(Box::new(msg)) };

    return 0;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_parse_resp_plain(server: *mut CApiScramServer, resp_plain: *const c_char, 
    serv_res: *mut *mut CApiScramResultServer, error_out: *mut *mut CApiScramRuntimeError) -> i32
{
    if server.is_null() == true
    {
        return -1;
    }
    else if serv_res.is_null() == true
    {
        return -3;
    }

    let resp = 
        match common::cstr_to_string(resp_plain, "resp_plain is not valid")
        {
            Ok(r) => r,
            Err(e) => 
            {
                unsafe 
                { 
                    if let Some(err) = error_out.as_mut()
                    {
                        *err = e.into_capi();
                    }
                }

                return 1;
            }
        };

    let msg: CApiScramResultServer = 
        unsafe 
        { 
            server
                .as_mut()
                .unwrap()
                .dyn_server
                .parse_response(resp.as_str())
                .into()
        };

    unsafe { *serv_res.as_mut().unwrap() = Box::into_raw(Box::new(msg)) };

    return 0;
}


#[repr(C)]
pub enum CApiScramResultType
{
    Data = 0,
    Error = 1,
    Final = 2,
}

/// Should be deallocated using capi_scram_server_result_free (C)
#[repr(C)]
pub struct CApiScramResultServer
{
    res: ScramResultServer,

    plain: Option<CString>,
    base64: Option<CString>,
}

impl From<ScramResultServer> for CApiScramResultServer
{
    fn from(value: ScramResultServer) -> Self 
    {
        return Self{ res: value, plain: None, base64: None };
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_result_free(resp: *mut CApiScramResultServer)
{
    if resp.is_null() == true
    {
        return;
    }

    drop(unsafe { Box::from_raw(resp) });

    return;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_result_type(resp: *mut CApiScramResultServer, resp_type: *mut CApiScramResultType) -> i32
{
    if resp.is_null() == true
    {
        return -1;
    }
    else if resp_type.is_null() == true
    {
        return -2;
    }

    unsafe 
    { 
        *resp_type.as_mut().unwrap() =
            match resp.as_ref().unwrap().res
            {
                ScramResultServer::Data(_) => 
                    CApiScramResultType::Data,
                ScramResultServer::Error(_) => 
                    CApiScramResultType::Error,
                ScramResultServer::Final(_) => 
                    CApiScramResultType::Final,
            };
    }

    return 0;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_result_base64(msg_res: *mut CApiScramResultServer, msg_base64_out: *mut *const c_char,
    err_out: *mut *const CApiScramRuntimeError) -> i32
{
    if msg_res.is_null() == true
    {
        return -1;
    }
    else if msg_base64_out.is_null() == true
    {
        return -2;
    }

    let this = unsafe { msg_res.as_mut().unwrap() };

    let msg = 
        if let Some(base64) = this.base64.as_ref()
        {
            base64.as_ptr()
        }
        else
        {
            let base64c = 
                CString::new(this.res.encode_base64().as_bytes())
                    .unwrap();

            if this.res.is_err() == true
            {
                if err_out.is_null() == false
                {
                    unsafe 
                    { 
                        *err_out.as_mut().unwrap() = 
                            capi_scram_err_map!(this.res.get_err().unwrap().clone()).into_capi();
                    }
                }

            }

            let base64c_ret = base64c.as_ptr();

            this.base64.replace(base64c);

            base64c_ret
        };
        

        unsafe { *msg_base64_out = msg };
    
    return 0;
}


#[unsafe(no_mangle)]
pub unsafe extern "C" 
fn capi_scram_server_result_plain(msg_res: *mut CApiScramResultServer, msg_plain_out: *mut *const c_char,
    err_out: *mut *const CApiScramRuntimeError) -> i32
{
    if msg_res.is_null() == true
    {
        return -1;
    }
    else if msg_plain_out.is_null() == true
    {
        return -2;
    }

    let this = unsafe { msg_res.as_mut().unwrap()};

    let msg = 
        if let Some(plain) = this.plain.as_ref()
        {
            plain.as_ptr()
        }
        else
        {
            let plainc = 
                CString::new(this.res.get_raw_output().as_bytes())
                    .unwrap();

            if this.res.is_err() == true
            {
                if err_out.is_null() == false
                {
                    unsafe 
                    { 
                        *err_out.as_mut().unwrap() = 
                            capi_scram_err_map!(this.res.get_err().unwrap().clone()).into_capi();
                    }
                }

            }

            let plainc_ret = plainc.as_ptr();

            this.base64.replace(plainc);

            plainc_ret
        };
        

    unsafe { *msg_plain_out = msg };
    
    return 0;
}
