


extern crate scram_rs;

use base64::Engine;
use base64::engine::general_purpose;
use scram_rs::ScramServerDyn;
use scram_rs::scram_sync::SyncScramClient;
use scram_rs::scram_sync::SyncScramServer;

use std::num::NonZeroU32;
use std::num::NonZeroUsize;
#[cfg(feature = "std")]
use std::time::Instant;
use scram_rs::scram_hashing_sha3_5::ScramSha3_512RustNative;
use scram_rs::ChannelBindType;
use scram_rs::ScramAuthServer;
use scram_rs::ScramCbHelper;
use scram_rs::ScramHashing;
use scram_rs::ScramNonce;
use scram_rs::ScramPassword;
use scram_rs::ScramResult;
use scram_rs::ScramSha256RustNative;
use scram_rs::ScramAuthClient;
use scram_rs::SCRAM_TYPES;

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

    impl ScramAuthClient for &AuthClient
    {
        fn get_username(&self) -> &str
        {
            return &self.username;
        }

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

    impl ScramCbHelper for &AuthClient
    {
        
    }

    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=";
    
    #[cfg(feature = "std")]
    let start = Instant::now();

    let cbt = ChannelBindType::None;

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

    let scram_res = 
        SyncScramClient::<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();
    // println!("init msg: {}", init.get_output().unwrap());
    assert_eq!( init.get_output().unwrap(), client_init ); 

    let resp_res = scram.parse_response(server_init);
    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);
    
    #[cfg(feature = "std")]
    {
        let el = start.elapsed();
        println!("took: {:?}", el);
    }

    assert_eq!(res.is_ok(), true, "{}", res.err().unwrap());

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



#[derive(Debug)]
struct AuthServer
{

}

impl ScramAuthServer<ScramSha256RustNative> for &AuthServer
{
    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();

        /*let a = general_purpose::STANDARD.encode(ScramSha256RustNative::derive(password.as_bytes(), &salt, iterations).unwrap());
        let b = general_purpose::STANDARD.encode(&salt);
        println!("{}  {}", a, b);*/

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

impl ScramAuthServer<ScramSha3_512RustNative> for &AuthServer
{
    fn get_password_for_user(&self, _username: &str, _authzid: Option<&str>) -> ScramResult<ScramPassword>
    {
        let password = "test_password_1234567890!";
        let salt = general_purpose::STANDARD.decode("dGhpc19pc19hX3NhbHQ=").unwrap();
        let iterations = ScramSha3_512RustNative::MIN_ITERS;

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

impl ScramCbHelper for &AuthServer
{
    
}

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

#[test]
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=";

    #[cfg(feature = "std")]
    let start = Instant::now();

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

    let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();

    #[cfg(feature = "std")]
    let l_start = Instant::now();

    let scram_res = 
        SyncScramServer
            ::<ScramSha256RustNative, &AuthServer, &AuthServer>
            ::new(&serv, &serv, nonce, scramtype, false);

    #[cfg(feature = "std")]
    {
        let el = l_start.elapsed();
        println!("0 took: {:?}", el);
    }

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

    let mut scram = scram_res.unwrap();

    #[cfg(feature = "std")]
    let l_start = Instant::now();
    let resp_res = scram.parse_response(client_init);

    #[cfg(feature = "std")]
    {
        let el = l_start.elapsed();
        println!("1 took: {:?}", el);
        println!("{}", resp_res.get_raw_output());
    }

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

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

    #[cfg(feature = "std")]
    let l_start = Instant::now();

    let resp_res = scram.parse_response(client_final);

    #[cfg(feature = "std")]
    {
        let el = l_start.elapsed();
        println!("2 took: {:?}", el);
    }

    if resp_res.is_err() == true
    {
        #[cfg(feature = "std")]
        println!("{}", resp_res.err().unwrap());

        assert_eq!(false, true);
        return;
    }
    
    #[cfg(feature = "std")]
    {
        let el = start.elapsed();
        println!("took: {:?}", el);
    }

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

#[test]
fn scram_sha3_512_server() 
{ 
    let _username = "user";
    let _password = "test_password_1234567890";
    let client_init = "n,,n=user,r=bm9uY2Ux";
    let server_init = "r=bm9uY2Uxbm9uY2Ux,s=dGhpc19pc19hX3NhbHQ=,i=10000";
    let client_final = "c=biws,r=bm9uY2Uxbm9uY2Ux,p=0U4jN9UK5BsRFITU+eFkCW4e2Jxfqp4LWFKfs9BYhS84jwVmJSU8knLmN1W20SbfKHkkjl0RmCPnZZUqhiXEZA==";
    let server_final = "v=1VKedjAPozPOoKnp3O17jQbt/XnI3glhNa7pJndvKHrG6cD1WeawZY65esDvEvae4hTKGFIjtdfGuFo994GsUg==";

    #[cfg(feature = "std")]
    let start = Instant::now();

    let serv = AuthServer::new();

    let nonce = ScramNonce::plain(b"nonce1").unwrap();

    let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA3-512").unwrap();

    #[cfg(feature = "std")]
    let l_start = Instant::now();

    let scram_res = 
        SyncScramServer
            ::<ScramSha3_512RustNative, &AuthServer, &AuthServer>
            ::new(&serv, &serv, nonce, scramtype, false);

    #[cfg(feature = "std")]
    {
        let el = l_start.elapsed();
        println!("0 took: {:?}", el);
    }

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

    let mut scram = scram_res.unwrap();

    
    #[cfg(feature = "std")]
    let l_start = Instant::now();

    let resp_res = scram.parse_response(client_init);

    #[cfg(feature = "std")]
    {
        let el = l_start.elapsed();
        println!("1 took: {:?}", el);
        println!("{}", resp_res.get_raw_output());
    }

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

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

    #[cfg(feature = "std")]
    let l_start = Instant::now();

    let resp_res = scram.parse_response(client_final);

    #[cfg(feature = "std")]
    {
        let el = l_start.elapsed();
        println!("2 took: {:?}", el);
    }

    if resp_res.is_err() == true
    {
        #[cfg(feature = "std")]
        println!("{}", resp_res.err().unwrap());

        assert_eq!(false, true);
        return;
    }
    
    #[cfg(feature = "std")]
    {
        let el = start.elapsed();
        println!("took: {:?}", el);
    }

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

#[cfg(feature = "use_ring")]
#[test]
fn scram_sha_256_server_ring() 
{
    use scram_rs::{ScramSha256Ring};
 
    #[derive(Debug)]
    struct AuthClient
    {
        username: String,
        password: String,
    }

    impl ScramAuthClient for &AuthClient
    {
        fn get_username(&self) -> &str
        {
            return &self.username;
        }

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

    impl ScramCbHelper for &AuthClient
    {
        
    }

    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=";
    
    #[cfg(feature = "std")]
    let start = Instant::now();

    let cbt = ChannelBindType::None;

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

    let scram_res = 
        SyncScramClient::<ScramSha256Ring, &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();
    // println!("init msg: {}", init.get_output().unwrap());
    assert_eq!( init.get_output().unwrap(), client_init ); 

    let resp_res = scram.parse_response(server_init);
    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);
    
    #[cfg(feature = "std")]
    {
        let el = start.elapsed();
        println!("took: {:?}", el);
    }

    assert_eq!(res.is_ok(), true, "{}", res.err().unwrap());

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

#[cfg(feature = "use_aws_lc")]
#[test]
fn scram_sha_256_server_aws_ls() 
{
    use scram_rs::ScramSha256AwsLc;
 
    #[derive(Debug)]
    struct AuthClient
    {
        username: String,
        password: String,
    }

    impl ScramAuthClient for &AuthClient
    {
        fn get_username(&self) -> &str
        {
            return &self.username;
        }

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

    impl ScramCbHelper for &AuthClient
    {
        
    }

    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=";
    
    #[cfg(feature = "std")]
    let start = Instant::now();

    let cbt = ChannelBindType::None;

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

    let scram_res = 
        SyncScramClient::<ScramSha256AwsLc, &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();
    // println!("init msg: {}", init.get_output().unwrap());
    assert_eq!( init.get_output().unwrap(), client_init ); 

    let resp_res = scram.parse_response(server_init);
    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);
    
    #[cfg(feature = "std")]
    {
        let el = start.elapsed();
        println!("took: {:?}", el);
    }

    assert_eq!(res.is_ok(), true, "{}", res.err().unwrap());

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

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

    let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();

    let scram_res = 
        SyncScramServer
            ::<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);
}

