// Copyright 2015-2017 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{
    error,
    signature::{self, Ed25519KeyPair, KeyPair},
    test, test_file,
};

#[test]
fn test_ed25519_traits() {
    test::compile_time_assert_send::<Ed25519KeyPair>();
    test::compile_time_assert_sync::<Ed25519KeyPair>();
}

/// Test vectors from `BoringSSL`.
#[test]
fn test_signature_ed25519() {
    test::run(
        test_file!("data/ed25519_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");
            let seed = test_case.consume_bytes("SEED");
            assert_eq!(32, seed.len());

            let public_key = test_case.consume_bytes("PUB");
            assert_eq!(32, public_key.len());

            let msg = test_case.consume_bytes("MESSAGE");

            let expected_sig = test_case.consume_bytes("SIG");

            {
                let key_pair =
                    Ed25519KeyPair::from_seed_and_public_key(&seed, &public_key).unwrap();
                let actual_sig = key_pair.sign(&msg);
                assert_eq!(&expected_sig[..], actual_sig.as_ref());
            }

            // Test PKCS#8 generation, parsing, and private-to-public calculations.
            let rng = test::rand::FixedSliceRandom { bytes: &seed };
            let pkcs8 = Ed25519KeyPair::generate_pkcs8v1(&rng).unwrap();
            let key_pair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8.as_ref()).unwrap();
            assert_eq!(public_key, key_pair.public_key().as_ref());

            // Test Signature generation.
            let actual_sig = key_pair.sign(&msg);
            assert_eq!(&expected_sig[..], actual_sig.as_ref());

            // Test Signature verification.
            test_signature_verification(&public_key, &msg, &expected_sig, Ok(()));

            let mut tampered_sig = expected_sig;
            tampered_sig[0] ^= 1;

            test_signature_verification(&public_key, &msg, &tampered_sig, Err(error::Unspecified));

            Ok(())
        },
    );
}

/// Test vectors from `BoringSSL`.
#[test]
fn test_signature_ed25519_verify() {
    test::run(
        test_file!("data/ed25519_verify_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            let public_key = test_case.consume_bytes("PUB");
            let msg = test_case.consume_bytes("MESSAGE");
            let sig = test_case.consume_bytes("SIG");
            let expected_result = match test_case.consume_string("Result").as_str() {
                "P" => Ok(()),
                "F" => Err(error::Unspecified),
                s => panic!("{s:?} is not a valid result"),
            };
            test_signature_verification(&public_key, &msg, &sig, expected_result);
            Ok(())
        },
    );
}
fn test_signature_verification(
    public_key: &[u8],
    msg: &[u8],
    sig: &[u8],
    expected_result: Result<(), error::Unspecified>,
) {
    assert_eq!(
        expected_result,
        signature::UnparsedPublicKey::new(&signature::ED25519, public_key).verify(msg, sig)
    );
}

#[test]
fn test_ed25519_from_seed_and_public_key_misuse() {
    const PRIVATE_KEY: &[u8] = include_bytes!("data/ed25519_test_private_key.bin");
    const PUBLIC_KEY: &[u8] = include_bytes!("data/ed25519_test_public_key.bin");

    assert!(Ed25519KeyPair::from_seed_and_public_key(PRIVATE_KEY, PUBLIC_KEY).is_ok());

    // Truncated private key.
    assert!(Ed25519KeyPair::from_seed_and_public_key(&PRIVATE_KEY[..31], PUBLIC_KEY).is_err());

    // Truncated public key.
    assert!(Ed25519KeyPair::from_seed_and_public_key(PRIVATE_KEY, &PUBLIC_KEY[..31]).is_err());

    // Swapped public and private key.
    assert!(Ed25519KeyPair::from_seed_and_public_key(PUBLIC_KEY, PRIVATE_KEY).is_err());
}

#[test]
fn test_ed25519_from_pkcs8() {
    fn check_result(
        input: &Vec<u8>,
        result: Result<Ed25519KeyPair, error::KeyRejected>,
        error: Option<String>,
    ) {
        match (result, error) {
            (Ok(_), None) => (),
            (Err(e), None) => panic!(
                "Failed with error \"{}\", but expected to succeed: \"{}\"",
                e,
                test::to_hex(input)
            ),
            (Ok(_), Some(e)) => panic!(
                "Succeeded, but expected error \"{}\": {}",
                e,
                test::to_hex(input)
            ),
            (Err(actual), Some(expected)) => {
                assert_eq!(
                    actual.description_(),
                    expected,
                    "Input: {}",
                    test::to_hex(input)
                );
            }
        };
    }

    // Just test that we can parse the input.
    test::run(
        test_file!("data/ed25519_from_pkcs8_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");
            let input = test_case.consume_bytes("Input");
            let error = test_case.consume_optional_string("Error");

            check_result(&input, Ed25519KeyPair::from_pkcs8(&input), error);

            Ok(())
        },
    );

    // Just test that we can parse the input.
    test::run(
        test_file!("data/ed25519_from_pkcs8_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");
            let input = test_case.consume_bytes("Input");
            let error = test_case.consume_optional_string("Error");

            check_result(
                &input,
                Ed25519KeyPair::from_pkcs8_maybe_unchecked(&input),
                error,
            );

            Ok(())
        },
    );
}

#[test]
fn ed25519_test_public_key_coverage() {
    const PRIVATE_KEY: &[u8] = include_bytes!("data/ed25519_test_private_key.p8");
    const PUBLIC_KEY: &[u8] = include_bytes!("data/ed25519_test_public_key.der");
    const PUBLIC_KEY_DEBUG: &str =
        "PublicKey(\"0590d26d769c711c3d8cbffc41f5b4665d63feb3d17765c3b630d50bf5c188fb\")";

    let key_pair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(PRIVATE_KEY).unwrap();

    // Test `AsRef<[u8]>`
    assert_eq!(key_pair.public_key().as_ref(), PUBLIC_KEY);

    // Test `Clone`.
    #[allow(clippy::clone_on_copy)]
    let _: <Ed25519KeyPair as KeyPair>::PublicKey = key_pair.public_key().clone();

    // Test `Copy`.
    let _: <Ed25519KeyPair as KeyPair>::PublicKey = *key_pair.public_key();

    // Test `Debug`.
    assert_eq!(PUBLIC_KEY_DEBUG, format!("{:?}", key_pair.public_key()));
    assert_eq!(
        format!(
            "Ed25519KeyPair {{ public_key: {:?} }}",
            key_pair.public_key()
        ),
        format!("{key_pair:?}")
    );
}


// Copyright 2015 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{aead, cipher, digest, error, hkdf, hmac, test, test_file};

#[test]
fn hkdf_tests() {
    test::run(test_file!("data/hkdf_tests.txt"), |section, test_case| {
        assert_eq!(section, "");
        let alg = {
            let digest_alg = test_case
                .consume_digest_alg("Hash")
                .ok_or(error::Unspecified)?;
            if digest_alg == &digest::SHA256 {
                hkdf::HKDF_SHA256
            } else {
                // TODO: add test vectors for other algorithms
                panic!("unsupported algorithm: {digest_alg:?}");
            }
        };
        let secret = test_case.consume_bytes("IKM");
        let salt = test_case.consume_bytes("salt");
        let info = test_case.consume_bytes("info");
        test_case.consume_bytes("PRK");
        let expected_out = test_case.consume_bytes("OKM");

        let salt = hkdf::Salt::new(alg, &salt);

        // TODO: test multi-part info, especially with empty parts.
        let My(out) = salt
            .extract(&secret)
            .expand(&[&info], My(expected_out.len()))
            .unwrap()
            .into();
        assert_eq!(out, expected_out);

        Ok(())
    });
}

#[test]
fn hkdf_output_len_tests() {
    for &alg in &[hkdf::HKDF_SHA256, hkdf::HKDF_SHA384, hkdf::HKDF_SHA512] {
        const MAX_BLOCKS: usize = 255;

        let salt = hkdf::Salt::new(alg, &[]);
        let prk = salt.extract(&[]); // TODO: enforce minimum length.

        {
            // Test zero length.
            let okm = prk.expand(&[b"info"], My(0)).unwrap();
            let result: My<Vec<u8>> = okm.into();
            assert_eq!(&result.0, &[]);
        }

        let max_out_len = MAX_BLOCKS * alg.hmac_algorithm().digest_algorithm().output_len;

        {
            // Test maximum length output succeeds.
            let okm = prk.expand(&[b"info"], My(max_out_len)).unwrap();
            let result: My<Vec<u8>> = okm.into();
            assert_eq!(result.0.len(), max_out_len);
        }

        {
            // Test too-large output fails.
            assert!(prk.expand(&[b"info"], My(max_out_len + 1)).is_err());
        }

        {
            // Test length mismatch (smaller).
            let okm = prk.expand(&[b"info"], My(2)).unwrap();
            let mut buf = [0u8; 1];
            assert_eq!(okm.fill(&mut buf), Err(error::Unspecified));
        }

        {
            // Test length mismatch (larger).
            let okm = prk.expand(&[b"info"], My(2)).unwrap();
            let mut buf = [0u8; 3];
            assert_eq!(okm.fill(&mut buf), Err(error::Unspecified));
        }

        {
            // Control for above two tests.
            let okm = prk.expand(&[b"info"], My(2)).unwrap();
            let mut buf = [0u8; 2];
            assert_eq!(okm.fill(&mut buf), Ok(()));
        }
    }
}

#[test]
/// Try creating various key types via HKDF.
fn hkdf_key_types() {
    for &alg in &[
        hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY,
        hkdf::HKDF_SHA256,
        hkdf::HKDF_SHA384,
        hkdf::HKDF_SHA512,
    ] {
        let salt = hkdf::Salt::new(alg, &[]);
        let prk = salt.extract(&[]);
        let okm = prk.expand(&[b"info"], alg.hmac_algorithm()).unwrap();
        let hmac_key = hmac::Key::from(okm);
        assert_eq!(hmac_key.algorithm(), alg.hmac_algorithm());

        let okm = prk.expand(&[b"info"], alg).unwrap();
        let hkdf_salt_key = hkdf::Salt::from(okm);
        assert_eq!(hkdf_salt_key.algorithm(), alg);

        let okm = prk.expand(&[b"info"], alg).unwrap();
        let _hkdf_prk_key = hkdf::Prk::from(okm);

        for aead_alg in [
            &aead::AES_256_GCM,
            &aead::AES_128_GCM,
            &aead::CHACHA20_POLY1305,
        ] {
            let okm = prk.expand(&[b"info"], aead_alg).unwrap();
            let _aead_prk_key = aead::UnboundKey::from(okm);
        }

        for cipher_alg in [&cipher::AES_256, &cipher::AES_128] {
            let okm = prk.expand(&[b"info"], cipher_alg).unwrap();
            let _aes_prk_key = cipher::UnboundCipherKey::from(okm);
        }
    }
}

#[test]
fn hkdf_clone_tests() {
    for &alg in &[
        hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY,
        hkdf::HKDF_SHA256,
        hkdf::HKDF_SHA384,
        hkdf::HKDF_SHA512,
    ] {
        // Coverage sanity check.
        assert_eq!(alg.clone(), alg);

        // Only using this API to construct a simple PRK.
        let prk = hkdf::Prk::new_less_safe(alg, &[0; 32]);
        let result: My<Vec<u8>> = prk
            .expand(&[b"info"], My(digest::MAX_OUTPUT_LEN))
            .unwrap()
            .into();

        let prk_clone = prk.clone();
        let result_2: My<Vec<u8>> = prk_clone
            .expand(&[b"info"], My(digest::MAX_OUTPUT_LEN))
            .unwrap()
            .into();
        assert_eq!(result, result_2);
    }
}

#[test]
fn hkdf_thread_safeness() {
    use std::thread;

    lazy_static::lazy_static! {
        /// Compute the Initial salt once, as the seed is constant
        static ref SECRET_KEY: hkdf::Salt = hkdf::Salt::new(hkdf::HKDF_SHA256, b"this is a test!");
    }

    // Compute the OKM, so we have something to compare to.
    let okm: My<Vec<u8>> = SECRET_KEY
        .extract(b"secret")
        .expand(&[b"info"], My(digest::MAX_OUTPUT_LEN))
        .unwrap()
        .into();

    let mut join_handles = Vec::new();
    for _ in 1..100 {
        let join_handle = thread::spawn(|| {
            let okm = SECRET_KEY
                .extract(b"secret")
                .expand(&[b"info"], My(digest::MAX_OUTPUT_LEN))
                .unwrap()
                .into();
            okm
        });
        join_handles.push(join_handle);
    }
    for handle in join_handles {
        let thread_okm: My<Vec<u8>> = handle.join().unwrap();
        assert_eq!(thread_okm, okm);
    }
}

/// Generic newtype wrapper that lets us implement traits for externally-defined
/// types.
#[derive(Debug, PartialEq)]
struct My<T: core::fmt::Debug + PartialEq>(T);

impl hkdf::KeyType for My<usize> {
    fn len(&self) -> usize {
        self.0
    }
}

impl From<hkdf::Okm<'_, My<usize>>> for My<Vec<u8>> {
    fn from(okm: hkdf::Okm<My<usize>>) -> Self {
        let mut r = vec![0u8; okm.len().0];
        okm.fill(&mut r).unwrap();
        Self(r)
    }
}


// Copyright 2015-2016 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::hmac::sign;
use aws_lc_rs::{digest, hmac, test, test_file};

#[test]
fn hmac_tests() {
    test::run(test_file!("data/hmac_tests.txt"), |section, test_case| {
        assert_eq!(section, "");
        let digest_alg = test_case.consume_digest_alg("HMAC");
        let key_value = test_case.consume_bytes("Key");
        let mut input = test_case.consume_bytes("Input");
        let output = test_case.consume_bytes("Output");

        let algorithm = {
            let digest_alg = match digest_alg {
                Some(digest_alg) => digest_alg,
                None => {
                    return Ok(());
                } // Unsupported digest algorithm
            };
            if digest_alg == &digest::SHA1_FOR_LEGACY_USE_ONLY {
                hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY
            } else if digest_alg == &digest::SHA256 {
                hmac::HMAC_SHA256
            } else if digest_alg == &digest::SHA384 {
                hmac::HMAC_SHA384
            } else if digest_alg == &digest::SHA512 {
                hmac::HMAC_SHA512
            } else {
                unreachable!()
            }
        };

        hmac_test_case_inner(algorithm, &key_value[..], &input[..], &output[..], true);

        // Tamper with the input and check that verification fails.
        if input.is_empty() {
            input.push(0);
        } else {
            input[0] ^= 1;
        }

        hmac_test_case_inner(algorithm, &key_value[..], &input[..], &output[..], false);

        Ok(())
    });
}

fn hmac_test_case_inner(
    algorithm: hmac::Algorithm,
    key_value: &[u8],
    input: &[u8],
    output: &[u8],
    is_ok: bool,
) {
    let key = hmac::Key::new(algorithm, key_value);

    // One-shot API.
    {
        let signature = hmac::sign(&key, input);
        assert_eq!(is_ok, signature.as_ref() == output);
        assert_eq!(is_ok, hmac::verify(&key, input, output).is_ok());
    }

    // Multi-part API, one single part.
    {
        let mut s_ctx = hmac::Context::with_key(&key);
        s_ctx.update(input);
        let signature = s_ctx.sign();
        assert_eq!(is_ok, signature.as_ref() == output);
    }

    // Multi-part API, byte by byte.
    {
        let mut ctx = hmac::Context::with_key(&key);
        for b in input {
            ctx.update(&[*b]);
        }
        let signature = ctx.sign();
        assert_eq!(is_ok, signature.as_ref() == output);
    }
}

#[test]
fn hmac_debug() {
    let key = hmac::Key::new(hmac::HMAC_SHA256, &[0; 32]);
    assert_eq!("Key { algorithm: SHA256 }", format!("{:?}", &key));

    let ctx = hmac::Context::with_key(&key);
    assert_eq!("Context { algorithm: SHA256 }", format!("{:?}", &ctx));

    assert_eq!("Algorithm(SHA256)", format!("{:?}", hmac::HMAC_SHA256));
}

#[test]
fn hmac_traits() {
    test::compile_time_assert_send::<hmac::Key>();
    test::compile_time_assert_sync::<hmac::Key>();
}

#[test]
fn hmac_thread_safeness() {
    use std::thread;
    lazy_static::lazy_static! {
        static ref SECRET_KEY: hmac::Key = hmac::Key::new(hmac::HMAC_SHA256, b"this is a test!");
        static ref MSG: Vec<u8> = vec![1u8; 256];
    }

    let signature = sign(&SECRET_KEY, &MSG);

    let mut join_handles = Vec::new();
    for _ in 1..100 {
        let join_handle = thread::spawn(|| {
            let signature = sign(&SECRET_KEY, &MSG);
            for _ in 1..100 {
                let my_signature = sign(&SECRET_KEY, &MSG);
                assert_eq!(signature.as_ref(), my_signature.as_ref());
            }
            signature
        });
        join_handles.push(join_handle);
    }
    for handle in join_handles {
        let thread_signature = handle.join().unwrap();
        assert_eq!(thread_signature.as_ref(), signature.as_ref());
    }
}


// Copyright 2015-2017 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{digest, error, pbkdf2, test, test_file};
use core::num::NonZeroU32;

/// Test vectors from `BoringSSL`, Go, and other sources.
#[test]
fn pbkdf2_tests() {
    test::run(test_file!("data/pbkdf2_tests.txt"), |section, test_case| {
        assert_eq!(section, "");
        let algorithm = {
            let digest_alg = test_case.consume_digest_alg("Hash").unwrap();
            if digest_alg == &digest::SHA1_FOR_LEGACY_USE_ONLY {
                pbkdf2::PBKDF2_HMAC_SHA1
            } else if digest_alg == &digest::SHA256 {
                pbkdf2::PBKDF2_HMAC_SHA256
            } else if digest_alg == &digest::SHA384 {
                pbkdf2::PBKDF2_HMAC_SHA384
            } else if digest_alg == &digest::SHA512 {
                pbkdf2::PBKDF2_HMAC_SHA512
            } else {
                unreachable!()
            }
        };
        #[allow(clippy::cast_possible_truncation)]
        let iterations = test_case.consume_usize("c") as u32;
        let iterations = NonZeroU32::new(iterations).unwrap();
        let secret = test_case.consume_bytes("P");
        let salt = test_case.consume_bytes("S");
        let dk = test_case.consume_bytes("DK");
        let verify_expected_result = test_case.consume_string("Verify");
        let verify_expected_result = match verify_expected_result.as_str() {
            "OK" => Ok(()),
            "Err" => Err(error::Unspecified),
            _ => panic!("Unsupported value of \"Verify\""),
        };

        {
            let mut out = vec![0u8; dk.len()];
            pbkdf2::derive(algorithm, iterations, &salt, &secret, &mut out);
            assert_eq!(dk == out, verify_expected_result.is_ok() || dk.is_empty());
        }

        assert_eq!(
            pbkdf2::verify(algorithm, iterations, &salt, &secret, &dk),
            verify_expected_result
        );

        Ok(())
    });
}

/// The API documentation specifies that derive/verify should panic, if the designated output length
/// is too long. Ring checks for an output array length while pbkdf2 is being ran, while we check
/// the array length before everything is processed.
/// Most platforms will fail to allocate this much memory, so we only test on platforms which can.
#[cfg(all(target_arch = "x86_64", target_vendor = "apple"))]
#[cfg(test)]
mod tests {
    use aws_lc_rs::{digest, pbkdf2};
    use core::num::NonZeroU32;
    use mirai_annotations::assume;

    #[test]
    #[should_panic(expected = "derived key too long")]
    fn pbkdf2_derive_too_long() {
        let iterations = NonZeroU32::new(1_u32).unwrap();
        let max_usize32 = u32::MAX as usize;
        for &alg in &[
            pbkdf2::PBKDF2_HMAC_SHA1,
            pbkdf2::PBKDF2_HMAC_SHA256,
            pbkdf2::PBKDF2_HMAC_SHA384,
            pbkdf2::PBKDF2_HMAC_SHA512,
        ] {
            let output_len = match_pbkdf2_digest(&alg).output_len;
            assume!(output_len < 2048);
            let mut out = vec![0u8; max_usize32 * output_len + 1];
            pbkdf2::derive(alg, iterations, b"salt", b"password", &mut out);
        }
    }

    #[test]
    #[should_panic(expected = "derived key too long")]
    fn pbkdf2_verify_too_long() {
        let iterations = NonZeroU32::new(1_u32).unwrap();
        let max_usize32 = u32::MAX as usize;
        for &alg in &[
            pbkdf2::PBKDF2_HMAC_SHA1,
            pbkdf2::PBKDF2_HMAC_SHA256,
            pbkdf2::PBKDF2_HMAC_SHA384,
            pbkdf2::PBKDF2_HMAC_SHA512,
        ] {
            let out = vec![0u8; max_usize32 * match_pbkdf2_digest(&alg).output_len + 1];
            pbkdf2::verify(alg, iterations, b"salt", b"password", &out).unwrap();
        }
    }

    fn match_pbkdf2_digest(&algorithm: &pbkdf2::Algorithm) -> &digest::Algorithm {
        if algorithm == pbkdf2::PBKDF2_HMAC_SHA1 {
            &digest::SHA1_FOR_LEGACY_USE_ONLY
        } else if algorithm == pbkdf2::PBKDF2_HMAC_SHA256 {
            &digest::SHA256
        } else if algorithm == pbkdf2::PBKDF2_HMAC_SHA384 {
            &digest::SHA384
        } else if algorithm == pbkdf2::PBKDF2_HMAC_SHA512 {
            &digest::SHA512
        } else {
            unreachable!()
        }
    }
}


// Copyright 2018 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::aead::quic;
use aws_lc_rs::{test, test_file};

#[test]
fn quic_aes_128() {
    test_quic(&quic::AES_128, test_file!("data/quic_aes_128_tests.txt"));
}

#[test]
fn quic_aes_256() {
    test_quic(&quic::AES_256, test_file!("data/quic_aes_256_tests.txt"));
}

#[test]
fn quic_chacha20() {
    test_quic(&quic::CHACHA20, test_file!("data/quic_chacha20_tests.txt"));
}

fn test_quic(alg: &'static quic::Algorithm, test_file: test::File) {
    test_sample_len(alg);

    test::run(test_file, |section, test_case| {
        assert_eq!(section, "");
        let key_bytes = test_case.consume_bytes("KEY");
        let sample = test_case.consume_bytes("SAMPLE");
        let mask = test_case.consume_bytes("MASK");

        let key = quic::HeaderProtectionKey::new(alg, &key_bytes)?;

        assert_eq!(mask.as_ref(), key.new_mask(&sample)?);

        Ok(())
    });
}

#[allow(clippy::range_plus_one)]
fn test_sample_len(alg: &'static quic::Algorithm) {
    let key_len = alg.key_len();
    let key_data = vec![0u8; key_len];

    let key = quic::HeaderProtectionKey::new(alg, &key_data).unwrap();

    let sample_len = 16;
    let sample_data = vec![0u8; sample_len + 2];

    // Sample is the right size.
    assert!(key.new_mask(&sample_data[..sample_len]).is_ok());

    // Sample is one byte too small.
    assert!(key.new_mask(&sample_data[..(sample_len - 1)]).is_err());

    // Sample is one byte too big.
    assert!(key.new_mask(&sample_data[..(sample_len + 1)]).is_err());

    // Sample is empty.
    assert!(key.new_mask(&[]).is_err());
}


// Copyright 2015-2016 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{
    rand::{self, SecureRandom as _},
    test,
};

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};

#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);

#[test]
fn test_system_random_lengths() {
    const LINUX_LIMIT: usize = 256;
    const WEB_LIMIT: usize = 65536;

    // Test that `fill` succeeds for various interesting lengths. `256` and
    // multiples thereof are interesting because that's an edge case for
    // `getrandom` on Linux.
    let lengths = [
        0,
        1,
        2,
        3,
        96,
        LINUX_LIMIT - 1,
        LINUX_LIMIT,
        LINUX_LIMIT + 1,
        LINUX_LIMIT * 2,
        511,
        512,
        513,
        4096,
        WEB_LIMIT - 1,
        WEB_LIMIT,
        WEB_LIMIT + 1,
        WEB_LIMIT * 2,
    ];

    for len in &lengths {
        let mut buf = vec![0; *len];

        let rng = rand::SystemRandom::new();
        assert!(rng.fill(&mut buf).is_ok());

        // If `len` < 96 then there's a big chance of false positives, but
        // otherwise the likelihood of a false positive is so too low to
        // worry about.
        if *len >= 96 {
            assert!(buf.iter().any(|x| *x != 0));
        }
    }
}

#[test]
fn test_system_random_traits() {
    test::compile_time_assert_clone::<rand::SystemRandom>();
    test::compile_time_assert_send::<rand::SystemRandom>();

    assert_eq!(
        "SystemRandom(())",
        format!("{:?}", rand::SystemRandom::new())
    );
}


// Copyright 2017 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::signature::{KeyPair, RsaParameters, RsaSubjectPublicKey};
use aws_lc_rs::test::to_hex_upper;
use aws_lc_rs::{rand, signature, signature::RsaKeyPair, test, test_file};

#[test]
fn rsa_traits() {
    test::compile_time_assert_send::<RsaKeyPair>();
    test::compile_time_assert_sync::<RsaKeyPair>();
    test::compile_time_assert_send::<signature::RsaSubjectPublicKey>();
    test::compile_time_assert_sync::<signature::RsaSubjectPublicKey>();
    test::compile_time_assert_send::<signature::RsaPublicKeyComponents<&[u8]>>();
    test::compile_time_assert_sync::<signature::RsaPublicKeyComponents<&[u8]>>();
    test::compile_time_assert_send::<signature::RsaPublicKeyComponents<Vec<u8>>>();
    test::compile_time_assert_sync::<signature::RsaPublicKeyComponents<Vec<u8>>>();
}

#[test]
fn rsa_from_pkcs8_test() {
    test::run(
        test_file!("data/rsa_from_pkcs8_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            let input = test_case.consume_bytes("Input");
            let error = test_case.consume_optional_string("Error");

            match (RsaKeyPair::from_pkcs8(&input), error) {
                (Ok(_), None) => (),
                (Err(e), None) => panic!("Failed with error \"{e}\", but expected to succeed"),
                (Ok(_), Some(e)) => panic!("Succeeded, but expected error \"{e}\""),
                (Err(actual), Some(expected)) => assert_eq!(
                    format!("{actual}"),
                    expected,
                    "Input: {}",
                    test::to_hex(input.as_slice())
                ),
            };

            Ok(())
        },
    );
}

#[test]
fn test_signature_rsa_pkcs1_sign() {
    let rng = rand::SystemRandom::new();
    test::run(
        test_file!("data/rsa_pkcs1_sign_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");
            let digest_name = test_case.consume_string("Digest");
            let alg = match digest_name.as_ref() {
                "SHA256" => &signature::RSA_PKCS1_SHA256,
                "SHA384" => &signature::RSA_PKCS1_SHA384,
                "SHA512" => &signature::RSA_PKCS1_SHA512,
                _ => panic!("Unsupported digest: {digest_name}"),
            };

            let private_key = test_case.consume_bytes("Key");
            let debug_msg = format!("Key = {}", test::to_hex(&private_key));
            let msg = test_case.consume_bytes("Msg");
            let expected = test_case.consume_bytes("Sig");
            let result = test_case.consume_string("Result");

            let key_pair = RsaKeyPair::from_der(&private_key);
            if result == "Fail-Invalid-Key" {
                assert!(key_pair.is_err(), "{}", &debug_msg);
                return Ok(());
            }
            let key_pair = key_pair.expect(&debug_msg);

            // XXX: This test is too slow on Android ARM Travis CI builds.
            // TODO: re-enable these tests on Android ARM.
            let mut actual = vec![0u8; key_pair.public_modulus_len()];
            key_pair
                .sign(alg, &rng, &msg, actual.as_mut_slice())
                .expect(&debug_msg);
            assert_eq!(actual.as_slice() == &expected[..], result == "Pass");
            Ok(())
        },
    );
}

#[test]
fn test_signature_rsa_pss_sign() {
    test::run(
        test_file!("data/rsa_pss_sign_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            let digest_name = test_case.consume_string("Digest");
            let alg = match digest_name.as_ref() {
                "SHA256" => &signature::RSA_PSS_SHA256,
                "SHA384" => &signature::RSA_PSS_SHA384,
                "SHA512" => &signature::RSA_PSS_SHA512,
                _ => panic!("Unsupported digest: {digest_name}"),
            };

            let result = test_case.consume_string("Result");
            let private_key = test_case.consume_bytes("Key");
            let key_pair = RsaKeyPair::from_der(&private_key);
            if key_pair.is_err() && result == "Fail-Invalid-Key" {
                return Ok(());
            }
            let key_pair = key_pair.unwrap();
            let msg = test_case.consume_bytes("Msg");
            let salt = test_case.consume_bytes("Salt");
            let _expected = test_case.consume_bytes("Sig");

            let rng = test::rand::FixedSliceRandom { bytes: &salt };

            let mut actual = vec![0u8; key_pair.public_modulus_len()];

            key_pair.sign(alg, &rng, &msg, actual.as_mut_slice())?;
            // TODO: *AWS-LC* does not allow the salt to be specified for PSS
            //assert_eq!(actual.as_slice() == &expected[..], result == "Pass");
            Ok(())
        },
    );
}

#[test]
fn test_signature_rsa_pkcs1_verify() {
    let sha1_params = &[
        &signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY,
        &signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY,
    ];
    let sha256_params = &[
        &signature::RSA_PKCS1_1024_8192_SHA256_FOR_LEGACY_USE_ONLY,
        &signature::RSA_PKCS1_2048_8192_SHA256,
    ];
    let sha384_params = &[
        &signature::RSA_PKCS1_2048_8192_SHA384,
        &signature::RSA_PKCS1_3072_8192_SHA384,
    ];
    let sha512_params = &[
        &signature::RSA_PKCS1_1024_8192_SHA512_FOR_LEGACY_USE_ONLY,
        &signature::RSA_PKCS1_2048_8192_SHA512,
    ];
    test::run(
        test_file!("data/rsa_pkcs1_verify_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            let digest_name = test_case.consume_string("Digest");
            let params: &[_] = match digest_name.as_ref() {
                "SHA1" => sha1_params,
                "SHA256" => sha256_params,
                "SHA384" => sha384_params,
                "SHA512" => sha512_params,
                _ => panic!("Unsupported digest: {digest_name}"),
            };
            let public_key = test_case.consume_bytes("Key");
            let msg = test_case.consume_bytes("Msg");
            let sig = test_case.consume_bytes("Sig");
            let is_valid = test_case.consume_string("Result") == "P";

            let key_bits = RsaParameters::public_modulus_len(&public_key);
            if key_bits.is_err() {
                assert!(!is_valid);
                return Ok(());
            }
            let key_bits = key_bits.unwrap();

            for &alg in params {
                let width_ok = key_bits >= alg.min_modulus_len();
                let width_ok = width_ok && key_bits <= alg.max_modulus_len();
                let actual_result =
                    signature::UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig);
                assert_eq!(actual_result.is_ok(), is_valid && width_ok);
            }

            Ok(())
        },
    );
}

#[test]
fn test_signature_rsa_pss_verify() {
    test::run(
        test_file!("data/rsa_pss_verify_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            let digest_name = test_case.consume_string("Digest");
            let alg = match digest_name.as_ref() {
                "SHA256" => &signature::RSA_PSS_2048_8192_SHA256,
                "SHA384" => &signature::RSA_PSS_2048_8192_SHA384,
                "SHA512" => &signature::RSA_PSS_2048_8192_SHA512,
                _ => panic!("Unsupported digest: {digest_name}"),
            };

            let public_key = test_case.consume_bytes("Key");
            let msg = test_case.consume_bytes("Msg");
            let sig = test_case.consume_bytes("Sig");
            let is_valid = test_case.consume_string("Result") == "P";

            let actual_result =
                signature::UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig);
            assert_eq!(actual_result.is_ok(), is_valid);

            Ok(())
        },
    );
}

// Test for `primitive::verify()`. Read public key parts from a file
// and use them to verify a signature.
#[test]
fn test_signature_rsa_primitive_verification() {
    test::run(
        test_file!("data/rsa_primitive_verify_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");
            let n = test_case.consume_bytes("n");
            let e = test_case.consume_bytes("e");
            let msg = test_case.consume_bytes("Msg");
            let sig = test_case.consume_bytes("Sig");
            let expected = test_case.consume_string("Result");
            let public_key = signature::RsaPublicKeyComponents { n: &n, e: &e };
            let result = public_key.verify(&signature::RSA_PKCS1_2048_8192_SHA256, &msg, &sig);
            assert_eq!(
                result.is_ok(),
                expected == "Pass",
                "N = {}",
                to_hex_upper(n)
            );
            Ok(())
        },
    );
}

#[test]
fn rsa_test_public_key_coverage() {
    const PRIVATE_KEY: &[u8] = include_bytes!("data/rsa_test_private_key_2048.p8");
    const PUBLIC_KEY: &[u8] = include_bytes!("data/rsa_test_public_key_2048.der");
    const PUBLIC_KEY_DEBUG: &str = include_str!("data/rsa_test_public_key_2048_debug.txt");

    let key_pair = RsaKeyPair::from_pkcs8(PRIVATE_KEY).unwrap();

    let pubkey = key_pair.public_key();
    // Test `AsRef<[u8]>`
    assert_eq!(pubkey.as_ref(), PUBLIC_KEY);

    // Test `Clone`.
    #[allow(let_underscore_drop)]
    let _: RsaSubjectPublicKey = pubkey.clone();

    #[cfg(feature = "ring-io")]
    assert_eq!(
        &[0x01, 0x00, 0x01],
        key_pair
            .public_key()
            .exponent()
            .big_endian_without_leading_zero()
    );

    // Test `Debug`
    assert_eq!(PUBLIC_KEY_DEBUG, format!("{:?}", key_pair.public_key()));
    assert_eq!(
        format!("RsaKeyPair {{ public_key: {:?} }}", key_pair.public_key()),
        format!("{key_pair:?}")
    );
}


#!/usr/bin/env rust-script
//! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//! SPDX-License-Identifier: Apache-2.0 OR ISC
//! To run, you will need to install rust-script:
//! ```
//! $ cargo install rust-script
//! ```
//!
//! After running the benchmarks, you can collect the data into a single CSV:
//! ```
//! $ find ./target/criterion -name "raw.csv" | xargs cat | sort | egrep -v "^group" > bench-aarch64-AL2.csv
//! ```
//!
//! ```cargo
//! [dependencies]
//! clap = { version = "4.0.29", features = ["derive"] }
//! itertools = "0.10.5"
//! ```

use std::cmp::Ordering;
use std::collections::HashMap;
use std::io::Write;
use std::ops::{Deref, DerefMut, Div};
use std::path::PathBuf;
use std::str::FromStr;
use std::string::String;
use std::{fs, io};

use clap::Parser;

use itertools::{
    EitherOrBoth::{Both, Left, Right},
    Itertools,
};

struct Stats(Vec<f64>);
struct FinalizedStats(Vec<f64>);

impl Deref for Stats {
    type Target = Vec<f64>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Deref for FinalizedStats {
    type Target = Vec<f64>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Stats {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl Stats {
    fn new() -> Self {
        Stats(Vec::new())
    }

    fn finalize(&self) -> FinalizedStats {
        let mut data = self.0.clone();
        data.sort_by(|a, b| a.partial_cmp(b).unwrap());
        FinalizedStats(data)
    }
}

impl FinalizedStats {
    fn percentile(&self, percent: f64) -> f64 {
        if percent >= 0.9999999 {
            return self[self.len() - 1];
        } else if percent < 0.0 {
            return self[0];
        }
        self[f64::trunc(percent * self.len() as f64) as usize]
    }

    fn median(&self) -> f64 {
        self.percentile(0.5)
    }

    fn max(&self) -> f64 {
        self[self.len() - 1]
    }

    fn min(&self) -> f64 {
        self[0]
    }
}

#[derive(Parser)]
#[command(about)]
struct Cli {
    /// CSV file to operate on
    csv_file: PathBuf,

    /// Compute the median
    #[arg(long, default_value = "false", required_unless_present_any(["max", "min", "percentile"]))]
    median: bool,

    /// Compute the maximum
    #[arg(long, default_value = "false", required_unless_present_any(["median", "min", "percentile"]))]
    max: bool,

    /// Compute the minimum
    #[arg(long, default_value = "false", required_unless_present_any(["median", "max", "percentile"]))]
    min: bool,

    /// Compute percentile
    #[arg(short, long, required_unless_present_any(["median", "max", "min"]))]
    percentile: Vec<u8>,

    /// Turn debugging information on
    #[arg(short, long)]
    verbose: bool,
}

impl Cli {
    fn header_line(&self) -> String {
        let mut line = String::new();
        if self.min {
            line = format!("{},aws-lc (min), ring (min), % diff (min)", line);
        }
        if self.median {
            line = format!("{},aws-lc (median), ring (median), % diff (median)", line);
        }
        if self.max {
            line = format!("{},aws-lc (max), ring (max), % diff (max)", line);
        }
        if self.percentile.len() > 0 {
            let mut percentiles = self.percentile.clone();
            percentiles.sort();
            for percentile in percentiles {
                line = format!(
                    "{},aws-lc (P{:02}), ring (P{:02}), % diff (P{:02})",
                    line, percentile, percentile, percentile
                );
            }
        }
        line
    }

    fn data_line(&self, aws_stats: &FinalizedStats, ring_stats: &FinalizedStats) -> String {
        let mut line = String::new();
        if self.min {
            let (aws, ring, rel) = compute(&aws_stats, &ring_stats, &FinalizedStats::min);
            line = format!("{},{:.2},{:.2},{:.2}", line, aws, ring, rel);
        }
        if self.median {
            let (aws, ring, rel) = compute(&aws_stats, &ring_stats, &FinalizedStats::median);
            line = format!("{},{:.2},{:.2},{:.2}", line, aws, ring, rel);
        }
        if self.max {
            let (aws, ring, rel) = compute(&aws_stats, &ring_stats, &FinalizedStats::max);
            line = format!("{},{:.2},{:.2},{:.2}", line, aws, ring, rel);
        }
        if self.percentile.len() > 0 {
            let mut percentiles = self.percentile.clone();
            percentiles.sort();
            for percentile in percentiles {
                let percentile = percentile as f64 / 100.0;
                let (aws, ring, rel) = compute(&aws_stats, &ring_stats, &|s: &FinalizedStats| {
                    s.percentile(percentile)
                });
                line = format!("{},{:.2},{:.2},{:.2}", line, aws, ring, rel);
            }
        }
        line
    }
}

fn insert_result(test: &str, avg: f64, results: &mut HashMap<String, Stats>) {
    if !results.contains_key(test) {
        results.insert(test.to_string(), Stats::new());
    }
    let results_vec = results.get_mut(test).unwrap();
    results_vec.push(avg);
}

fn compute<F>(aws_stats: &FinalizedStats, ring_stats: &FinalizedStats, comp: &F) -> (f64, f64, f64)
where
    F: Fn(&FinalizedStats) -> f64,
{
    let aws = comp(aws_stats);
    let ring = comp(ring_stats);
    let relative_percentage = 100.0 * (1.0 - aws / ring);
    (aws, ring, relative_percentage)
}

fn numerical_string_compare(a: &str, b: &str) -> Ordering {
    let mut number_compare = false;
    let mut a_num = 0u32;
    let mut b_num = 0u32;

    for pair in a.chars().into_iter().zip_longest(b.chars().into_iter()) {
        match pair {
            Both(ac, bc) => {
                if ac.is_digit(10) {
                    if bc.is_digit(10) {
                        if ac.cmp(&bc).is_eq() {
                            continue;
                        }
                        a_num *= 10;
                        a_num += ac.to_digit(10).unwrap();
                        b_num *= 10;
                        b_num += bc.to_digit(10).unwrap();
                        number_compare = true;
                    } else if number_compare {
                        return Ordering::Greater;
                    } else {
                        return ac.cmp(&bc);
                    }
                } else if bc.is_digit(10) {
                    if number_compare {
                        return Ordering::Less;
                    } else {
                        return ac.cmp(&bc);
                    }
                } else if number_compare {
                    return a_num.cmp(&b_num);
                } else {
                    let result = ac.cmp(&bc);
                    if !result.is_eq() {
                        return result;
                    }
                }
            }
            Left(_ac) => {
                return Ordering::Greater;
            }
            Right(_bc) => {
                return Ordering::Less;
            }
        }
    }
    return if number_compare {
        a_num.cmp(&b_num)
    } else {
        Ordering::Equal
    };
}

/// Tests can be run from the command line:
///    $ rust-script --test ./util/process-criterion-csv.rs
#[test]
fn test_numerical_string_compare() {
    let h123 = "Hello256-123-bytes";
    let h987 = "Hello256-987-bytes";
    let h1234 = "Hello256-1234-bytes";
    let h9876 = "Hello256-9876-bytes";
    assert_eq!(Ordering::Equal, numerical_string_compare(h123, h123));
    assert_eq!(Ordering::Less, numerical_string_compare(h123, h987));
    assert_eq!(Ordering::Greater, numerical_string_compare(h987, h123));
    assert_eq!(Ordering::Less, numerical_string_compare(h123, h1234));
    assert_eq!(Ordering::Greater, numerical_string_compare(h1234, h123));
    assert_eq!(Ordering::Less, numerical_string_compare(h123, h9876));
    assert_eq!(Ordering::Greater, numerical_string_compare(h9876, h123));

    assert_eq!(Ordering::Equal, numerical_string_compare(h987, h987));
    assert_eq!(Ordering::Less, numerical_string_compare(h987, h1234));
    assert_eq!(Ordering::Greater, numerical_string_compare(h1234, h987));
    assert_eq!(Ordering::Less, numerical_string_compare(h987, h9876));
    assert_eq!(Ordering::Greater, numerical_string_compare(h9876, h987));

    assert_eq!(Ordering::Equal, numerical_string_compare(h1234, h1234));
    assert_eq!(Ordering::Less, numerical_string_compare(h1234, h9876));
    assert_eq!(Ordering::Greater, numerical_string_compare(h9876, h1234));

    assert_eq!(Ordering::Equal, numerical_string_compare(h9876, h9876));
}

#[test]
fn test_numerical_end_string_compare() {
    let h123 = "Hello256-123";
    let h987 = "Hello256-987";
    assert_eq!(Ordering::Less, numerical_string_compare(h123, h987));
    assert_eq!(Ordering::Greater, numerical_string_compare(h987, h123));
    assert_eq!(Ordering::Equal, numerical_string_compare(h123, h123));
    assert_eq!(Ordering::Equal, numerical_string_compare(h987, h987));
}

fn main() {
    let cli = Cli::parse();

    let mut ring_results: HashMap<String, Stats> = HashMap::new();
    let mut aws_results: HashMap<String, Stats> = HashMap::new();

    let contents = fs::read_to_string(&cli.csv_file)
        .expect(&format!("Unable to open file: '{:?}'", &cli.csv_file));

    for line in contents.lines() {
        if line.starts_with("group") {
            continue;
        }
        let components: Vec<&str> = line.split(",").collect();
        assert_eq!(8, components.len());
        let test = components[0].trim();
        let lib = components[1].trim();
        let time = f64::from_str(components[5]).expect(&format!("Unable to parse time: {}", line));
        let iter = u32::from_str(components[7])
            .expect(&format!("Unable to parse iteration count: {}", line));
        let avg = time.div(iter as f64);
        match lib {
            "AWS-LC" => insert_result(test, avg, &mut aws_results),
            "Ring" => insert_result(test, avg, &mut ring_results),
            _ => panic!("Unrecognized library: {}", lib),
        }
    }

    let mut test_keys: Vec<&String> = aws_results.keys().collect();
    test_keys.sort_by(|a, b| numerical_string_compare(a, b));
    let mut handle = io::stdout().lock();
    writeln!(handle, "Test{}", cli.header_line()).unwrap();

    for test in test_keys {
        let aws_stats = aws_results.get(test.as_str()).unwrap().finalize();
        let ring_stats = ring_results.get(test.as_str()).unwrap().finalize();
        write!(handle, "{}", test).unwrap();
        write!(handle, "{}", cli.data_line(&aws_stats, &ring_stats)).unwrap();
        writeln!(handle, "").unwrap();
    }
    drop(handle);
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use crate::{
    get_aws_lc_include_path, get_aws_lc_sys_includes_path, get_generated_include_path,
    get_rust_include_path,
};
use bindgen::callbacks::{ItemInfo, ParseCallbacks};
use std::fmt::Debug;
use std::path::Path;

#[derive(Debug)]
struct StripPrefixCallback {
    remove_prefix: Option<String>,
}

impl StripPrefixCallback {
    fn new(prefix: &str) -> StripPrefixCallback {
        StripPrefixCallback {
            remove_prefix: Some(prefix.to_string()),
        }
    }
}

impl ParseCallbacks for StripPrefixCallback {
    fn generated_name_override(&self, item_info: ItemInfo<'_>) -> Option<String> {
        self.remove_prefix.as_ref().and_then(|s| {
            let prefix = format!("{s}_");
            item_info
                .name
                .strip_prefix(prefix.as_str())
                .map(String::from)
        })
    }
}

fn prepare_clang_args(manifest_dir: &Path, build_prefix: Option<&str>) -> Vec<String> {
    let mut clang_args: Vec<String> = vec![
        "-I".to_string(),
        get_rust_include_path(manifest_dir).display().to_string(),
        "-I".to_string(),
        get_aws_lc_include_path(manifest_dir).display().to_string(),
    ];

    if let Some(include_paths) = get_aws_lc_sys_includes_path() {
        for path in include_paths {
            clang_args.push("-I".to_string());
            clang_args.push(path.display().to_string());
        }
    }

    if let Some(prefix) = build_prefix {
        clang_args.push(format!("-DBORINGSSL_PREFIX={prefix}"));
        clang_args.push("-I".to_string());
        clang_args.push(
            get_generated_include_path(manifest_dir)
                .display()
                .to_string(),
        );
    }

    clang_args
}

const COPYRIGHT: &str = r#"
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC
"#;

const PRELUDE: &str = r#"
#![allow(
    unused_imports,
    non_camel_case_types,
    non_snake_case,
    non_upper_case_globals,
    improper_ctypes,
    clippy::cast_lossless,
    clippy::cast_possible_truncation,
    clippy::default_trait_access,
    clippy::must_use_candidate,
    clippy::not_unsafe_ptr_arg_deref,
    clippy::ptr_as_ptr,
    clippy::semicolon_if_nothing_returned,
    clippy::too_many_lines,
    clippy::unreadable_literal,
    clippy::used_underscore_binding,
    clippy::useless_transmute
)]
"#;

#[derive(Default)]
pub(crate) struct BindingOptions<'a> {
    pub build_prefix: Option<&'a str>,
    pub include_ssl: bool,
    pub disable_prelude: bool,
}

fn prepare_bindings_builder(manifest_dir: &Path, options: &BindingOptions<'_>) -> bindgen::Builder {
    let clang_args = prepare_clang_args(manifest_dir, options.build_prefix);

    let mut builder = bindgen::Builder::default()
        .derive_copy(true)
        .derive_debug(true)
        .derive_default(true)
        .derive_eq(true)
        .allowlist_file(r".*(/|\\)openssl(/|\\)[^/\\]+\.h")
        .allowlist_file(r".*(/|\\)rust_wrapper\.h")
        .rustified_enum(r"point_conversion_form_t")
        .default_macro_constant_type(bindgen::MacroTypeVariation::Signed)
        .generate_comments(true)
        .fit_macro_constants(false)
        .size_t_is_usize(true)
        .layout_tests(true)
        .prepend_enum_name(true)
        .formatter(bindgen::Formatter::Rustfmt)
        .clang_args(clang_args)
        .raw_line(COPYRIGHT)
        .header(
            get_rust_include_path(manifest_dir)
                .join("rust_wrapper.h")
                .display()
                .to_string(),
        );

    if !options.disable_prelude {
        builder = builder.raw_line(PRELUDE);
    }

    if options.include_ssl {
        builder = builder.clang_arg("-DAWS_LC_RUST_INCLUDE_SSL");
    }

    if let Some(ps) = &options.build_prefix {
        builder = builder.parse_callbacks(Box::new(StripPrefixCallback::new(ps)));
    }

    builder
}

pub(crate) fn generate_bindings(
    manifest_dir: &Path,
    options: &BindingOptions<'_>,
) -> bindgen::Bindings {
    prepare_bindings_builder(manifest_dir, options)
        .generate()
        .expect("Unable to generate bindings.")
}


// Copyright (c) 2022, Google Inc.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use std::env;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::process::Command;

#[cfg(any(
    feature = "bindgen",
    not(any(
        all(target_os = "macos", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "x86"),
        all(target_os = "linux", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64")
    ))
))]
mod bindgen;

pub(crate) fn get_aws_lc_include_path(manifest_dir: &Path) -> PathBuf {
    manifest_dir.join("aws-lc").join("include")
}

pub(crate) fn get_rust_include_path(manifest_dir: &Path) -> PathBuf {
    manifest_dir.join("include")
}

pub(crate) fn get_generated_include_path(manifest_dir: &Path) -> PathBuf {
    manifest_dir.join("generated-include")
}

pub(crate) fn get_aws_lc_sys_includes_path() -> Option<Vec<PathBuf>> {
    env::var("AWS_LC_SYS_INCLUDES")
        .map(|colon_delim_paths| colon_delim_paths.split(':').map(PathBuf::from).collect())
        .ok()
}

#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum OutputLib {
    RustWrapper,
    Crypto,
    Ssl,
}

#[allow(dead_code)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum OutputLibType {
    Static,
    Dynamic,
}

impl Default for OutputLibType {
    fn default() -> Self {
        let build_type_result = env::var("AWS_LC_SYS_STATIC");
        if let Ok(build_type) = build_type_result {
            eprintln!("AWS_LC_SYS_STATIC={build_type}");
            // If the environment variable is set, we ignore every other factor.
            let build_type = build_type.to_lowercase();
            if build_type.starts_with('0')
                || build_type.starts_with('n')
                || build_type.starts_with("off")
            {
                // Only dynamic if the value is set and is a "negative" value
                return OutputLibType::Dynamic;
            }

            return OutputLibType::Static;
        }
        OutputLibType::Static
    }
}

impl OutputLibType {
    fn rust_lib_type(&self) -> &str {
        match self {
            OutputLibType::Static => "static",
            OutputLibType::Dynamic => "dylib",
        }
    }
}

impl OutputLib {
    fn libname(self, prefix: Option<&str>) -> String {
        format!(
            "{}{}",
            if let Some(pfix) = prefix { pfix } else { "" },
            match self {
                OutputLib::Crypto => "crypto",
                OutputLib::Ssl => "ssl",
                OutputLib::RustWrapper => {
                    "rust_wrapper"
                }
            }
        )
    }
}

fn artifact_output_dir(path: &Path) -> PathBuf {
    path.join("build")
        .join("artifacts")
        .join(get_platform_output_path())
}

fn get_platform_output_path() -> PathBuf {
    PathBuf::new()
}

const VERSION: &str = env!("CARGO_PKG_VERSION");

fn prefix_string() -> String {
    format!("aws_lc_{}", VERSION.to_string().replace('.', "_"))
}

#[cfg(feature = "bindgen")]
fn target_platform_prefix(name: &str) -> String {
    format!("{}_{}_{}", env::consts::OS, env::consts::ARCH, name)
}

fn test_command(executable: &OsStr, args: &[&OsStr]) -> bool {
    if let Ok(output) = Command::new(executable).args(args).output() {
        return output.status.success();
    }
    false
}

fn find_cmake_command() -> Option<&'static OsStr> {
    if test_command("cmake3".as_ref(), &["--version".as_ref()]) {
        Some("cmake3".as_ref())
    } else if test_command("cmake".as_ref(), &["--version".as_ref()]) {
        Some("cmake".as_ref())
    } else {
        None
    }
}

fn get_cmake_config(manifest_dir: &PathBuf) -> cmake::Config {
    cmake::Config::new(manifest_dir)
}

fn prepare_cmake_build(manifest_dir: &PathBuf, build_prefix: Option<&str>) -> cmake::Config {
    let mut cmake_cfg = get_cmake_config(manifest_dir);

    if OutputLibType::default() == OutputLibType::Dynamic {
        cmake_cfg.define("BUILD_SHARED_LIBS", "1");
    } else {
        cmake_cfg.define("BUILD_SHARED_LIBS", "0");
    }

    let opt_level = env::var("OPT_LEVEL").unwrap_or_else(|_| "0".to_string());
    if opt_level.ne("0") {
        if opt_level.eq("1") || opt_level.eq("2") {
            cmake_cfg.define("CMAKE_BUILD_TYPE", "relwithdebinfo");
        } else {
            cmake_cfg.define("CMAKE_BUILD_TYPE", "release");
        }
    }

    if let Some(symbol_prefix) = build_prefix {
        cmake_cfg.define("BORINGSSL_PREFIX", symbol_prefix);
        let include_path = manifest_dir.join("generated-include");
        cmake_cfg.define(
            "BORINGSSL_PREFIX_HEADERS",
            include_path.display().to_string(),
        );
    }

    // Build flags that minimize our crate size.
    cmake_cfg.define("BUILD_TESTING", "OFF");
    if cfg!(feature = "ssl") {
        cmake_cfg.define("BUILD_LIBSSL", "ON");
    } else {
        cmake_cfg.define("BUILD_LIBSSL", "OFF");
    }
    // Build flags that minimize our dependencies.
    cmake_cfg.define("DISABLE_PERL", "ON");
    cmake_cfg.define("DISABLE_GO", "ON");

    if cfg!(feature = "asan") {
        env::set_var("CC", "/usr/bin/clang");
        env::set_var("CXX", "/usr/bin/clang++");
        env::set_var("ASM", "/usr/bin/clang");

        cmake_cfg.define("ASAN", "1");
    }

    cmake_cfg
}

fn build_rust_wrapper(manifest_dir: &PathBuf) -> PathBuf {
    prepare_cmake_build(manifest_dir, Some(&prefix_string()))
        .configure_arg("--no-warn-unused-cli")
        .build()
}

#[cfg(any(
    feature = "bindgen",
    not(any(
        all(target_os = "macos", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "x86"),
        all(target_os = "linux", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64")
    ))
))]
fn generate_bindings(manifest_dir: &Path, prefix: &str, bindings_path: &PathBuf) {
    let options = bindgen::BindingOptions {
        build_prefix: Some(prefix),
        include_ssl: cfg!(feature = "ssl"),
        disable_prelude: true,
    };

    let bindings = bindgen::generate_bindings(manifest_dir, &options);

    bindings
        .write(Box::new(std::fs::File::create(bindings_path).unwrap()))
        .expect("written bindings");
}

#[cfg(feature = "bindgen")]
fn generate_src_bindings(manifest_dir: &Path, prefix: &str, src_bindings_path: &Path) {
    bindgen::generate_bindings(
        manifest_dir,
        &bindgen::BindingOptions {
            build_prefix: Some(prefix),
            include_ssl: false,
            ..Default::default()
        },
    )
    .write_to_file(src_bindings_path.join(format!("{}.rs", target_platform_prefix("crypto"))))
    .expect("write bindings");

    bindgen::generate_bindings(
        manifest_dir,
        &bindgen::BindingOptions {
            build_prefix: Some(prefix),
            include_ssl: true,
            ..Default::default()
        },
    )
    .write_to_file(src_bindings_path.join(format!("{}.rs", target_platform_prefix("crypto_ssl"))))
    .expect("write bindings");
}

fn emit_rustc_cfg(cfg: &str) {
    println!("cargo:rustc-cfg={cfg}");
}

macro_rules! cfg_bindgen_platform {
    ($binding:ident, $os:literal, $arch:literal, $additional:expr) => {
        let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
        let $binding = {
            (target_os == $os && target_arch == $arch && $additional)
                .then(|| {
                    emit_rustc_cfg(concat!($os, "_", $arch));
                    true
                })
                .unwrap_or(false)
        };
    };
}

fn main() {
    use crate::OutputLib::{Crypto, RustWrapper, Ssl};

    let mut is_bindgen_required = cfg!(feature = "bindgen");
    let output_lib_type = OutputLibType::default();

    let is_internal_generate = env::var("AWS_LC_RUST_INTERNAL_BINDGEN")
        .unwrap_or_else(|_| String::from("0"))
        .eq("1");

    let pregenerated = !is_bindgen_required || is_internal_generate;

    cfg_bindgen_platform!(linux_x86, "linux", "x86", pregenerated);
    cfg_bindgen_platform!(linux_x86_64, "linux", "x86_64", pregenerated);
    cfg_bindgen_platform!(linux_aarch64, "linux", "aarch64", pregenerated);
    cfg_bindgen_platform!(macos_x86_64, "macos", "x86_64", pregenerated);

    if !(linux_x86 || linux_x86_64 || linux_aarch64 || macos_x86_64) {
        emit_rustc_cfg("use_bindgen_generated");
        is_bindgen_required = true;
    }

    check_dependencies();

    let manifest_dir = env::current_dir().unwrap();
    let manifest_dir = dunce::canonicalize(Path::new(&manifest_dir)).unwrap();
    let prefix = prefix_string();

    let out_dir = build_rust_wrapper(&manifest_dir);

    #[allow(unused_assignments)]
    let mut bindings_available = false;
    if is_internal_generate {
        #[cfg(feature = "bindgen")]
        {
            let src_bindings_path = Path::new(&manifest_dir).join("src");
            generate_src_bindings(&manifest_dir, &prefix, &src_bindings_path);
            bindings_available = true;
        }
    } else if is_bindgen_required {
        #[cfg(any(
            feature = "bindgen",
            not(any(
                all(target_os = "macos", target_arch = "x86_64"),
                all(target_os = "linux", target_arch = "x86"),
                all(target_os = "linux", target_arch = "x86_64"),
                all(target_os = "linux", target_arch = "aarch64")
            ))
        ))]
        {
            let gen_bindings_path = Path::new(&env::var("OUT_DIR").unwrap()).join("bindings.rs");
            generate_bindings(&manifest_dir, &prefix, &gen_bindings_path);
            bindings_available = true;
        }
    } else {
        bindings_available = true;
    }

    assert!(
        bindings_available,
        "aws-lc-sys build failed. Please enable the 'bindgen' feature on aws-lc-rs or aws-lc-sys"
    );

    println!(
        "cargo:rustc-link-search=native={}",
        artifact_output_dir(&out_dir).display()
    );

    println!(
        "cargo:rustc-link-lib={}={}",
        output_lib_type.rust_lib_type(),
        Crypto.libname(Some(&prefix))
    );

    if cfg!(feature = "ssl") {
        println!(
            "cargo:rustc-link-lib={}={}",
            output_lib_type.rust_lib_type(),
            Ssl.libname(Some(&prefix))
        );
    }

    println!(
        "cargo:rustc-link-lib={}={}",
        output_lib_type.rust_lib_type(),
        RustWrapper.libname(Some(&prefix))
    );

    for include_path in vec![
        get_rust_include_path(&manifest_dir),
        get_generated_include_path(&manifest_dir),
        get_aws_lc_include_path(&manifest_dir),
    ] {
        println!("cargo:include={}", include_path.display());
    }
    if let Some(include_paths) = get_aws_lc_sys_includes_path() {
        for path in include_paths {
            println!("cargo:include={}", path.display());
        }
    }

    println!("cargo:rerun-if-changed=builder/");
    println!("cargo:rerun-if-changed=aws-lc/");
    println!("cargo:rerun-if-env-changed=AWS_LC_SYS_STATIC");
}

fn check_dependencies() {
    let mut missing_dependency = false;

    if let Some(cmake_cmd) = find_cmake_command() {
        env::set_var("CMAKE", cmake_cmd);
    } else {
        eprintln!("Missing dependency: cmake");
        missing_dependency = true;
    };

    assert!(
        !missing_dependency,
        "Required build dependency is missing. Halting build."
    );
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use paste::paste;
use std::os::raw::{c_char, c_long, c_void};

#[allow(unused_macros)]
macro_rules! use_bindings {
    ($bindings:ident) => {
        mod $bindings;
        pub use $bindings::*;
    };
}

macro_rules! platform_binding {
    ($platform:ident) => {
        paste! {
            #[cfg(all($platform, not(feature = "ssl"), not(use_bindgen_generated)))]
            use_bindings!([< $platform _crypto >]);

            #[cfg(all($platform, feature = "ssl", not(use_bindgen_generated)))]
            use_bindings!([< $platform _crypto_ssl >]);
        }
    };
}

platform_binding!(linux_x86);

platform_binding!(linux_x86_64);

platform_binding!(linux_aarch64);

platform_binding!(macos_x86_64);

#[cfg(use_bindgen_generated)]
mod generated {
    #![allow(
        unused_imports,
        non_camel_case_types,
        non_snake_case,
        non_upper_case_globals,
        improper_ctypes,
        clippy::cast_lossless,
        clippy::cast_possible_truncation,
        clippy::default_trait_access,
        clippy::must_use_candidate,
        clippy::not_unsafe_ptr_arg_deref,
        clippy::ptr_as_ptr,
        clippy::semicolon_if_nothing_returned,
        clippy::too_many_lines,
        clippy::unreadable_literal,
        clippy::used_underscore_binding,
        clippy::useless_transmute
    )]

    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[cfg(use_bindgen_generated)]
pub use generated::*;

#[allow(non_snake_case)]
#[must_use]
pub fn ERR_GET_LIB(packed_error: u32) -> i32 {
    unsafe { ERR_GET_LIB_RUST(packed_error) }
}

#[allow(non_snake_case)]
#[must_use]
pub fn ERR_GET_REASON(packed_error: u32) -> i32 {
    unsafe { ERR_GET_REASON_RUST(packed_error) }
}

#[allow(non_snake_case)]
#[must_use]
pub fn ERR_GET_FUNC(packed_error: u32) -> i32 {
    unsafe { ERR_GET_FUNC_RUST(packed_error) }
}

#[allow(non_snake_case, clippy::not_unsafe_ptr_arg_deref)]
pub fn BIO_get_mem_data(b: *mut BIO, pp: *mut *mut c_char) -> c_long {
    unsafe { BIO_ctrl(b, BIO_CTRL_INFO, 0, pp.cast::<c_void>()) }
}

pub fn init() {
    unsafe { CRYPTO_library_init() }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use std::mem::MaybeUninit;

fn sha1_tester(input: &[u8]) -> [u8; 20] {
    let mut hash = MaybeUninit::<[u8; 20]>::uninit();

    unsafe {
        aws_lc_sys::SHA1(input.as_ptr(), input.len(), hash.as_mut_ptr().cast());
        hash.assume_init()
    }
}

fn compare(result: &[u8], expected_result: &[u8]) {
    println!("Comparing: {result:?} to {expected_result:?}");
    assert_eq!(result, expected_result);
}

#[test]
fn sha1() {
    let input1 = b"hello";
    let result1 = sha1_tester(input1);
    let openssl_result1 = openssl::sha::sha1(input1);
    compare(&result1, &openssl_result1);
}

#[test]
fn error_checking() {
    unsafe {
        let error = aws_lc_sys::ERR_get_error();
        let err_lib = aws_lc_sys::ERR_GET_LIB(error);
        let err_reason = aws_lc_sys::ERR_GET_REASON(error);
        let err_func = aws_lc_sys::ERR_GET_FUNC(error);
        assert_eq!(err_lib, 0);
        assert_eq!(err_reason, 0);
        assert_eq!(err_func, 0);
    }
}


#!/usr/bin/env rust-script
//! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//! SPDX-License-Identifier: Apache-2.0 OR ISC
//! ```cargo
//! [dependencies]
//! toml = "0.7.2"
//! clap = { version = "4.1.6", features = ["derive"] }
//! ```

use clap::Parser;
use std::fs::read_to_string;
use toml::Table;

#[derive(Parser)]
#[command(about)]
struct Args {
    cargo_file_path: Option<String>,
    #[arg(short = 'v', long = "version")]
    version: bool,
    #[arg(short = 'n', long = "name")]
    name: bool,
}

fn main() {
    let args = Args::parse();

    let content = read_to_string(args.cargo_file_path.unwrap_or("Cargo.toml".to_string()))
        .expect("failed to read file");

    let table = content.parse::<Table>().unwrap();

    let package = table["package"].as_table().unwrap();
    if args.name || !(args.version || args.name) {
        println!("{}", package["name"].as_str().unwrap());
    } else {
        println!("{}", package["version"].as_str().unwrap());
    };
}


#!/usr/bin/env rust-script
//! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//! SPDX-License-Identifier: Apache-2.0 OR ISC
//! ```cargo
//! [dependencies]
//! clap = { version = "4.2.2", features = ["derive"] }
//! regex = "1.7.3"
//! semver = "1.0.17"
//! ```

use clap::Parser;

#[derive(Parser)]
#[command(about)]
struct Args {
    tags: Vec<String>,
}

fn main() {
    let args = Args::parse();

    let input: Vec<String> = args.tags;

    // Modified regex from https://semver.org/
    let re = regex::Regex::new(
        r"^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
    ).unwrap();

    let mut tags: Vec<semver::Version> = input
        .into_iter()
        .filter(|t| re.is_match(t))
        .map(|t| semver::Version::parse(t.strip_prefix("v").unwrap()).unwrap())
        .collect();

    tags.sort();

    let latest = tags.pop().unwrap();

    println!("v{latest}")
}


#!/usr/bin/env rust-script

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

fn main() {
    println!("{} {}", std::env::consts::OS, std::env::consts::ARCH);
}


// Copyright 2020-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use serde_yaml;

use crate::defs;

#[derive(Debug)]
pub enum Error {
    IoError(std::io::Error),
    YamlError(serde_yaml::Error),
}

#[derive(Debug, Deserialize, Serialize)]
pub enum Target {
    // Alias for backwards compatibility with config files
    #[serde(alias = "NginxStanza")]
    Conf {
        path: String,
        user: Option<String>,
        group: Option<String>,
    },
}

#[derive(Debug, Deserialize, Serialize)]
pub enum Source {
    Acm {
        certificate_arn: String,
        bucket: Option<String>,
    },
    FileDb {
        path: String,
    },
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Token {
    pub label: String,
    pub pin: Option<String>,
    pub source: Source,
    pub target: Option<Target>,
    pub refresh_interval_secs: Option<u64>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Enclave {
    pub cpu_count: usize,
    pub memory_mib: u64,
    pub image_path: Option<String>,
    pub boot_timeout_ms: Option<u64>,
    pub p11kit_port: Option<u32>,
    pub rpc_port: Option<u32>,
    pub attestation_retry_count: Option<usize>,
}

#[derive(Debug, Deserialize, Serialize)]
pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Log {
    pub level: LogLevel,
    pub enable_timestamp: Option<bool>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Options {
    #[serde(default = "Options::default_service")]
    pub service: String,
    #[serde(default = "Options::default_force_start")]
    // Alias for backwards compatibility with config files
    #[serde(alias = "nginx_force_start")]
    pub force_start: bool,
    #[serde(default = "Options::default_reload_wait_ms")]
    // Alias for backwards compatibility with config files
    #[serde(alias = "nginx_reload_wait_ms")]
    pub reload_wait_ms: u64,
    #[serde(default = "Options::default_sync_interval_secs")]
    pub sync_interval_secs: u64,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
    pub enclave: Enclave,
    pub tokens: Vec<Token>,
    pub log: Option<Log>,
    #[serde(default)]
    pub options: Options,
}

impl Config {
    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
        let file = std::fs::OpenOptions::new()
            .read(true)
            .open(path)
            .map_err(Error::IoError)?;
        serde_yaml::from_reader(file).map_err(Error::YamlError)
    }
}

impl From<LogLevel> for log::Level {
    fn from(src: LogLevel) -> Self {
        match src {
            LogLevel::Error => log::Level::Error,
            LogLevel::Warn => log::Level::Warn,
            LogLevel::Info => log::Level::Info,
            LogLevel::Debug => log::Level::Debug,
            LogLevel::Trace => log::Level::Trace,
        }
    }
}

impl Options {
    fn default_service() -> String {
        defs::DEFAULT_SERVICE.to_string()
    }
    fn default_force_start() -> bool {
        defs::DEFAULT_FORCE_START
    }
    fn default_reload_wait_ms() -> u64 {
        defs::DEFAULT_RELOAD_WAIT_MS
    }
    fn default_sync_interval_secs() -> u64 {
        defs::DEFAULT_SYNC_INTERVAL_SECS
    }
}

impl Default for Options {
    fn default() -> Self {
        Self {
            service: Self::default_service(),
            force_start: Self::default_force_start(),
            reload_wait_ms: Self::default_reload_wait_ms(),
            sync_interval_secs: Self::default_sync_interval_secs(),
        }
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use log::{info, warn};
use nix::sys::signal;
use nix::unistd;
use std::fs::OpenOptions;
use std::io::Write;
use std::process::Command;
use std::time::{Duration, Instant};

use super::defs;
use super::ne;
use crate::{config, util};
use vtok_rpc::api::schema;
use vtok_rpc::{HttpTransport, Transport, VsockAddr, VsockStream};

#[derive(Debug)]
pub enum Error {
    NitroCliError(ne::Error),
    P11KitSetupError(std::io::Error),
    RpcConnectError(std::io::Error),
    RpcTransportError(vtok_rpc::TransportError),
    SystemdExecError(std::io::Error),
    VsockProxyError(Option<i32>),
}

pub struct P11neEnclave {
    cid: u32,
    pid: i32,
    boot_timeout: std::time::Duration,
    rpc_port: u32,
    attestation_retry_count: usize,
}

impl P11neEnclave {
    pub fn new(enclave_config: config::Enclave) -> Result<Self, Error> {
        let eri = ne::run_enclave(
            enclave_config
                .image_path
                .as_ref()
                .map(|s| s.as_str())
                .unwrap_or(defs::DEFAULT_EIF_PATH),
            enclave_config.cpu_count,
            enclave_config.memory_mib,
        )
        .map_err(Error::NitroCliError)?;

        info!("Setting up p11-kit config");
        OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(format!(
                "/etc/pkcs11/modules/{}.module",
                defs::P11_MODULE_NAME
            ))
            .and_then(|mut file| {
                file.write(
                    format!(
                        "remote:vsock:cid={};port={}\nmodule:{}\n",
                        eri.enclave_cid,
                        enclave_config
                            .p11kit_port
                            .unwrap_or(defs::DEFAULT_P11KIT_PORT),
                        defs::P11_MODULE_NAME,
                    )
                    .as_bytes(),
                )
            })
            .map_err(Error::P11KitSetupError)?;

        info!("Restarting vsock proxy");
        Command::new("systemctl")
            .args(&["restart", "nitro-enclaves-vsock-proxy"])
            .status()
            .map_err(Error::SystemdExecError)
            .and_then(|status| {
                if status.success() {
                    Ok(())
                } else {
                    Err(Error::VsockProxyError(status.code()))
                }
            })?;

        Ok(Self {
            // TODO: replace these rudimentary casts with proper checks/conversions.
            cid: eri.enclave_cid as u32,
            pid: eri.process_id as i32,
            boot_timeout: std::time::Duration::from_millis(
                enclave_config
                    .boot_timeout_ms
                    .unwrap_or(defs::DEFAULT_ENCLAVE_BOOT_TIMEOUT_MS),
            ),
            rpc_port: enclave_config.rpc_port.unwrap_or(defs::DEFAULT_RPC_PORT),
            attestation_retry_count: enclave_config
                .attestation_retry_count
                .unwrap_or(defs::DEFAULT_ATTESTATION_RETRY_COUNT),
        })
    }

    pub fn wait_boot(&self) -> bool {
        let limit = Instant::now() + self.boot_timeout;
        let poll_dur = Duration::from_millis(100);
        while Instant::now() < limit {
            if let Ok(Ok(_)) = self.rpc(&schema::ApiRequest::DescribeDevice) {
                return true;
            }
            if let Err(util::SleepError::UserExit) = util::interruptible_sleep(poll_dur) {
                return false;
            }
        }
        false
    }

    pub fn pid(&self) -> i32 {
        self.pid
    }

    pub fn add_token(&self, token: schema::Token) -> Result<schema::ApiResponse, Error> {
        self.retry_rpc(&schema::ApiRequest::AddToken { token })
    }

    pub fn refresh_token(
        &self,
        label: String,
        pin: String,
        envelope_key: schema::EnvelopeKey,
    ) -> Result<schema::ApiResponse, Error> {
        self.retry_rpc(&schema::ApiRequest::RefreshToken {
            label,
            pin,
            envelope_key,
        })
    }

    pub fn remove_token(&self, label: String, pin: String) -> Result<schema::ApiResponse, Error> {
        self.rpc(&schema::ApiRequest::RemoveToken { label, pin })
    }

    pub fn update_token(
        &self,
        label: String,
        pin: String,
        token: schema::Token,
    ) -> Result<schema::ApiResponse, Error> {
        self.retry_rpc(&schema::ApiRequest::UpdateToken { label, pin, token })
    }

    pub fn describe_token(&self, label: String, pin: String) -> Result<schema::ApiResponse, Error> {
        self.rpc(&schema::ApiRequest::DescribeToken { label, pin })
    }

    fn retry_rpc(&self, request: &schema::ApiRequest) -> Result<schema::ApiResponse, Error> {
        let mut count = 1;
        loop {
            // Transport errors are non-recoverable.
            let res = self.rpc(request)?;
            if res.is_ok() || count == self.attestation_retry_count {
                return Ok(res);
            }
            let sleep = Duration::from_millis((1 << (count - 1)) * 100);
            warn!(
                "RPC error (will retry in {}ms): {:?}",
                sleep.as_millis(),
                res
            );
            if let Err(util::SleepError::UserExit) = util::interruptible_sleep(sleep) {
                return Ok(res);
            }
            count += 1;
        }
    }

    fn rpc(&self, request: &schema::ApiRequest) -> Result<schema::ApiResponse, Error> {
        VsockStream::connect(VsockAddr {
            cid: self.cid,
            port: self.rpc_port,
        })
        .map_err(Error::RpcConnectError)
        .map(|stream| HttpTransport::new(stream, schema::API_URL))
        .and_then(|mut xport| {
            xport
                .send_request(request)
                .map_err(Error::RpcTransportError)?;
            xport.recv_response().map_err(Error::RpcTransportError)
        })
    }
}

impl Drop for P11neEnclave {
    fn drop(&mut self) {
        info!("Killing enclave pid={}", self.pid());
        signal::kill(unistd::Pid::from_raw(self.pid()), signal::Signal::SIGTERM)
            .unwrap_or_default();
        info!("Cleaning up p11kit config");
        std::fs::remove_file(format!(
            "/etc/pkcs11/modules/{}.module",
            defs::P11_MODULE_NAME
        ))
        .unwrap_or_else(|err| warn!("Cleanup error: {:?}", err));
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::sync::{Arc, Mutex};

use lazy_static::lazy_static;
use log::warn;
use serde::Deserialize;
use serde_json;
use std::process::Command;

lazy_static! {
    static ref IMDS_CACHE: Arc<Mutex<Option<ImdsCache>>> = Arc::new(Mutex::new(None));
}

#[derive(Debug)]
pub enum Error {
    IoError(std::io::Error),
    ProcessError(Option<i32>, String),
    Utf8Error(std::string::FromUtf8Error),
    ParseError(serde_json::Error),
    PoisonedLock,
}

#[allow(non_snake_case, dead_code)]
#[derive(Debug, Deserialize)]
pub struct IamInfo {
    #[serde(rename = "Code")]
    pub code: String,
    #[serde(rename = "LastUpdated")]
    pub last_updated: String,
    #[serde(rename = "InstanceProfileArn")]
    pub instance_profile_arn: String,
    #[serde(rename = "InstanceProfileId")]
    pub instance_profile_id: String,
}

#[allow(non_snake_case, dead_code)]
#[derive(Debug, Deserialize)]
pub struct IamRole {
    #[serde(rename = "Path")]
    #[serde(skip)]
    pub path: serde_json::Value,
    #[serde(rename = "RoleName")]
    #[serde(skip)]
    pub role_name: serde_json::Value,
    #[serde(rename = "RoleId")]
    #[serde(skip)]
    pub role_id: serde_json::Value,
    #[serde(rename = "Arn")]
    pub arn: String,
    #[serde(rename = "CreateDate")]
    #[serde(skip)]
    pub create_date: serde_json::Value,
    #[serde(rename = "AssumeRolePolicyDocument")]
    #[serde(skip)]
    pub assume_role_policy_document: serde_json::Value,
    #[serde(rename = "MaxSessionDuration")]
    #[serde(skip)]
    pub max_session_duration: serde_json::Value,
    #[serde(rename = "RoleLastUsed")]
    #[serde(skip)]
    pub role_last_used: serde_json::Value,
}

#[allow(non_snake_case)]
#[derive(Debug, Deserialize)]
pub struct IamRoleInfo {
    #[serde(rename = "Role")]
    pub role: IamRole,
}

#[allow(non_snake_case, dead_code)]
#[derive(Clone, Debug, Deserialize)]
pub struct SecurityCredentials {
    #[serde(rename = "Code")]
    pub code: String,
    #[serde(rename = "LastUpdated")]
    pub last_updated: String,
    #[serde(rename = "Type")]
    pub type_: String,
    #[serde(rename = "AccessKeyId")]
    pub access_key_id: String,
    #[serde(rename = "SecretAccessKey")]
    pub secret_access_key: String,
    #[serde(rename = "Token")]
    pub token: String,
    #[serde(rename = "Expiration")]
    pub expiration: String,
}

pub fn partition() -> Result<String, Error> {
    get_with(|cache| cache.partition.clone())
}

pub fn region() -> Result<String, Error> {
    get_with(|cache| cache.region.clone())
}

pub fn role_arn() -> Result<String, Error> {
    get_with(|cache| cache.role_arn.clone())
}

pub fn creds() -> Result<SecurityCredentials, Error> {
    get_with(|cache| cache.creds.clone())
}

pub fn invalidate_cache() -> Result<(), Error> {
    IMDS_CACHE
        .lock()
        .map(|mut opt| {
            opt.take();
        })
        .map_err(|_| Error::PoisonedLock)
}

struct ImdsCache {
    partition: String,
    region: String,
    role_arn: String,
    creds: SecurityCredentials,
}

impl ImdsCache {
    fn new() -> Result<Self, Error> {
        let token = Self::get_session_token()?;

        Ok(Self {
            partition: Self::fetch_partition(&token)?,
            region: Self::fetch_region(&token)?,
            role_arn: Self::fetch_role_arn(&token)?,
            creds: Self::fetch_creds(&token)?,
        })
    }

    fn get_session_token() -> Result<String, Error> {
        let args = [
            "-X",
            "PUT",
            "http://169.254.169.254/latest/api/token",
            "-H",
            "X-aws-ec2-metadata-token-ttl-seconds: 600",
        ];

        let output = Command::new("curl")
            .args(&args)
            .output()
            .map_err(Error::IoError)?;

        if !output.status.success() {
            return Err(Error::ProcessError(
                output.status.code(),
                String::from_utf8_lossy(&output.stderr).to_string(),
            ));
        }
        String::from_utf8(output.stdout).map_err(Error::Utf8Error)
    }

    fn fetch(key: &str, token: &str) -> Result<String, Error> {
        let url = format!("http://169.254.169.254{}", key);
        let header = format!("X-aws-ec2-metadata-token: {}", token);
        let output = Command::new("curl")
            .arg("-H")
            .arg(header)
            .arg("-fs")
            .arg(url)
            .output()
            .map_err(Error::IoError)?;
        if !output.status.success() {
            return Err(Error::ProcessError(
                output.status.code(),
                String::from_utf8_lossy(&output.stderr).to_string(),
            ));
        }
        String::from_utf8(output.stdout).map_err(Error::Utf8Error)
    }

    fn fetch_partition(token: &str) -> Result<String, Error> {
        Self::fetch("/latest/meta-data/services/partition", token)
    }

    fn fetch_region(token: &str) -> Result<String, Error> {
        Self::fetch("/latest/meta-data/placement/region", token)
    }

    fn fetch_role_name(token: &str) -> Result<String, Error> {
        Self::fetch("/latest/meta-data/iam/security-credentials", token)
    }

    fn fetch_role_arn(token: &str) -> Result<String, Error> {
        let role_name = Self::fetch_role_name(token)?;

        let output = Command::new("aws")
            .arg("iam")
            .arg("get-role")
            .arg("--role-name")
            .arg(format!("{}", role_name))
            .output()
            .map_err(Error::IoError)?;
        // If error, fallback to use the IMDS info to get the IAM role arn.
        // Note: This logic would not work for IAM roles which include paths.
        if !output.status.success() {
            warn!("Cannot fetch IAM role arn using the AWS CLI iam get-role command. Falling back to using IMDS.");
            warn!("For IAM roles with paths included, please add the IAM policy permission for iam:GetRole as per documentation - https://docs.aws.amazon.com/enclaves/latest/user/nitro-enclave-refapp.html.");
            let profile_arn =
                serde_json::from_str::<IamInfo>(&Self::fetch("/latest/meta-data/iam/info", token)?)
                    .and_then(|info| Ok(info.instance_profile_arn))
                    .map_err(Error::ParseError)?;

            let role_arn = profile_arn
                .split(":")
                .map(|word| match word.find("instance-profile/") {
                    Some(_) => format!("role/{}", role_name),
                    None => word.to_string(),
                })
                .collect::<Vec<String>>()
                .join(":");

            return Ok(role_arn);
        }

        let iam_role_info: IamRoleInfo =
            serde_json::from_str(&String::from_utf8(output.stdout).map_err(Error::Utf8Error)?)
                .map_err(Error::ParseError)?;

        Ok(iam_role_info.role.arn)
    }

    fn fetch_creds(token: &str) -> Result<SecurityCredentials, Error> {
        let creds_str = Self::fetch(
            format!(
                "/latest/meta-data/iam/security-credentials/{}",
                Self::fetch_role_name(token)?
            )
            .as_str(),
            token,
        )?;
        serde_json::from_str(creds_str.as_str()).map_err(Error::ParseError)
    }
}

fn get_with<F, R>(getter: F) -> Result<R, Error>
where
    F: FnOnce(&ImdsCache) -> R,
{
    let mut guard = IMDS_CACHE.lock().map_err(|_| Error::PoisonedLock)?;
    if guard.is_none() {
        guard.replace(ImdsCache::new()?);
    }
    Ok(guard
        .as_ref()
        .map(|cache| getter(cache))
        // Safe to unwrap due to the `replace` above.
        .unwrap())
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Write;
use std::time::Instant;

use log;

use crate::config;
use crate::defs;

pub struct Logger {
    timebase: Instant,
    level: log::Level,
    enable_timestamp: bool,
}

impl Logger {
    pub fn new(log_config: Option<config::Log>) -> Self {
        let (level, enable_timestamp) = log_config
            .map(|log| {
                (
                    log.level.into(),
                    log.enable_timestamp.unwrap_or(defs::DEFAULT_LOG_TIMESTAMP),
                )
            })
            .unwrap_or((defs::DEFAULT_LOG_LEVEL, defs::DEFAULT_LOG_TIMESTAMP));
        Self {
            level,
            enable_timestamp,
            timebase: Instant::now(),
        }
    }

    fn fmt_now(&self) -> String {
        let diff = Instant::now().duration_since(self.timebase);
        let mut secs = diff.as_secs();
        let day = secs / (24 * 3600);
        secs %= 24 * 3600;
        let hour = secs / 3600;
        secs %= 3600;
        let min = secs / 60;
        secs %= 60;

        format!(
            "{:3}d {:02}:{:02}:{:02}.{:06}",
            day,
            hour,
            min,
            secs,
            diff.subsec_micros(),
        )
    }
}

impl log::Log for Logger {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        // TODO: make log level configurable
        metadata.level() <= self.level
    }
    fn log(&self, record: &log::Record) {
        if self.enabled(record.metadata()) {
            let mut msg = String::new();
            if self.enable_timestamp {
                write!(msg, "[{}] ", self.fmt_now()).unwrap_or_default();
            }
            eprintln!("{}|{:6}| {}", msg, record.level(), record.args());
        }
    }
    fn flush(&self) {}
}


// Copyright 2020-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
extern crate lazy_static;
extern crate log;
extern crate nix;
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;
extern crate vtok_rpc;

mod agent;
mod config;
mod enclave;
mod imds;
mod logger;
mod ne;
mod util;

use log::{debug, info};
use nix::sys::signal;
use std::fmt;
use std::sync::atomic::Ordering;

pub mod defs {
    pub const RUN_DIR: &str = "/run/nitro_enclaves/acm";
    pub const P11_MODULE_NAME: &str = "p11ne";

    pub const SERVICE_NGINX: &str = "nginx";
    pub const SERVICE_HTTPD: &str = "httpd";
    pub const HTTPD_OVERRIDE_DATA: &str =
        "[Service]\nType=forking\nExecStart=\nExecStart=/usr/sbin/httpd $OPTIONS -k start\n";
    pub const HTTPD_OVERRIDE_DIR: &str = "/etc/systemd/system/httpd.service.d/";
    pub const HTTPD_OVERRIDE_FILE: &str = "/etc/systemd/system/httpd.service.d/httpd.conf";

    pub const DEFAULT_CONFIG_PATH: &str = "/etc/nitro_enclaves/acm.yaml";
    pub const DEFAULT_EIF_PATH: &str = "/usr/share/nitro_enclaves/p11ne/p11ne.eif";
    pub const DEFAULT_P11KIT_PORT: u32 = 9999;
    pub const DEFAULT_RPC_PORT: u32 = 10000;
    pub const DEFAULT_ENCLAVE_BOOT_TIMEOUT_MS: u64 = 5000;
    pub const DEFAULT_FORCE_START: bool = true;
    pub const DEFAULT_RELOAD_WAIT_MS: u64 = 1000;
    pub const DEFAULT_SYNC_INTERVAL_SECS: u64 = 600;
    pub const DEFAULT_TOKEN_REFRESH_INTERVAL_SECS: u64 = 12 * 3600;
    pub const DEFAULT_LOG_LEVEL: log::Level = log::Level::Info;
    pub const DEFAULT_LOG_TIMESTAMP: bool = false;
    pub const DEFAULT_ACM_BUCKET: &str = "prod";
    pub const DEFAULT_ATTESTATION_RETRY_COUNT: usize = 5;
    pub const DEFAULT_SERVICE: &str = SERVICE_NGINX;
}

pub mod gdata {
    use std::sync::atomic::AtomicBool;

    pub static EXIT_CONDITION: AtomicBool = AtomicBool::new(false);
}

#[derive(Debug)]
enum Error {
    AgentError(agent::Error),
    ConfigError(config::Error),
    SignalHandlerInstallError(nix::Error),
}

impl From<Error> for i32 {
    fn from(_other: Error) -> i32 {
        // NOTE: we could discriminate between errors here to provide a more specific
        // exit code.
        1
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // TODO: pretty-format error messages
        match self {
            Self::AgentError(e) => write!(f, "AgentError: {:?}", e),
            Self::ConfigError(e) => write!(f, "ConfigError: {:?}", e),
            Self::SignalHandlerInstallError(e) => write!(f, "SignalHandlerInstallError: {:?}", e),
        }
    }
}

extern "C" fn signal_handler(signo: std::os::raw::c_int) {
    match signo {
        nix::libc::SIGINT | nix::libc::SIGTERM => {
            info!("Setting exit condition");
            gdata::EXIT_CONDITION.store(true, Ordering::SeqCst);
        }
        nix::libc::SIGALRM => {
            debug!("Waking up");
        }
        _ => (),
    }
}

fn rusty_main() -> Result<(), Error> {
    let mut args = std::env::args();

    args.next();

    // TODO: get config file from args
    let mut config =
        config::Config::from_file(defs::DEFAULT_CONFIG_PATH).map_err(Error::ConfigError)?;

    log::set_boxed_logger(Box::new(logger::Logger::new(config.log.take())))
        .map(|()| log::set_max_level(log::LevelFilter::Debug))
        .unwrap_or_else(|e| eprintln!("Warning: failed to initialize logger: {:?}", e));

    // Install signal handlers
    let sa = signal::SigAction::new(
        signal::SigHandler::Handler(signal_handler),
        signal::SaFlags::empty(),
        signal::SigSet::empty(),
    );
    unsafe {
        signal::sigaction(signal::SIGINT, &sa).map_err(Error::SignalHandlerInstallError)?;
        signal::sigaction(signal::SIGTERM, &sa).map_err(Error::SignalHandlerInstallError)?;
    }

    agent::Agent::new(config)
        .and_then(|mut ag| ag.run())
        .map_err(Error::AgentError)
}

fn main() {
    match rusty_main() {
        Ok(()) => std::process::exit(0),
        Err(e) => {
            eprintln!("Error: {}", e);
            std::process::exit(i32::from(e))
        }
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::process::Command;

#[derive(Debug)]
pub enum Error {
    RunError(Option<i32>, String),
    ExecError(std::io::Error),
    JsonError(serde_json::Error),
}

/// The information provided by a `describe-enclaves` request.
#[derive(Clone, Serialize, Deserialize)]
pub struct EnclaveDescribeInfo {
    #[serde(rename = "EnclaveID")]
    /// The full ID of the enclave.
    pub enclave_id: String,
    #[serde(rename = "ProcessID")]
    /// The PID of the enclave process which manages the enclave.
    pub process_id: u32,
    #[serde(rename = "EnclaveCID")]
    /// The enclave's CID.
    pub enclave_cid: u64,
    #[serde(rename = "NumberOfCPUs")]
    /// The number of CPUs used by the enclave.
    pub cpu_count: u64,
    #[serde(rename = "CPUIDs")]
    /// The IDs of the CPUs used by the enclave.
    pub cpu_ids: Vec<u32>,
    #[serde(rename = "MemoryMiB")]
    /// The memory provided to the enclave (in MiB).
    pub memory_mib: u64,
    #[serde(rename = "State")]
    /// The current state of the enclave.
    pub state: String,
    #[serde(rename = "Flags")]
    /// The bit-mask which provides the enclave's launch flags.
    pub flags: String,
}

/// The information provided by a `run-enclave` request.
#[derive(Clone, Serialize, Deserialize)]
pub struct EnclaveRunInfo {
    #[serde(rename = "EnclaveID")]
    /// The full ID of the enclave.
    pub enclave_id: String,
    #[serde(rename = "ProcessID")]
    /// The PID of the enclave process which manages the enclave.
    pub process_id: u32,
    #[serde(rename = "EnclaveCID")]
    /// The enclave's CID.
    pub enclave_cid: u64,
    #[serde(rename = "NumberOfCPUs")]
    /// The number of CPUs used by the enclave.
    pub cpu_count: usize,
    #[serde(rename = "CPUIDs")]
    /// The IDs of the CPUs used by the enclave.
    pub cpu_ids: Vec<u32>,
    #[serde(rename = "MemoryMiB")]
    /// The memory provided to the enclave (in MiB).
    pub memory_mib: u64,
}

/// The information provided by a `terminate-enclave` request.
#[derive(Clone, Serialize, Deserialize)]
pub struct EnclaveTerminateInfo {
    #[serde(rename = "EnclaveID")]
    /// The full ID of the enclave.
    pub enclave_id: String,
    #[serde(rename = "Terminated")]
    /// A flag indicating if the enclave has terminated.
    pub terminated: bool,
}

pub fn run_enclave(
    image_path: &str,
    cpu_count: usize,
    memory_mib: u64,
) -> Result<EnclaveRunInfo, Error> {
    let output = Command::new("nitro-cli")
        .arg("run-enclave")
        .args(&["--eif-path", image_path])
        .args(&["--cpu-count", &format!("{}", cpu_count)])
        .args(&["--memory", &format!("{}", memory_mib)])
        .output()
        .map_err(Error::ExecError)?;
    if !output.status.success() {
        return Err(Error::RunError(
            output.status.code(),
            String::from_utf8_lossy(output.stderr.as_slice()).to_string(),
        ));
    }
    serde_json::from_slice(output.stdout.as_slice()).map_err(Error::JsonError)
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use std::fmt::Write;
use std::fs::OpenOptions;
use std::io::Read;
use std::path::Path;
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};

use crate::gdata;
use nix::libc;

pub enum SleepError {
    UserExit,
}

pub fn interruptible_sleep(dur: Duration) -> Result<(), SleepError> {
    let wake_time = Instant::now() + dur;
    loop {
        let now = Instant::now();
        if now >= wake_time {
            return Ok(());
        }
        let remaining = libc::timespec {
            tv_sec: (wake_time - now).as_secs() as libc::time_t,
            tv_nsec: (wake_time - now).subsec_nanos() as libc::c_long,
        };
        unsafe { libc::nanosleep(&remaining, std::ptr::null_mut()) };
        if gdata::EXIT_CONDITION.load(Ordering::SeqCst) {
            return Err(SleepError::UserExit);
        }
    }
}

pub fn generate_pkcs11_pin() -> Result<String, std::io::Error> {
    OpenOptions::new()
        .read(true)
        .open("/dev/urandom")
        .and_then(|mut file| {
            let mut buf = vec![0u8; 16];
            file.read_exact(buf.as_mut_slice())?;
            Ok(bytes_to_hex(buf.as_slice()))
        })
}

pub fn bytes_to_hex(bytes: &[u8]) -> String {
    let mut hex = String::with_capacity(bytes.len() * 2);
    for b in bytes {
        // Pushing to a string should never fail, unless we're out of memory,
        // in which case it's ok to panic.
        write!(hex, "{:02x}", b).unwrap();
    }
    hex
}

pub fn create_dirs_for_file<P: AsRef<Path>>(file_path: P) -> Result<(), std::io::Error> {
    Path::new(file_path.as_ref())
        .parent()
        .ok_or(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            format!("bad file path: {}", file_path.as_ref().display()),
        ))
        .and_then(|dir| std::fs::create_dir_all(dir))
}


// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use super::mngtok::Error;
use nix::unistd;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader, Write};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;

pub struct HttpdService {}

impl HttpdService {
    pub fn write_tls_entries(
        path: &str,
        uid: Option<nix::unistd::Uid>,
        gid: Option<nix::unistd::Gid>,
        key_uri: &str,
        cert_path: Option<String>,
    ) -> Result<(), Error> {
        let mut to_write = String::new();
        OpenOptions::new()
            .read(true)
            .open(path)
            .map_err(Error::TargetIoError)
            .and_then(|file| {
                let reader = BufReader::new(&file);
                for line in reader.lines() {
                    if let Ok(l) = line {
                        let nl = {
                            if l.starts_with("SSLCertificateKeyFile") {
                                format!("SSLCertificateKeyFile \"{}\"", key_uri)
                            } else if l.starts_with("SSLCertificateFile") {
                                match cert_path {
                                    Some(ref cp) => format!("SSLCertificateFile \"{}\"", cp),
                                    None => l,
                                }
                            } else {
                                l
                            }
                        };
                        to_write.push_str(&nl);
                        to_write.push_str("\n");
                    }
                }
                Ok(())
            })?;

        OpenOptions::new()
            .write(true)
            .truncate(true)
            .mode(0o440)
            .open(path)
            .map_err(Error::TargetIoError)
            .and_then(|mut file| {
                unistd::fchown(file.as_raw_fd(), uid, gid).map_err(Error::NixError)?;
                nix::sys::stat::fchmod(
                    file.as_raw_fd(),
                    // Safe becase 0o440 is valid.
                    unsafe { nix::sys::stat::Mode::from_bits_unchecked(0o440) },
                )
                .map_err(Error::NixError)?;
                write!(file, "{}", to_write).map_err(Error::TargetIoError)?;
                Ok(())
            })?;
        Ok(())
    }
}