/* Copyright (c) Fortanix, Inc.
 *
 * Licensed under the GNU General Public License, version 2 <LICENSE-GPL or
 * https://www.gnu.org/licenses/gpl-2.0.html> or the Apache License, Version
 * 2.0 <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>, at your
 * option. This file may not be copied, modified, or distributed except
 * according to those terms. */

use mbedtls_sys::ECDSA_MAX_LEN as MBEDTLS_ECDSA_MAX_LEN;
use mbedtls_sys::*;

use crate::error::{Error, IntoResult, Result};

define!(
    #[c_ty(ecp_group_id)]
    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
    enum EcGroupId {
        None = ECP_DP_NONE,
        SecP192R1 = ECP_DP_SECP192R1,
        SecP224R1 = ECP_DP_SECP224R1,
        SecP256R1 = ECP_DP_SECP256R1,
        SecP384R1 = ECP_DP_SECP384R1,
        SecP521R1 = ECP_DP_SECP521R1,
        Bp256R1 = ECP_DP_BP256R1,
        Bp384R1 = ECP_DP_BP384R1,
        Bp512R1 = ECP_DP_BP512R1,
        Curve25519 = ECP_DP_CURVE25519,
        SecP192K1 = ECP_DP_SECP192K1,
        SecP224K1 = ECP_DP_SECP224K1,
        SecP256K1 = ECP_DP_SECP256K1,
        Curve448 = ECP_DP_CURVE448,
    }
);

impl From<ecp_group_id> for EcGroupId {
    fn from(inner: ecp_group_id) -> EcGroupId {
        match inner {
            ECP_DP_NONE => EcGroupId::None,
            ECP_DP_SECP192R1 => EcGroupId::SecP192R1,
            ECP_DP_SECP224R1 => EcGroupId::SecP224R1,
            ECP_DP_SECP256R1 => EcGroupId::SecP256R1,
            ECP_DP_SECP384R1 => EcGroupId::SecP384R1,
            ECP_DP_SECP521R1 => EcGroupId::SecP521R1,
            ECP_DP_BP256R1 => EcGroupId::Bp256R1,
            ECP_DP_BP384R1 => EcGroupId::Bp384R1,
            ECP_DP_BP512R1 => EcGroupId::Bp512R1,
            ECP_DP_CURVE25519 => EcGroupId::Curve25519,
            ECP_DP_SECP192K1 => EcGroupId::SecP192K1,
            ECP_DP_SECP224K1 => EcGroupId::SecP224K1,
            ECP_DP_SECP256K1 => EcGroupId::SecP256K1,
            ECP_DP_CURVE448 => EcGroupId::Curve448,
            _ => panic!("Invalid EC group ID"),
        }
    }
}

/// Maximum size of an elliptic curve signature generated by `sign`, and the minimum
/// buffer size input to `sign`.
pub const ECDSA_MAX_LEN: usize = MBEDTLS_ECDSA_MAX_LEN as usize;

define!(
    #[c_ty(ecp_keypair)]
    #[repr(C)]
    struct EcpKeypair;
    const init: fn() -> Self = ecp_keypair_init;
    const drop: fn(&mut Self) = ecp_keypair_free;
    impl<'a> Into<ptr> {}
    impl<'a> UnsafeFrom<ptr> {}
);

define!(
    #[c_ty(ecdh_context)]
    #[repr(C)]
    struct Ecdh;
    const init: fn() -> Self = ecdh_init;
    const drop: fn(&mut Self) = ecdh_free;
    impl<'a> Into<ptr> {}
);

impl Ecdh {
    pub fn from_keys(private: &EcpKeypair, public: &EcpKeypair) -> Result<Ecdh> {
        if public.inner.grp.id == ECP_DP_NONE || public.inner.grp.id != private.inner.grp.id {
            return Err(Error::EcpBadInputData);
        }

        let mut ret = Self::init();
        unsafe {
            ecp_group_copy(&mut ret.inner.grp, &private.inner.grp).into_result()?;
            mpi_copy(&mut ret.inner.d, &private.inner.d).into_result()?;
            ecp_copy(&mut ret.inner.Qp, &public.inner.Q).into_result()?;
        }
        Ok(ret)
    }

    pub fn calc_secret<F: crate::rng::Random>(
        &mut self,
        shared: &mut [u8],
        rng: &mut F,
    ) -> Result<usize> {
        let mut olen = 0;
        unsafe {
            ecdh_calc_secret(
                &mut self.inner,
                &mut olen,
                shared.as_mut_ptr(),
                shared.len(),
                Some(F::call),
                rng.data_ptr(),
            )
            .into_result()?
        };
        Ok(olen)
    }
}

#[cfg(test)]
mod tests {
    use crate::pk::Pk;

    #[test]
    fn p192_dh() {
        // From NIST CAVS 14.1, ECC CDH Primitive (SP800-56A Section 5.7.1.2),
        // Generated on Mon Nov 19 10:52:17 2012. Curve P-192, test vector
        // number 0.
        const PRIVATE_P192: &'static [u8] = b"-----BEGIN PRIVATE KEY-----
MG8CAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQEEVTBTAgEBBBjxfT/qNnt000CFHKQn
DcskwnH0Rb7Z1SehNAMyAASxUFNAH1coVjfsMkwc0hOeOmfeNzkjSzfyacFYY3SC
qtZEzWkt0dPvLIp8SeOJ9/Y=
-----END PRIVATE KEY-----\0";

        const PUBLIC_P192: &'static [u8] = b"-----BEGIN PUBLIC KEY-----
MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEQupt2Zad0qYf6hqsf46Y7cyJbG5V
hXzA375dfGH6yIsRgRveMo6KDRK/AanSBLUj
-----END PUBLIC KEY-----\0";

        const DH_P192: &'static [u8] = &[
            0x80, 0x3d, 0x8a, 0xb2, 0xe5, 0xb6, 0xe6, 0xfc, 0xa7, 0x15, 0x73, 0x7c, 0x3a, 0x82,
            0xf7, 0xce, 0x3c, 0x78, 0x31, 0x24, 0xf6, 0xd5, 0x1c, 0xd0,
        ];

        let mut k_pr = Pk::from_private_key(PRIVATE_P192, None).unwrap();
        let k_pb = Pk::from_public_key(PUBLIC_P192).unwrap();
        let mut out = [0; 192 / 8];
        let len = k_pr
            .agree(&k_pb, &mut out, &mut crate::test_support::rand::test_rng())
            .unwrap();
        assert_eq!(len, DH_P192.len());
        assert_eq!(out, DH_P192);
    }
}
