/*-
 * 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(not(feature = "std"))]
use alloc::string::ToString;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use base64::{Engine, engine::general_purpose};

use crate::{ChannelBindType, ScramErrorCode, ScramResult, ScramServerError, ScramType, scram_error};
use super::AsyncScramCbHelper;



impl ChannelBindType
{
    pub(crate) async 
    fn async_get_cb_data_raw(&self, sbh: &dyn AsyncScramCbHelper) -> ScramResult<Vec<u8>>
    {
        match self
        {
            Self::None => 
                return Ok(b"".to_vec()),
            Self::Unsupported => 
                return Ok(b"".to_vec()),
            Self::TlsUnique => 
                return sbh.get_tls_unique().await,
            Self::TlsServerEndpoint => 
                return sbh.get_tls_server_endpoint().await,
            Self::TlsExporter => 
                return sbh.get_tls_exporter().await,
        }
    }

    /// Server uses this function to verify the the client channel bind
    /// in final message.
    /// 
    /// # Arguments
    /// 
    /// * `st` - [ScramType] a current scram type
    /// 
    /// * `cb_attr` - a received channel binding data from client in base64 format
    /// 
    /// * `endpoint_hash` - a servers TLS end point certificate
    /// 
    /// # Returns
    /// 
    /// * [ScramResult] nothing in payload or error
    pub(crate) async 
    fn async_server_final_verify_client_cb(
        &self, 
        st: &ScramType, 
        cb_attr: &str,
        sbh: &(dyn AsyncScramCbHelper + Sync),
    ) -> ScramResult<()>
    {
        // verify input
        // If we are not using channel binding, the binding data is expected
        // to always be "biws", which is "n,," base64-encoded, or "eSws",
        // which is "y,,".  We also have to check whether the flag is the same
        // one that the client originally sent. auth-scram_8c_source.c:1310

        let comp_cb_attr = 
            match *self
            {
                Self::TlsExporter => 
                {
                    if st.scram_chan_bind == false
                    {
                        scram_error!(
                            ScramErrorCode::InternalError,
                            ScramServerError::OtherError,
                            "assertion trap: cb_type: {}, scram_type: {}, does not \
                                include SCRAM channel binding",
                            self, st
                        );
                    }

                    let expt_tls = 
                        match sbh.get_tls_exporter().await
                        {
                            Ok(r) => r,   
                            Err(e) => 
                                scram_error!(
                                    ScramErrorCode::InternalError,
                                    ScramServerError::OtherError,
                                    "assertion trap: cb_type: {}, scram_type: {} \
                                    TlsExporter requires endpoint data from TLS connection! \
                                    Error returned: '{}'", 
                                    self, st, e
                                ),
                        };

                    let header = 
                        [
                            self.convert2header().as_bytes(), //"p=tls-server-end-point,,".as_bytes(),
                            expt_tls.as_slice(),
                            //cbind_data
                        ].concat();
                    
                    let bheader = general_purpose::STANDARD.encode(header);
                    
                    bheader
                },
                Self::TlsUnique =>
                {
                    if st.scram_chan_bind == false
                    {
                        scram_error!(
                            ScramErrorCode::InternalError,
                            ScramServerError::OtherError,
                            "assertion trap: cb_type: {}, scram_type: {}, does not \
                                include SCRAM channel binding",
                            self, st
                        );
                    }

                    let uniq_tls = 
                        match sbh.get_tls_unique().await
                        {
                            Ok(r) => r,   
                            Err(e) => 
                                scram_error!(
                                    ScramErrorCode::InternalError,
                                    ScramServerError::OtherError,
                                    "assertion trap: cb_type: {}, scram_type: {} \
                                    TlsUnique requires endpoint data from TLS connection! \
                                    Error returned: '{}'", 
                                    self, st, e
                                ),
                        };

                    let header = 
                        [
                            self.convert2header().as_bytes(), //"p=tls-server-end-point,,".as_bytes(),
                            uniq_tls.as_slice(),
                            //cbind_data
                        ].concat();
                    
                    let bheader = general_purpose::STANDARD.encode(header);
                    
                    bheader
                },
                Self::TlsServerEndpoint =>
                {
                    if st.scram_chan_bind == false
                    {
                        scram_error!(
                            ScramErrorCode::InternalError,
                            ScramServerError::OtherError,
                            "assertion trap: cb_type: {}, scram_type: {}, does not \
                                include SCRAM channel binding",
                            self, st
                        );
                    }

                    let endp_cert_hash = 
                        match sbh.get_tls_server_endpoint().await
                        {
                            Ok(r) => r,   
                            Err(e) => 
                                scram_error!(
                                    ScramErrorCode::InternalError,
                                    ScramServerError::OtherError,
                                    "assertion trap: cb_type: {}, scram_type: {} \
                                    TlsServerEndpoint requires endpoint data from TLS connection! \
                                    Error returned: '{}'", 
                                    self, st, e
                                ),
                        };

                    //get the data from ChannelBindingData which contains 
                    //hash data of server's SSL certificate and combine it
                    let header = 
                        [
                            self.convert2header().as_bytes(), //"p=tls-server-end-point,,".as_bytes(),
                            endp_cert_hash.as_slice(),
                            //cbind_data
                        ].concat();
                    
                    let bheader = general_purpose::STANDARD.encode(header);


                    bheader
                },
                Self::Unsupported => 
                {
                    "eSws".to_string()
                },
                Self::None =>
                {
                    "biws".to_string()
                }
            };

        if comp_cb_attr.as_str() == cb_attr
        {
            return Ok(());
        }
        else
        {
            scram_error!(
                ScramErrorCode::VerificationError, 
                ScramServerError::OtherError,
                "SCRAM channel binding '{}' check failed! Scram type: {}",
                self, st
            );
        }
    }

}