/*-
 * 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
 */

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

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

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

#[cfg(not(feature = "std"))]
use alloc::string::String;

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

#[cfg(not(feature = "std"))]
use crate::alloc::string::ToString;

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

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

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

#[cfg(feature = "std")]
use std::borrow::Borrow;

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

#[cfg(feature = "std")]
use std::hash::Hash;

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

#[cfg(feature = "std")]
use std::hash::Hasher;

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

#[cfg(feature = "std")]
use std::cmp::Ordering;



use async_trait::async_trait;
use base64::Engine;
use base64::engine::general_purpose;

use crate::
{
    ChannelBindType, 
    ScramCommon, 
    ScramErrorCode, 
    ScramHashing, 
    ScramKey, 
    ScramNonce, 
    ScramPassword, 
    ScramResult, 
    ScramResultClient, 
    ScramResultServer, 
    ScramServerError, 
    ScramType, 
    scram_error, 
    scram_error_map, 
    scram_ierror, 
    scram_ierror_map, 
    scram_parser::ScramData, 
    scram_state::ScramState
};

use super::{AsyncScramAuthClient, AsyncScramAuthServer, AsyncScramCbHelper, AsyncScramServerDyn};

/// # A async Scram Server. 
/// 
/// Should be used on the server side. Can be coverted into boxed `dyn` object.
/// 
/// ## Generics
/// 
/// * `S` - [ScramHashing] A hashing algorith which will be used. This crate 
///     does not provides a methods for hashing algorith negoatiations. It 
///     should be known prior to constructiong the instance.
/// 
/// * `A` - [ScramAuthServer] a credentials storage.
/// 
/// * `B` - [ScramCbHelper] a channel binding helper.
/// 
/// ```text
/// If it is required to support `channel binding`, the crate does not provide
/// a helpers to obtain required information. You must find out how to extract 
/// endpoint certificate from TLS connection.
///     i.e native_tls::TlsStream::tls_server_end_point()  
/// ```
#[derive(Debug)]
pub struct AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send, 
    A: AsyncScramAuthServer<S> + Sync + Send, 
    B: AsyncScramCbHelper + Sync + Send
{
    ///  Instance ID number
    id: usize,

    /// The hasher which will be used: SHA-1 SHA-256
    hasher: PhantomData<S>,

    /// The Auth backend which handles user search
    auth: A,

    /// The current instance type
    st: &'static ScramType,

    /// username n=
    username: Option<String>,

    /// authzid a=
    authzid: Option<String>,

    /// Returned password with status found/notfound
    sp: ScramPassword,

    /// Generated server nonce
    server_nonce: String,

    /// Current scram state
    state: ScramState,
    
    /// Received channel binding opt from client
    cli_chanbind: ChannelBindType,

    /// A callback to support channel bind mechanism
    chanbind_helper: B,

    /// Ignores the extensions which may appear after the base message items.
    ignore_exts: bool,
}

impl<'ss, S, A, B> Eq 
for  AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send + 'ss, 
    A: AsyncScramAuthServer<S> + Sync + Send +'ss, 
    B: AsyncScramCbHelper + Sync + Send + 'ss
{

}

impl<'ss, S, A, B> PartialEq 
for  AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send + 'ss, 
    A: AsyncScramAuthServer<S> + Sync + Send +'ss, 
    B: AsyncScramCbHelper + Sync + Send + 'ss
{
    fn eq(&self, other: &Self) -> bool 
    {
        return self.id == other.id;
    }
}

impl<'ss, S, A, B> Ord 
for  AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send + 'ss, 
    A: AsyncScramAuthServer<S> + Sync + Send +'ss, 
    B: AsyncScramCbHelper + Sync + Send + 'ss
{
    fn cmp(&self, other: &Self) -> Ordering 
    {
        return self.id.cmp(&other.id);
    }
}

impl<'ss, S, A, B> PartialOrd 
for  AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send + 'ss, 
    A: AsyncScramAuthServer<S> + Sync + Send +'ss, 
    B: AsyncScramCbHelper + Sync + Send + 'ss
{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> 
    {
        return Some(self.cmp(other));
    }
}

impl<'ss, S, A, B> Borrow<usize> 
for  AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send + 'ss, 
    A: AsyncScramAuthServer<S> + Sync + Send +'ss, 
    B: AsyncScramCbHelper + Sync + Send + 'ss
{
    fn borrow(&self) -> &usize 
    {
        return &self.id;
    }
}

impl<'ss> Eq for Box<dyn AsyncScramServerDyn + 'ss>
{

}

impl<'ss> PartialEq for Box<dyn AsyncScramServerDyn + 'ss>
{
    fn eq(&self, other: &Self) -> bool 
    {
        return self.as_ref().borrow() == other.as_ref().borrow();
    }
}

impl<'ss> PartialEq<usize> for Box<dyn AsyncScramServerDyn + 'ss>
{
    fn eq(&self, other: &usize) -> bool 
    {
        return self.as_ref().borrow() == other;
    }
}

impl<'ss> Ord for Box<dyn AsyncScramServerDyn + 'ss>
{
    fn cmp(&self, other: &Self) -> Ordering 
    {
        return self.as_ref().borrow().cmp(other.as_ref().borrow());
    }
}

impl<'ss> PartialOrd for Box<dyn AsyncScramServerDyn + 'ss>
{
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> 
    {
        return Some(self.cmp(other));
    }
}

impl<'ss> PartialOrd<usize> for Box<dyn AsyncScramServerDyn + 'ss>
{
    fn partial_cmp(&self, other: &usize) -> Option<Ordering> 
    {
        return Some(self.as_ref().borrow().cmp(other));
    }
}

impl<'ss> Hash for Box<dyn AsyncScramServerDyn + 'ss>
{
    fn hash<H: Hasher>(&self, state: &mut H) 
    {
        self.as_ref().borrow().hash(state);
    }
}

impl<'ss, S, A, B> AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Sync + Send + 'ss, 
    A: AsyncScramAuthServer<S> + Sync + Send +'ss, 
    B: AsyncScramCbHelper + Sync + Send + 'ss
{
    /// Consumes the instance converting it into the [AsyncScramServerDyn]. So this instance
    /// can be stored in the vector of elements.
    pub 
    fn make_dyn(mut self, id: NonZeroUsize) -> Box<dyn AsyncScramServerDyn + 'ss>
    {
        self.id = id.get();

        return Box::new(self);
    }
}

impl<S, A, B> AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send, A: AsyncScramAuthServer<S> + Sync + Send, B: AsyncScramCbHelper + Sync + Send
{
    /// Creates a new instance of the SyncScramServer.
    /// 
    /// # Arguments
    /// 
    /// * `scram_auth_serv` - A reference to the instance which implements
    /// [ScramAuthServer]
    /// 
    /// * `data_chanbind` - A channel binding data TLS Endpoint Cert Hash
    /// 
    /// * `chan_bind_helper` - An implemented trait [ScramCbHelper] which should provide 
    ///                 crate with all necessary data for channel bind.
    /// 
    /// * `scram_nonce` - A [ScramNonce] Scram Nonce type
    /// 
    /// * `st` - [ScramType] A type of the scram picked by name from table [super::scram_common::SCRAM_TYPES]
    /// 
    /// * `ignore_exts` - if set to `true` ignores the additional data which may appear after the 
    ///         protocol data (i.e extensions which are not supported). If set to false generates
    ///         an error if any unknown data presents after expected data.
    /// 
    /// # Examples
    /// 
    /// ```ignore
    /// let serv = AuthServer::new();
    /// let nonce = ScramNonce::Base64(server_nonce);
    ///
    /// let scramtype = ScramCommon::get_scramtype("SCRAM-SHA-256").unwrap();
    /// let scram_res = AsyncScramServer::<ScramSha256, AuthServer, AuthServer>::new(&serv, &serv, nonce, scramtype, false);
    /// ```
    pub 
    fn new(
        scram_auth_serv: A,
        chan_bind_helper: B,
        scram_nonce: ScramNonce, 
        st: &'static ScramType,
        ignore_exts: bool,
    ) -> ScramResult<AsyncScramServer<S, A, B>>
    {
        let res = 
            Self
            {
                id: 0,
                hasher: PhantomData,
                auth: scram_auth_serv,
                st: st,
                username: None,
                authzid: None,
                sp: ScramPassword::default(),
                server_nonce: scram_nonce.get_nonce(),
                state: ScramState::WaitForClientInitalMsg,
                cli_chanbind: ChannelBindType::n(),
                chanbind_helper: chan_bind_helper,
                ignore_exts: ignore_exts
            };

        return Ok(res);
    }


    /// Exposing the username privided by the client. Can be used to form a
    ///     log message. On early stages before client sends username, it is
    ///     not availabel.
    /// 
    /// # Returns
    /// 
    /// * [Option]
    ///     - `Some` with ref to username [String]
    ///     - `None` if not yet available
    pub 
    fn get_auth_username(&self) -> Option<&String>
    {
        return self.username.as_ref();
    }

    /// Decodes the input from base64 and performes parsing and result computation.  
    /// 
    /// # Arguments
    /// 
    /// * `input` - A response received from client.
    /// 
    /// # Returns
    /// 
    /// * A [ScramResultServer] is returned.
    pub async 
    fn parse_response_base64<T>(&mut self, input: T) -> ScramResultServer
    where T: AsRef<[u8]>
    {
        let decoded = 
            match general_purpose::STANDARD.decode(input)
                .map_err(|e| 
                    scram_error_map!(ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                        "base64 decoding of client response failed with error, '{}'", e)
                )
            {
                Ok(r) => r,
                Err(e) => return ScramResultServer::Error(e)
            };
                

        let dec_utf8 = 
            match str::from_utf8(&decoded)
                    .map_err(|e| 
                        scram_error_map!(
                            ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                            "base64 decoded response contains invalid UTF-8 seq, '{}'", e
                        )
                    )
            {
                Ok(r) => r,
                Err(e) => return ScramResultServer::Error(e)
            };

        return self.parse_response(dec_utf8).await;
    }

    /// Performes parsing of the response from client and result computation.  
    /// It is assumed that resp is UTF-8 valid sequences
    /// 
    /// # Arguments
    /// 
    /// * `resp` - A response from client as ref str.
    /// 
    /// * `to_base` - if set to true, will encode response into base64.
    /// 
    /// # Returns
    /// 
    /// * The [ScramResultServer] is returned.
    #[inline]
    pub async
    fn parse_response(&mut self, resp: &str) -> ScramResultServer
    {
        match self.parse_response_internal(resp).await
        {
            Ok(r) =>
            {
                if self.state == ScramState::Completed
                {
                    return ScramResultServer::Final(r);
                }
                else
                {
                    return ScramResultServer::Data(r);
                }
            },
            Err(e) =>
            {
                return ScramResultServer::Error(e);
            }
        }
    }
    
    async 
    fn parse_response_internal(&mut self, resp: &str) -> ScramResult<String>
    {
        let parsed_resp = ScramData::try_from((resp, &self.state, self.ignore_exts))?;

        match parsed_resp
        {
            // Initial message from client
            ScramData::CmsgInitial{chan_bind, user, authzid, nonce} => //todo
            {
                //channel bind test
                chan_bind.server_initial_verify_client_cb(self.st)?;

                //authID is not supported

                //get user
                let sp = 
                    self.auth.get_password_for_user(user, authzid).await?;

                /*if sp.is_ok() == false
                {
                    scram_error!(ScramErrorCode::ExternalError,
                        "authentification server failed for unknown reason");
                }*/

                // form output
                let output = 
                    [
                        "r=", nonce, &self.server_nonce, 
                        ",s=", sp.get_salt_base64(), 
                        ",i=", &sp.get_iterations().to_string()
                    ].concat();

                // update state
                self.cli_chanbind = chan_bind;
                self.username = Some(user.to_string());
                self.authzid = authzid.map(|v| v.to_string());
                self.sp = sp;
                self.state = ScramState::WaitForClientFinalMsg{client_nonce: String::from(nonce)};

                return Ok(output);
            },
            ScramData::SmsgInitial{..} => 
            {
                panic!("server can not handle state: {}", parsed_resp);
            },
            //final message from client (STEP 2)
            ScramData::CmsgFinalMessage{chanbinding, finalnonce, proof, client_nonce} =>
            {
                // verify channel bind
                self
                    .cli_chanbind
                    .async_server_final_verify_client_cb(
                        self.st, 
                        chanbinding,
                        &self.chanbind_helper
                    )
                    .await?;

                let nonce = [client_nonce, self.server_nonce.as_str()].concat();

                // verify nonce
                if finalnonce != nonce.as_str()
                {
                    scram_error!(
                        ScramErrorCode::VerificationError,
                        ScramServerError::OtherError,
                        "received invalid nonce username: '{}' rcvn: '{}' have: '{}'", 
                        self.username.as_ref().map_or("-missing-", |v| v.as_str()), finalnonce, client_nonce
                    );
                }

                /*let cb_data = base64::encode(
                    [
                        self.cli_chanbind.convert2header(), 
                        self.cli_chanbind.convert2data()
                    ].concat());*/
    
                //base64 config
                
                let client_final_without_proof = ["c=", chanbinding, ",r=", &nonce].concat();
                let client_first_bare = ["n=", self.username.as_ref().unwrap(), ",r=", client_nonce].concat();
                let server_first = 
                    [
                        "r=", &nonce, 
                        ",s=", self.sp.get_salt_base64(),
                        ",i=", &self.sp.get_iterations().to_string(),
                    ]
                    .concat();

                let authmsg = 
                    [
                        &client_first_bare,
                        ",",
                        &server_first,
                        ",",
                        &client_final_without_proof,
                    ]
                    .concat();
                
                let keys = self.sp.get_scram_keys();

                let client_key = S::hmac(keys.get_clinet_key(), &self.sp.get_salted_hashed_password())?;
                let server_key = S::hmac(keys.get_server_key(), &self.sp.get_salted_hashed_password())?;
                let stored_key = S::hash(&client_key);
                let client_signature = S::hmac(authmsg.as_bytes(), &stored_key)?;
                let server_signature = S::hmac(authmsg.as_bytes(), &server_key)?;

                let calc_client_proof = S::xor_arrays(&client_key, &client_signature)?;

                // verify proof
                let recv_decoded_proof = 
                    general_purpose::STANDARD.decode(proof)
                        .map_err(|e| 
                            scram_error_map!(
                                ScramErrorCode::MalformedScramMsg, 
                                ScramServerError::OtherError,
                                "base64 decode client proof failed, {}", e
                            )
                        )?;

                if calc_client_proof != recv_decoded_proof
                {
                    scram_error!(ScramErrorCode::VerificationError, ScramServerError::InvalidProof,
                        "received invalid proof from client, username: '{}'", 
                        self.username.as_ref().map_or("-missing-", |v| v.as_str()));
                }

                let output = ["v=", &general_purpose::STANDARD.encode(&server_signature)].concat();

                // update states
                self.state = ScramState::Completed;

                return Ok(output);
            },
            ScramData::SmsgFinalMessage{..} =>
            {
                panic!("server can not handle state: {}", parsed_resp);
            }
        }
    }
}

#[async_trait]
impl<S, A, B> AsyncScramServerDyn 
for AsyncScramServer<S, A, B>
where 
    S: ScramHashing + Send, 
    A: AsyncScramAuthServer<S> + Sync + Send, 
    B: AsyncScramCbHelper + Sync + Send
{
    fn get_id(&self) -> usize
    {
        return self.id;
    }

    fn get_auth_username(&self) -> Option<&String>
    {
        return self.username.as_ref();
    }

    fn get_auth_authzid(&self) -> Option<&String>
    {
        return self.authzid.as_ref();
    }

    async 
    fn parse_response_base64(&mut self, input: &[u8]) -> ScramResultServer
    {
        let decoded = 
            match general_purpose::STANDARD.decode(input)
                .map_err(|e| 
                    scram_error_map!(ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                        "base64 decoding of client response failed with error, '{}'", e)
                )
                .map_err(|e| ScramResultServer::Error(e))
            {
                Ok(r) => r,
                Err(e) => return e
            };
                

        let dec_utf8 = 
            match str::from_utf8(&decoded)
                    .map_err(|e| 
                        scram_error_map!(
                            ScramErrorCode::MalformedScramMsg, ScramServerError::InvalidEncoding,
                            "base64 decoded response contains invalid UTF-8 seq, '{}'", e
                        )
                    )
            {
                Ok(r) => r,
                Err(e) => return ScramResultServer::Error(e)
            };

        return self.parse_response(dec_utf8).await;
    }

    async 
    fn parse_response(&mut self, resp: &str) -> ScramResultServer
    {
        match self.parse_response_internal(resp).await
        {
            Ok(r) =>
            {
                if self.state == ScramState::Completed
                {
                    return ScramResultServer::Final(r);
                }
                else
                {
                    return ScramResultServer::Data(r);
                }
            },
            Err(e) =>
            {
                return ScramResultServer::Error(e);
            }
        }
    }
}


/// # A async Scram Client. 
/// 
/// Should be used on the client side.
/// 
/// ## Generics 
/// 
/// * `S`- [ScramHashing] a preferable hashing algorithm.
/// 
/// * `A`- [ScramAuthClient] an adapter which is used to obtain user creds.
/// 
/// * `B` - [ScramCbHelper] a channel bind helper.
/// 
/// ```text
/// If it is required to support `channel binding`, the crate does not provide
/// a helpers to obtain required information. You must find out how to extract 
/// endpoint certificate from TLS connection.
///     i.e native_tls::TlsStream::tls_server_end_point()  
/// ```
#[derive(Debug)]
pub struct AsyncScramClient<S: ScramHashing, A: AsyncScramAuthClient + Sync, B: AsyncScramCbHelper>
{
    /// A hasher picked
    hasher: PhantomData<S>,
    
    /// A authentification callback
    auth: A,
    
    /// A client generated/picked nonce
    client_nonce: String,
    
    /// A current state step
    state: ScramState,
    
    /// A type of the channel bind [ChannelBindType]
    chanbind: ChannelBindType,
    
    /// A callback to support channel bind mechanism
    chanbind_helper: B,

    /// Ignore the additional data (extensions).
    ignore_exts: bool
}

impl<S: ScramHashing, A: AsyncScramAuthClient + Sync, B: AsyncScramCbHelper> AsyncScramClient<S, A, B>
{
    /// Creates a new client instance with borrowed arguments. Sets all fields to 
    /// default instance.
    /// 
    /// # Arguments
    /// 
    /// * `scram_auth_cli` - an authentification instance which implements [ScramAuthClient]
    /// 
    /// * `scram_nonce` - a client scram nonce [ScramNonce]
    /// 
    /// * `chan_bind_type` - picks the channel bound [ChannelBindType]. It is
    ///                     responsibility of the developer to correctly set the chan binding
    ///                     type.
    /// 
    /// * `chan_bind_helper` - a data type which implements a traint [ScramCbHelper] which
    ///                 contains functions for realization which are designed to provide the
    ///                 channel bind data to the `SCRAM` crate.
    /// 
    /// * `ignore_exts` - if set to true ignores the unsupported extensions which may be found
    ///                 after the main data. Otherwise, the error will be generated reporting 
    ///                 unexpected data.
    /// # Examples
    /// 
    /// ```ignore
    /// let cbt = ChannelBindType::None;
    ///
    /// let ac = AuthClient::new(username, password);
    /// let nonce = ScramNonce::Plain(&client_nonce_dec);
    ///
    /// let scram_res = SyncScramClient::<ScramSha256, AuthClient, AuthClient>::new(&ac, nonce, cbt, &ac);
    /// ```
    pub 
    fn new(
        scram_auth_cli: A, 
        scram_nonce: ScramNonce, 
        chan_bind_type: ChannelBindType,
        chan_bind_helper: B,
        ignore_exts: bool
    ) -> ScramResult<AsyncScramClient<S, A, B>>
    {
        return Ok(
            Self
            {
                hasher: PhantomData,
                auth: scram_auth_cli,
                client_nonce: scram_nonce.get_nonce(),
                state: ScramState::InitClient,
                chanbind: chan_bind_type,
                chanbind_helper: chan_bind_helper,
                ignore_exts: ignore_exts
            }
        );
    }

    /// Checks if the client authentification was completed successfully.
    pub 
    fn is_completed(&self) -> bool
    {
        return self.state == ScramState::Completed;
    }

    /// Initializes the SCRAM negoatiation from client
    /// 
    /// # Arguments
    /// 
    /// * `to_base64` - when set to true, the generated response will be encoded to
    ///                 base64.  
    /// 
    /// # Returns
    /// * base64 encoded or plain string with formed message
    pub async 
    fn init_client(&mut self) -> ScramResultClient
    {       
        let compiled = 
            [
                self.chanbind.convert2header(),
                "n=", self.auth.get_username().await,
                ",r=", self.client_nonce.as_str(),
            ].concat();
    
        self.state = ScramState::WaitForServInitMsg;     
        
        return ScramResultClient::Output(compiled);
    }

    /// Decodes the response from server which is in base64 encoded and performes parsing
    /// and result computation.
    ///
    /// # Arguments
    ///
    /// * `input` - a base64 encoded response from server
    /// 
    /// # Returns
    /// 
    /// * The [Result] is returned as alias [ScramResult].
    /// 
    /// - [Result::Ok] is returned with [ScramResultClient] which contains a hint how to
    ///     act on the next step.
    /// 
    /// - [Result::Err] is returned in case of error.
    pub async 
    fn parse_response_base64<T: AsRef<[u8]>>(&mut self, input: T) -> ScramResult<ScramResultClient>
    {
        let decoded = 
            general_purpose::STANDARD.decode(input)
                .map_err(|e| 
                    scram_ierror_map!(
                        ScramErrorCode::MalformedScramMsg, 
                        "base64 decode server response failed, '{}'", 
                        e
                    )
                )?;

        let dec_utf8 = 
            str::from_utf8(&decoded)
                .map_err(|e| 
                    scram_ierror_map!(
                        ScramErrorCode::MalformedScramMsg, 
                        "base64 decoded response contains invalid UTF-8 seq, '{}'", 
                        e
                    )
                )?;

        return self.parse_response(dec_utf8).await;
    }

    /// Performes parsing of the response from server and result computation.  
    /// It is assumed that resp is UTF-8 valid sequences
    /// 
    /// # Arguments
    /// 
    /// * `resp` - A response from client as ref str.
    /// 
    /// * `to_base64` - if set to true, will encode response into base64.
    /// 
    /// # Returns
    /// 
    /// * The [Result] is returned as alias [ScramResult].
    /// 
    /// - [Result::Ok] is returned with [ScramResultClient] which contains a hint how to
    ///     act on the next step.
    /// 
    /// - [Result::Err] is returned in case of error.
    pub async 
    fn parse_response(&mut self, resp: &str) -> ScramResult<ScramResultClient>
    {
        let parsed_resp = ScramData::try_from((resp, &self.state, self.ignore_exts))?;

        match parsed_resp
        {
            ScramData::CmsgInitial{..} =>
            {
                panic!("scram: client can not handle state: {}", parsed_resp);
            },
            // Client sends response to server after intial message
            ScramData::SmsgInitial{nonce, salt, itrcnt} => 
            {
                //validate iterations
                if itrcnt == 0 || itrcnt > ScramCommon::SCRAM_MAX_ITERS
                {
                    scram_ierror!(
                        ScramErrorCode::InternalError, 
                        "iterations count is not appropriate: i='{}'", 
                        itrcnt
                    );
                }

                //todo channel bind things SASL 
                let cb_data = 
                    general_purpose::STANDARD
                        .encode(
                            [
                                self.chanbind.convert2header().as_bytes(), 
                                self.chanbind.async_get_cb_data_raw(&self.chanbind_helper).await?.as_slice(),
                            ]
                            .concat()
                        );

                let client_final_message_bare = ["c=", &cb_data, ",r=", nonce].concat();

                let authmsg = 
                    [
                        "n=", self.auth.get_username().await,
                        ",r=", self.client_nonce.as_str(), 
                        ",", resp,
                        ",", client_final_message_bare.as_str()
                    ]
                    .concat();

                // get keys (client, server keys)
                let (key_client, key_server) = 
                    self
                        .auth
                        .get_scram_keys()
                        .await
                        .map_or(
                            ScramKey::default_client_server_keys(), 
                            |key| (key.get_clinet_key(), key.get_server_key())
                        ); 

                let salted_password = 
                    S::derive(
                        self.auth.get_password().await.as_bytes(), 
                        &salt, 
                        NonZeroU32::new(itrcnt).unwrap()
                    )?;

                let client_key = S::hmac(key_client, &salted_password)?;
                let server_key = S::hmac(key_server, &salted_password)?;

                

                let stored_key = S::hash(&client_key);
                let client_signature = S::hmac(authmsg.as_bytes(), &stored_key)?;
                let server_signature = S::hmac(authmsg.as_bytes(), &server_key)?;
                
                let client_response = 
                    [
                        &client_final_message_bare,
                        ",p=",
                        &general_purpose::STANDARD
                            .encode(
                                    S::xor_arrays(&client_key, &client_signature)?
                            )
                    ]
                    .concat();
                

                self.state = ScramState::WaitForServFinalMsg{server_signature: server_signature};
                
                return Ok(ScramResultClient::Output(client_response));
            },
            ScramData::CmsgFinalMessage{ .. } =>
            {
                panic!("scram: client can not handle state: {}", parsed_resp);
            },
            ScramData::SmsgFinalMessage{verifier, server_verifier} =>
            {
                if &verifier == server_verifier
                {
                    self.state = ScramState::Completed;

                    return Ok(ScramResultClient::Completed);
                }
                else
                {
                    scram_ierror!(
                        ScramErrorCode::VerificationError,
                        "state: '{}', server signature mismatch, server sig: '{:x?}', local: {:x?}",
                        self.state, server_verifier, verifier
                    );
                }

            }
        }
    }
}


#[cfg(feature = "std")]
#[cfg(test)]
mod tests
{
    
    use async_trait::async_trait; 
    use crate::{ScramSha256RustNative, SCRAM_TYPES};
    
    use super::*;

    #[derive(Debug)]
    struct AuthServer
    {

    }

    #[async_trait]
    impl AsyncScramAuthServer<ScramSha256RustNative> for &AuthServer
    {
        async fn get_password_for_user(&self, _username: &str, _authzid: Option<&str>) -> ScramResult<ScramPassword>
        {
            let password = "pencil";
            let salt = b"[m\x99h\x9d\x125\x8e\xec\xa0K\x14\x126\xfa\x81".to_vec();
            let iterations = NonZeroU32::new(4096).unwrap();

            return 
                Ok(
                    ScramPassword::found_secret_password(
                        ScramSha256RustNative::derive(password.as_bytes(), &salt, iterations).unwrap(),
                        general_purpose::STANDARD.encode(salt), 
                        iterations,
                        None
                    )
                );

                    
        }
    }

    #[async_trait]
    impl AsyncScramCbHelper for &AuthServer
    {
        async 
        fn get_tls_server_endpoint(&self) -> ScramResult<Vec<u8>> 
        {
            crate::HELPER_UNSUP_SERVER!("endpoint");
        }

        async 
        fn get_tls_unique(&self) -> ScramResult<Vec<u8>> {
            crate::HELPER_UNSUP_SERVER!("unique");
        }

        async 
        fn get_tls_exporter(&self) -> ScramResult<Vec<u8>> 
        {
            crate::HELPER_UNSUP_SERVER!("exporter");
        }
    }

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

    #[futures_test::test]
    async fn scram_sha256_server() 
    {     
        
        


        let _username = "user";
        let _password = "pencil";
        let client_nonce = "rOprNGfwEbeRWgbNEkqO";
        let _client_nonce_dec = general_purpose::STANDARD.decode(client_nonce).unwrap();
        let client_init = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
        let server_init = "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096";
        let server_nonce = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0";
        let _server_nonce_dec = b"\x86\xf6\x03\xa5e\x1a\xd9\x16\x93\x08\x07\xee\xc4R%\x8e\x13e\x16M".to_vec();
        let client_final = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
        let server_final = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";


        let serv = AuthServer::new();
        let nonce = ScramNonce::base64(server_nonce).unwrap();

        let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();
        let scram_res = 
            AsyncScramServer::<ScramSha256RustNative, &AuthServer, &AuthServer>::new(&serv, &serv, nonce, scramtype, false);
        assert_eq!(scram_res.is_ok(), true);

        let mut scram = scram_res.unwrap();

        let resp_res = scram.parse_response(client_init).await;

        assert_eq!(resp_res.is_ok(), true);


        let resp = resp_res.get_raw_output();
        assert_eq!( resp, server_init ); 

        let resp_res = scram.parse_response(client_final).await;

        if resp_res.is_err() == true
        {
            println!("{}", resp_res.err().unwrap());
            assert_eq!(false, true);
            return;
        }

        let resp = resp_res.get_raw_output();
        assert_eq!( resp, server_final ); 
    }

    #[futures_test::test]
    async fn scram_sha256_works() 
    { 
        #[derive(Debug)]
        struct AuthClient
        {
            username: String,
            password: String,
        }

        #[async_trait]
        impl AsyncScramAuthClient for &AuthClient
        {
            async 
            fn get_username(&self) -> &str
            {
                return &self.username;
            }

            async 
            fn get_password(&self) -> &str
            {
                return &self.password;
            }
        }

        #[async_trait]
        impl AsyncScramCbHelper for &AuthClient
        {
            async 
            fn get_tls_server_endpoint(&self) -> ScramResult<Vec<u8>> 
            {
                crate::HELPER_UNSUP_CLIENT!("endpoint");
            }

            async 
            fn get_tls_unique(&self) -> ScramResult<Vec<u8>> {
                crate::HELPER_UNSUP_CLIENT!("unique");
            }

            async 
            fn get_tls_exporter(&self) -> ScramResult<Vec<u8>> 
            {
                crate::HELPER_UNSUP_CLIENT!("exporter");
            }
        }

        impl AuthClient
        {
            pub fn new(u: &'static str, p: &'static str) -> Self
            {
                return AuthClient{username: u.to_string(), password: p.to_string()};
            }
        }

        let username = "user";
        let password = "pencil";
        let client_nonce = "rOprNGfwEbeRWgbNEkqO";
        let client_nonce_dec = general_purpose::STANDARD.decode(client_nonce).unwrap();
        let client_init = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO";
        let server_init = "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096";
        let _server_nonce = "%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0";
        let _server_nonce_dec = b"\x86\xf6\x03\xa5e\x1a\xd9\x16\x93\x08\x07\xee\xc4R%\x8e\x13e\x16M";
        let client_final = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=";
        let server_final = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=";


        let cbt = ChannelBindType::None;

        let ac = AuthClient::new(username, password);
        let nonce = ScramNonce::plain(&client_nonce_dec).unwrap();

        let scram_res = 
            AsyncScramClient::<ScramSha256RustNative, &AuthClient, &AuthClient>::new(&ac, nonce, cbt, &ac, false);
        assert_eq!(scram_res.is_ok(), true);

        let mut scram = scram_res.unwrap();
        
        let init = scram.init_client().await;
        assert_eq!( init.get_output().unwrap(), client_init ); 

        let resp_res = scram.parse_response(server_init).await;
        assert_eq!(resp_res.is_ok(), true);

        let resp = resp_res.unwrap().unwrap_output().unwrap();
        assert_eq!( resp.as_str(), client_final ); 

        let res = scram.parse_response(server_final).await;
        assert_eq!(res.is_ok(), true);

        assert_eq!(scram.is_completed(), true);
    }

    #[test]
    fn scram_incorrect_test()
    {
        use std::time::Instant;
        let server_init = "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096";
        
        let start = Instant::now();
            let res = ScramData::try_from((server_init, &ScramState::WaitForServInitMsg, false));
        let el = start.elapsed();
        println!("took: {:?}", el);

        assert_eq!(res.is_ok(), true);

        return;
    } 

    #[test]
    fn scram_dyn_server_test() 
    { 
        let serv = AuthServer::new();
        let nonce = ScramNonce::none().unwrap();

        let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();
        let scram_res = 
            AsyncScramServer
                ::<ScramSha256RustNative, &AuthServer, &AuthServer>
                ::new(&serv, &serv, nonce, scramtype, false)
                    .unwrap();

        let id: usize = 111;

        let dyn_scram_res = scram_res.make_dyn(NonZeroUsize::new(id).unwrap());

        assert_eq!(dyn_scram_res.get_id(), id);

        let items = vec![dyn_scram_res];

        let search_res = items.binary_search_by(|probe| probe.partial_cmp(&id).unwrap());

        assert_eq!(search_res.is_ok(), true); 
    }
}
