use crate::msgs::enums::{CipherSuite, HashAlgorithm, SignatureAlgorithm, SignatureScheme};
use crate::msgs::enums::{NamedGroup, ProtocolVersion};
use crate::msgs::handshake::KeyExchangeAlgorithm;
use crate::msgs::handshake::DecomposedSignatureScheme;
use crate::msgs::handshake::{ClientECDHParams, ServerECDHParams};
use crate::msgs::codec::{Reader, Codec};
use ring;
#[allow(non_camel_case_types)]
#[derive(Debug, PartialEq)]
pub enum BulkAlgorithm {
    
    AES_128_GCM,
    
    AES_256_GCM,
    
    CHACHA20_POLY1305,
}
pub struct KeyExchangeResult {
    pub pubkey: ring::agreement::PublicKey,
    pub premaster_secret: Vec<u8>,
}
pub struct KeyExchange {
    pub group: NamedGroup,
    alg: &'static ring::agreement::Algorithm,
    privkey: ring::agreement::EphemeralPrivateKey,
    pub pubkey: ring::agreement::PublicKey,
}
impl KeyExchange {
    pub fn named_group_to_ecdh_alg(group: NamedGroup)
                                   -> Option<&'static ring::agreement::Algorithm> {
        match group {
            NamedGroup::X25519 => Some(&ring::agreement::X25519),
            NamedGroup::secp256r1 => Some(&ring::agreement::ECDH_P256),
            NamedGroup::secp384r1 => Some(&ring::agreement::ECDH_P384),
            _ => None,
        }
    }
    pub fn supported_groups() -> &'static [NamedGroup] {
        
        &[
            NamedGroup::X25519,
            NamedGroup::secp384r1,
            NamedGroup::secp256r1
        ]
    }
    pub fn client_ecdhe(kx_params: &[u8]) -> Option<KeyExchangeResult> {
        let mut rd = Reader::init(kx_params);
        let ecdh_params = ServerECDHParams::read(&mut rd)?;
        KeyExchange::start_ecdhe(ecdh_params.curve_params.named_group)?
            .complete(&ecdh_params.public.0)
    }
    pub fn start_ecdhe(named_group: NamedGroup) -> Option<KeyExchange> {
        let alg = KeyExchange::named_group_to_ecdh_alg(named_group)?;
        let rng = ring::rand::SystemRandom::new();
        let ours = ring::agreement::EphemeralPrivateKey::generate(alg, &rng).unwrap();
        let pubkey = ours.compute_public_key().unwrap();
        Some(KeyExchange {
            group: named_group,
            alg,
            privkey: ours,
            pubkey,
        })
    }
    pub fn check_client_params(&self, kx_params: &[u8]) -> bool {
        self.decode_client_params(kx_params).is_some()
    }
    fn decode_client_params(&self, kx_params: &[u8]) -> Option<ClientECDHParams> {
        let mut rd = Reader::init(kx_params);
        let ecdh_params = ClientECDHParams::read(&mut rd).unwrap();
        if rd.any_left() {
            None
        } else {
            Some(ecdh_params)
        }
    }
    pub fn server_complete(self, kx_params: &[u8]) -> Option<KeyExchangeResult> {
        self.decode_client_params(kx_params)
            .and_then(|ecdh| self.complete(&ecdh.public.0))
    }
    pub fn complete(self, peer: &[u8]) -> Option<KeyExchangeResult> {
        let peer_key = ring::agreement::UnparsedPublicKey::new(self.alg, peer);
        let secret = ring::agreement::agree_ephemeral(self.privkey,
                                                      &peer_key,
                                                      (),
                                                      |v| {
                                                          let mut r = Vec::new();
                                                          r.extend_from_slice(v);
                                                          Ok(r)
                                                      });
        if secret.is_err() {
            return None;
        }
        Some(KeyExchangeResult {
            pubkey: self.pubkey,
            premaster_secret: secret.unwrap(),
        })
    }
}
#[derive(Debug)]
pub struct SupportedCipherSuite {
    
    pub suite: CipherSuite,
    
    pub kx: KeyExchangeAlgorithm,
    
    pub bulk: BulkAlgorithm,
    
    pub hash: HashAlgorithm,
    
    pub sign: SignatureAlgorithm,
    
    pub enc_key_len: usize,
    
    
    
    
    pub fixed_iv_len: usize,
    
    
    
    
    pub explicit_nonce_len: usize,
    pub(crate) hkdf_algorithm: ring::hkdf::Algorithm,
}
impl PartialEq for SupportedCipherSuite {
    fn eq(&self, other: &SupportedCipherSuite) -> bool {
        self.suite == other.suite
    }
}
impl SupportedCipherSuite {
    
    pub fn get_hash(&self) -> &'static ring::digest::Algorithm {
        self.hkdf_algorithm.hmac_algorithm().digest_algorithm()
    }
    
    
    
    pub fn do_client_kx(&self, kx_params: &[u8]) -> Option<KeyExchangeResult> {
        match self.kx {
            KeyExchangeAlgorithm::ECDHE => KeyExchange::client_ecdhe(kx_params),
            _ => None,
        }
    }
    
    
    pub fn start_server_kx(&self, named_group: NamedGroup) -> Option<KeyExchange> {
        match self.kx {
            KeyExchangeAlgorithm::ECDHE => KeyExchange::start_ecdhe(named_group),
            _ => None,
        }
    }
    
    
    
    pub fn resolve_sig_schemes(&self,
                              offered: &[SignatureScheme])
                              -> Vec<SignatureScheme> {
        let mut our_preference = vec![
            
            
            SignatureScheme::make(self.sign, self.hash),
            
            
            SignatureScheme::make(self.sign, HashAlgorithm::SHA512),
            SignatureScheme::make(self.sign, HashAlgorithm::SHA384),
            SignatureScheme::make(self.sign, HashAlgorithm::SHA256)
        ];
        
        if self.sign == SignatureAlgorithm::RSA {
            our_preference.push(SignatureScheme::RSA_PSS_SHA512);
            our_preference.push(SignatureScheme::RSA_PSS_SHA384);
            our_preference.push(SignatureScheme::RSA_PSS_SHA256);
        }
        our_preference.retain(|pref| offered.contains(pref));
        our_preference
    }
    
    pub fn get_aead_alg(&self) -> &'static ring::aead::Algorithm {
        match self.bulk {
            BulkAlgorithm::AES_128_GCM => &ring::aead::AES_128_GCM,
            BulkAlgorithm::AES_256_GCM => &ring::aead::AES_256_GCM,
            BulkAlgorithm::CHACHA20_POLY1305 => &ring::aead::CHACHA20_POLY1305,
        }
    }
    
    
    pub fn key_block_len(&self) -> usize {
        (self.enc_key_len + self.fixed_iv_len) * 2 + self.explicit_nonce_len
    }
    
    pub fn usable_for_version(&self, version: ProtocolVersion) -> bool {
        match version {
            ProtocolVersion::TLSv1_3 => self.sign == SignatureAlgorithm::Anonymous,
            ProtocolVersion::TLSv1_2 => self.sign != SignatureAlgorithm::Anonymous,
            _ => false,
        }
    }
    
    pub fn can_resume_to(&self, new_suite: &SupportedCipherSuite) -> bool {
        if self.usable_for_version(ProtocolVersion::TLSv1_3) &&
            new_suite.usable_for_version(ProtocolVersion::TLSv1_3) {
            
            
            self.hash == new_suite.hash
        } else if self.usable_for_version(ProtocolVersion::TLSv1_2) &&
            new_suite.usable_for_version(ProtocolVersion::TLSv1_2) {
            
            
            self.suite == new_suite.suite
        } else {
            
            false
        }
    }
}
pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite =
    SupportedCipherSuite {
        suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
        kx: KeyExchangeAlgorithm::ECDHE,
        sign: SignatureAlgorithm::ECDSA,
        bulk: BulkAlgorithm::CHACHA20_POLY1305,
        hash: HashAlgorithm::SHA256,
        enc_key_len: 32,
        fixed_iv_len: 12,
        explicit_nonce_len: 0,
        hkdf_algorithm: ring::hkdf::HKDF_SHA256,
    };
pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite =
    SupportedCipherSuite {
        suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
        kx: KeyExchangeAlgorithm::ECDHE,
        sign: SignatureAlgorithm::RSA,
        bulk: BulkAlgorithm::CHACHA20_POLY1305,
        hash: HashAlgorithm::SHA256,
        enc_key_len: 32,
        fixed_iv_len: 12,
        explicit_nonce_len: 0,
        hkdf_algorithm: ring::hkdf::HKDF_SHA256,
    };
pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    kx: KeyExchangeAlgorithm::ECDHE,
    sign: SignatureAlgorithm::RSA,
    bulk: BulkAlgorithm::AES_128_GCM,
    hash: HashAlgorithm::SHA256,
    enc_key_len: 16,
    fixed_iv_len: 4,
    explicit_nonce_len: 8,
    hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
    kx: KeyExchangeAlgorithm::ECDHE,
    sign: SignatureAlgorithm::RSA,
    bulk: BulkAlgorithm::AES_256_GCM,
    hash: HashAlgorithm::SHA384,
    enc_key_len: 32,
    fixed_iv_len: 4,
    explicit_nonce_len: 8,
    hkdf_algorithm: ring::hkdf::HKDF_SHA384,
};
pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
    kx: KeyExchangeAlgorithm::ECDHE,
    sign: SignatureAlgorithm::ECDSA,
    bulk: BulkAlgorithm::AES_128_GCM,
    hash: HashAlgorithm::SHA256,
    enc_key_len: 16,
    fixed_iv_len: 4,
    explicit_nonce_len: 8,
    hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
    kx: KeyExchangeAlgorithm::ECDHE,
    sign: SignatureAlgorithm::ECDSA,
    bulk: BulkAlgorithm::AES_256_GCM,
    hash: HashAlgorithm::SHA384,
    enc_key_len: 32,
    fixed_iv_len: 4,
    explicit_nonce_len: 8,
    hkdf_algorithm: ring::hkdf::HKDF_SHA384,
};
pub static TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256,
    kx: KeyExchangeAlgorithm::BulkOnly,
    sign: SignatureAlgorithm::Anonymous,
    bulk: BulkAlgorithm::CHACHA20_POLY1305,
    hash: HashAlgorithm::SHA256,
    enc_key_len: 32,
    fixed_iv_len: 12,
    explicit_nonce_len: 0,
    hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS13_AES_256_GCM_SHA384,
    kx: KeyExchangeAlgorithm::BulkOnly,
    sign: SignatureAlgorithm::Anonymous,
    bulk: BulkAlgorithm::AES_256_GCM,
    hash: HashAlgorithm::SHA384,
    enc_key_len: 32,
    fixed_iv_len: 12,
    explicit_nonce_len: 0,
    hkdf_algorithm: ring::hkdf::HKDF_SHA384,
};
pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite {
    suite: CipherSuite::TLS13_AES_128_GCM_SHA256,
    kx: KeyExchangeAlgorithm::BulkOnly,
    sign: SignatureAlgorithm::Anonymous,
    bulk: BulkAlgorithm::AES_128_GCM,
    hash: HashAlgorithm::SHA256,
    enc_key_len: 16,
    fixed_iv_len: 12,
    explicit_nonce_len: 0,
    hkdf_algorithm: ring::hkdf::HKDF_SHA256,
};
pub static ALL_CIPHERSUITES: [&'static SupportedCipherSuite; 9] =
    [
     &TLS13_CHACHA20_POLY1305_SHA256,
     &TLS13_AES_256_GCM_SHA384,
     &TLS13_AES_128_GCM_SHA256,
     
     &TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
     &TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
     &TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
     &TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
     &TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
     &TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256];
pub fn choose_ciphersuite_preferring_client(client_suites: &[CipherSuite],
                                            server_suites: &[&'static SupportedCipherSuite])
                                            -> Option<&'static SupportedCipherSuite> {
    for client_suite in client_suites {
        if let Some(selected) = server_suites.iter().find(|x| *client_suite == x.suite) {
            return Some(*selected);
        }
    }
    None
}
pub fn choose_ciphersuite_preferring_server(client_suites: &[CipherSuite],
                                            server_suites: &[&'static SupportedCipherSuite])
                                            -> Option<&'static SupportedCipherSuite> {
    if let Some(selected) = server_suites.iter().find(|x| client_suites.contains(&x.suite)) {
        return Some(*selected);
    }
    None
}
pub fn reduce_given_sigalg(all: &[&'static SupportedCipherSuite],
                           sigalg: SignatureAlgorithm)
                           -> Vec<&'static SupportedCipherSuite> {
    all.iter()
        .filter(|&&suite| suite.sign == SignatureAlgorithm::Anonymous || suite.sign == sigalg)
        .cloned()
        .collect()
}
pub fn reduce_given_version(all: &[&'static SupportedCipherSuite],
                            version: ProtocolVersion)
                            -> Vec<&'static SupportedCipherSuite> {
    all.iter()
        .filter(|&&suite| suite.usable_for_version(version))
        .cloned()
        .collect()
}
#[cfg(test)]
mod test {
    use crate::msgs::enums::CipherSuite;
    #[test]
    fn test_client_pref() {
        let client = vec![CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                          CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384];
        let server = vec![&super::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
                          &super::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256];
        let chosen = super::choose_ciphersuite_preferring_client(&client, &server);
        assert!(chosen.is_some());
        assert_eq!(chosen.unwrap(),
                   &super::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256);
    }
    #[test]
    fn test_server_pref() {
        let client = vec![CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
                          CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384];
        let server = vec![&super::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
                          &super::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256];
        let chosen = super::choose_ciphersuite_preferring_server(&client, &server);
        assert!(chosen.is_some());
        assert_eq!(chosen.unwrap(),
                   &super::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384);
    }
}