use std::io::Read;

use sequoia_openpgp as openpgp;

use openpgp::Cert;
use openpgp::Fingerprint;
use openpgp::Result;
use openpgp::crypto::Password;
use openpgp::crypto::mpi;
use openpgp::crypto;
use openpgp::packet::Key;
use openpgp::packet::Packet;
use openpgp::packet::key::PublicParts;
use openpgp::packet::key::UnspecifiedRole;
use openpgp::packet::signature::SignatureBuilder;
use openpgp::parse::PacketParser;
use openpgp::parse::PacketParserResult;
use openpgp::parse::Parse;
use openpgp::parse::stream::GoodChecksum;
use openpgp::parse::stream::MessageLayer;
use openpgp::parse::stream::MessageStructure;
use openpgp::parse::stream::VerificationHelper;
use openpgp::parse::stream::VerifierBuilder;
use openpgp::serialize::stream::LiteralWriter;
use openpgp::serialize::stream::Message;
use openpgp::serialize::stream::Signer;
use openpgp::types::HashAlgorithm;
use openpgp::types::SignatureType;

use anyhow::Context;

/// Test data.
pub mod data;

use crate as backend;

use crate::Backend;
use crate::utils::run_async;

// A wrapper for KeyHandle, which implements crypto::Signer.
pub struct TestSigner(
    Box<dyn crate::KeyHandle + Send + Sync>,
    Key<PublicParts, UnspecifiedRole>);

impl From<Box<dyn backend::KeyHandle + Send + Sync>> for TestSigner {
    fn from(kh: Box<dyn backend::KeyHandle + Send + Sync>) -> Self {
        let key = run_async(async {
            kh.public_key().await
        }).expect("ok"); // XXX: don't unwrap
        Self(kh, key)
    }
}

impl crypto::Signer for TestSigner {
    fn public(&self) -> &Key<PublicParts, UnspecifiedRole>
    {
        &self.1
    }

    fn sign(&mut self, hash_algo: HashAlgorithm, digest: &[u8])
            -> Result<mpi::Signature>
    {
        run_async(async {
            self.0.sign(hash_algo, digest).await.map(|(_pk_algo, sig)| sig)
        })?
    }
}

pub struct VerifyHelper {
    // The certificates.
    pub keyring: Vec<Cert>,
    // The good signatures.
    pub signers: Vec<Fingerprint>,
}

impl VerifyHelper {
    pub fn new(keyring: &[ Cert ]) -> Self {
        Self {
            keyring: keyring.to_vec(),
            signers: Vec::new(),
        }
    }
}

impl VerificationHelper for &mut VerifyHelper {
    fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> Result<Vec<Cert>> {
        Ok(self.keyring
           .iter()
           .filter(|cert| {
               cert.keys().any(|k| {
                   ids.iter().any(|id| {
                       id.aliases(openpgp::KeyHandle::from(k.key().fingerprint()))
                   })
               })
           })
           .cloned()
           .collect::<Vec<_>>())
    }
    fn check(&mut self, structure: MessageStructure) -> Result<()> {
        for (i, layer) in structure.into_iter().enumerate() {
            match layer {
                MessageLayer::Encryption { .. } if i == 0 => (),
                MessageLayer::Compression { .. } if i == 1 => (),
                MessageLayer::SignatureGroup { ref results } => {
                    for r in results {
                        if let Ok(GoodChecksum { ka, .. }) = r {
                            self.signers.push(ka.key().fingerprint());
                        } else {
                            log::info!("Bad signature: {:?}", r);
                        }
                    }
                }
                _ => return Err(anyhow::anyhow!(
                    "Unexpected message structure")),
            }
        }

        Ok(())
    }
}

/// Sign a message and verify that the signature is good.
///
/// If signing_keys is None, this is expected to fail.
pub fn sign_verify(signers: Vec<TestSigner>,
                   keyring: Vec<Cert>,
                   signing_keys: Option<&[ Fingerprint ]>) -> Result<()>
{
    let p = &openpgp::policy::StandardPolicy::new();

    let mut output = Vec::new();
    let message = Message::new(&mut output);

    let builder = SignatureBuilder::new(SignatureType::Binary);

    let mut signers = signers.into_iter();
    let mut signer = Signer::with_template(
        message, signers.next().expect("a signer"), builder)?;
    for s in signers {
        signer = signer.add_signer(s)?;
    }
    let signer = signer.build().context("Failed to create signer")?;
    let mut writer = LiteralWriter::new(signer).build()
        .context("Failed to create literal writer")?;

    std::io::copy(&mut std::io::Cursor::new(b"hello"), &mut writer)
        .context("Failed to sign")?;
    if let Err(err) = writer.finalize().context("Failed to sign") {
        if signing_keys.is_none() {
            // Expected failure.
            return Ok(());
        } else {
            panic!("Failed to finalize writer: {}", err);
        }
    }

    let mut h = VerifyHelper::new(&keyring);
    let mut v = VerifierBuilder::from_bytes(&output[..])?
        .with_policy(p, None, &mut h)?;

    let mut message = Vec::new();
    let r = v.read_to_end(&mut message);

    if let Some(signing_keys) = signing_keys {
        assert!(r.is_ok());
        assert_eq!(message, b"hello");
        assert_eq!(&h.signers, signing_keys);
    } else {
        assert!(r.is_err());
    }

    Ok(())
}

/// Try to decrypt a message.
///
/// `ciphertext` is the well-formed encrypted message.
/// plaintext is the expected plaintext.  If it is not `None`,
/// then it must match what is decrypted.
///
/// `password` is an optional password.  If supplied, then it
/// must be used to unlock the key.
///
/// This function tries to decrypt all of the keys with the password.
pub async fn try_decrypt<B>(backend: &mut B,
                            ciphertext: &[u8], plaintext: Option<&[u8]>,
                            recipient: openpgp::KeyHandle,
                            password: Option<Password>)
    -> Result<()>
where B: Backend
{
    let mut good = false;

    let mut session_key = None;
    let mut ppr = PacketParser::from_bytes(ciphertext)?;
    while let PacketParserResult::Some(mut pp) = ppr {
        match pp.packet {
            Packet::PKESK(ref pkesk)
                if pkesk.recipient().as_ref() == Some(&recipient) =>
            {
                // Manual search.
                log::debug!("Considering PKESK for {}",
                            pkesk.recipient().unwrap());

                'done: for device in backend.list().await {
                    for mut key in device.list().await {
                        log::debug!("Considering key {}", key.fingerprint());

                        let key_handle =
                            openpgp::KeyHandle::from(key.fingerprint());
                        if pkesk.recipient()
                            .map(|r| ! r.aliases(&key_handle))
                            .unwrap_or(false)
                        {
                            log::debug!("Wrong key for PKESK {}", key.fingerprint());
                            continue;
                        }

                        if let Some(password) = password.as_ref() {
                            // Relock, just in case.
                            key.lock().await?;

                            if let Err(e) = key.unlock(Some(password)).await {
                                eprintln!("Failed to unlock key: {}", e);
                                // There may be another key that we
                                // can unlock with this password.
                                continue;
                            }
                        }

                        if let Some((algo, sk))
                            = key.decrypt_pkesk(&pkesk).await
                        {
                            session_key = Some((algo, sk));
                            break 'done;
                        }
                    }
                    if session_key.is_none() {
                        return Err(anyhow::anyhow!(
                            "Decryption failed".to_string()));
                    }
                }
            }
            Packet::PKESK(ref pkesk) => {
                // Catch-all for packets that failed the manual search.
                log::debug!("Ignoring PKESK for {:?}", pkesk.recipient());
            }
            Packet::SEIP(_) => {
                if let Some(session_key) = session_key.take() {
                    let (algo, sk) = session_key;
                    log::debug!("Decrypting PKESK");
                    pp.decrypt(algo, &sk)?;
                    log::debug!("Decrypting PKESK succeeded");
                } else {
                    return Err(anyhow::anyhow!(
                        "Could not decrypt a PKESK".to_string()));
                }
            }
            Packet::Literal(_) => {
                pp.buffer_unread_content()?;
                if let Packet::Literal(l) = &pp.packet {
                    if let Some(plaintext) = plaintext {
                        if plaintext != l.body() {
                            return Err(anyhow::anyhow!(format!(
                                "Plaintext does not match:\n\
                                 got:      {:?},\n\
                                 expected: {:?}",
                                l.body(), plaintext)));
                        }
                    }
                    good = true;
                }
            }
            _ => (),
        }

        let (_packet, next_ppr) = pp.recurse()?;
        ppr = next_ppr;
    }

    if let PacketParserResult::EOF(ppr) = ppr {
        assert!(ppr.is_message().is_ok());
        // A possible reason this may fail is if the message uses
        // compression, but compression support is not enabled.
        assert!(good);
    }

    Ok(())
}

/// Preinit is a function that is called at the start of every
/// test.  If it returns `true`, the test should be run.  If it
/// returns `false`, then the test should be skipped.  This is useful
/// when the required test infrastructure is not detected.
///
/// `$serialize_tests` is whether to serialize the tests for this
/// backend.  If `false`, the tests are run concurrently.
///
/// `$import_cert` imports a certificate into the backend.
///
/// `$can_import_encrypted_secret_key_material` is whether
/// `$import_cert` can import encrypted secret key material.
///
/// If `$key_sets` is `Some`, then the backend can only load standard
/// keys consisting of (up to) an encrypt key, a signing key and an
/// authentication key.  The integer indicates the number of key sets
/// that the backend can load simultaneously.  If `$key_sets` is
/// `None`, then the backend can load an infinite number of keys.
///
/// Some backends always protect keys by default.  If
/// `$default_password` is `Some` that's the password for keys when
/// they are imported.
#[macro_export]
macro_rules! generate_tests {
    ($preinit: expr, $serialize_tests: expr,
     $backend: ty, $init_backend: expr,
     $import_cert: expr, $can_import_encrypted_secret_key_material: expr,
     $key_sets: expr, $default_password: expr,
     $can_export: expr,
     $can_change_password: expr,
     $can_delete: expr) => {
        static SERIALIZE: std::sync::Mutex<()> = std::sync::Mutex::new(());

        fn serialize_maybe() -> Option<std::sync::MutexGuard<'static, ()>> {
            let serialize_tests: bool = $serialize_tests;
            if serialize_tests {
                loop {
                    match SERIALIZE.lock() {
                        Ok(guard) => return Some(guard),
                        Err(_err) => {
                            // Another test panicked.  Don't let that
                            // stop us.
                            SERIALIZE.clear_poison();
                        }
                    }
                }
            } else {
                None
            }
        }

        /// strip the primary key's secret key material, if any.
        ///
        /// This is useful when `$key_sets` is `Some` and the backend
        /// doesn't have space for the primary key.
        fn strip_primary_secret(cert: Cert) -> Cert {
            if cert.primary_key().key().has_secret() {
                let mut pk = cert.primary_key().key().clone();
                pk.steal_secret();

                let cert = cert.insert_packets(pk).expect("can merge").0;
                assert!(! cert.primary_key().key().has_secret());
                cert
            } else {
                cert
            }
        }

        // Make sure $import_cert works.
        #[tokio::test]
        pub async fn import_cert_helper() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // A simple test: import a certificate.  Make sure the
            // keys are actually imported.

            let mut backend = $init_backend().await;
            let mut cert = Cert::from_bytes(
                $crate::test_framework::data::key("no-password.asc"))
                .expect("valid cert");

            let key_sets: Option<usize> = $key_sets;
            if key_sets.is_some() {
                cert = strip_primary_secret(cert);
            }

            $import_cert(&mut backend, &cert).await;

            let mut imported_keys = Vec::new();
            for device in backend.list().await {
                for key in device.list().await {
                    imported_keys.push(key.fingerprint());
                }
            }
            imported_keys.sort();

            let mut expected_keys = Vec::new();
            for ka in cert.keys().secret() {
                expected_keys.push(ka.key().fingerprint());
            }
            expected_keys.sort();

            if imported_keys != expected_keys {
                eprintln!(
                    "Imported keys:{}",
                    imported_keys.iter()
                        .map(|fpr| fpr.to_string())
                        .collect::<Vec<String>>()
                        .join("\n  "));
                eprintln!(
                    "Expected keys:{}",
                    expected_keys.iter()
                        .map(|fpr| fpr.to_string())
                        .collect::<Vec<String>>()
                        .join("\n  "));

                panic!("Importing keys is broken");
            }

            Ok(())
        }

        // Check backend.import_cert.
        #[tokio::test]
        pub async fn backend_import_cert() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // A simple test: import a certificate.  Make sure the
            // keys are actually imported.

            let mut backend = $init_backend().await;

            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("carol-subkeys-a.asc"))
                .expect("valid cert");

            let mut expected_keys = Vec::new();
            for ka in cert.keys().secret() {
                expected_keys.push(ka.key().fingerprint());
            }
            expected_keys.sort();

            for expected_status in [ImportStatus::New, ImportStatus::Unchanged] {
                eprintln!("expected_status: {:?}", expected_status);
                match backend.import(cert.clone()).await {
                    Err(err) => {
                        if let Some(Error::ExternalImportRequired(_))
                            = err.downcast_ref()
                        {
                            // The backend does not support import_cert.
                            // That's okay.  Skip the test.
                            return Ok(());
                        } else {
                            return Err(err);
                        }
                    }
                    Ok(results) => {
                        let mut imported_keys = results
                            .into_iter()
                            .map(|(import_status, key)| {
                                assert_eq!(import_status, expected_status);
                                key.fingerprint()
                            })
                            .collect::<Vec<Fingerprint>>();
                        imported_keys.sort();

                        if imported_keys != expected_keys {
                            eprintln!(
                                "Imported keys:{}",
                                imported_keys.iter()
                                    .map(|fpr| fpr.to_string())
                                    .collect::<Vec<String>>()
                                    .join("\n  "));
                            eprintln!(
                                "Expected keys:{}",
                                expected_keys.iter()
                                    .map(|fpr| fpr.to_string())
                                    .collect::<Vec<String>>()
                                    .join("\n  "));

                            panic!("Importing keys is broken");
                        }

                        let mut listed_keys = Vec::new();
                        for device in backend.list().await {
                            for key in device.list().await {
                                listed_keys.push(key.fingerprint());
                            }
                        }
                        listed_keys.sort();

                        if listed_keys != expected_keys {
                            eprintln!(
                                "Listed keys:{}",
                                listed_keys.iter()
                                    .map(|fpr| fpr.to_string())
                                    .collect::<Vec<String>>()
                                    .join("\n  "));
                            eprintln!(
                                "Expected keys:{}",
                                expected_keys.iter()
                                    .map(|fpr| fpr.to_string())
                                    .collect::<Vec<String>>()
                                    .join("\n  "));

                            panic!("Listing keys is broken");
                        }
                    }
                }
            }

            Ok(())
        }

        #[tokio::test]
        pub async fn decrypt_simple() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // A simple test: given a secret key and a message encrypted to
            // the key, can we use the backend to decrypt it?

            let mut backend = $init_backend().await;
            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("no-password.asc"))
                .expect("valid cert");

            $import_cert(&mut backend, &cert).await;

            let msg: &[u8] = $crate::test_framework::data::message("no-password.asc");
            let keyid = KeyHandle::KeyID("AA5BFAC1E4A121A0".parse()?);

            let default_password: Option<&str> = $default_password;
            let default_password: Option<Password>
                = default_password.map(|p| Password::from(p));
            if let Err(err) = $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"foo\n"), keyid, default_password).await
            {
                eprintln!("Decrypting: {}", err);
                Err(err)
            } else {
                Ok(())
            }
        }

        #[tokio::test]
        pub async fn decrypt_missing_password() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let can_import_encrypted_secret_key_material: bool
                = $can_import_encrypted_secret_key_material;
            if ! can_import_encrypted_secret_key_material {
                log::debug!("Backend doesn't support importing encrypt secret \
                             material.  Skipping test.");
                return Ok(());
            }

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // Load a few variants of a certificate: one where the
            // keys are encrypted with the password "bob", one where
            // they are encrypted with the password "smithy", and one
            // that is not encrypted.

            let mut backend = $init_backend().await;

            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("bob-password-bob.asc"))
                .expect("valid cert");
            $import_cert(&mut backend, &cert).await;

            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("bob-password-smithy.asc"))
                .expect("valid cert");
            $import_cert(&mut backend, &cert).await;

            let msg: &[u8] = $crate::test_framework::data::message("bob.asc");
            let keyid = KeyHandle::KeyID("0A4BA2249168D779".parse()?);

            assert!(
                $crate::test_framework::try_decrypt(
                    &mut backend, msg, Some(b"hi bob\n"), keyid, None)
                    .await.is_err());

            Ok(())
        }

        #[tokio::test]
        pub async fn decrypt_with_password() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let can_import_encrypted_secret_key_material: bool
                = $can_import_encrypted_secret_key_material;
            if ! can_import_encrypted_secret_key_material {
                log::debug!("Backend doesn't support importing encrypt secret \
                             material.  Skipping test.");
                return Ok(());
            }

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // Load a certificate where the keys are encrypted with
            // the password "bob".  Make sure we can decrypt the
            // message.  Load a variant of the certificate where the
            // passwords have been changed.  Make sure we can decrypt
            // the message with the new password.  Finally, load a
            // variant of the certificate where the keys are not
            // encrypted.

            let mut backend = $init_backend().await;

            let msg: &[u8] = $crate::test_framework::data::message("bob.asc");
            let keyid = KeyHandle::KeyID("0A4BA2249168D779".parse()?);

            // Import Bob's certificate.  The keys are password
            // protected.  Make sure we can use them with the right
            // password, and can't with the wrong one.
            log::info!("Importing certificate variant #1");
            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("bob-password-bob.asc"))
                .expect("valid cert");
            $import_cert(&mut backend, &cert).await;

            log::info!("Decrypting with wrong password:");
            assert!(
                $crate::test_framework::try_decrypt(
                    &mut backend, msg, Some(b"hi bob\n"),
                    keyid.clone(), Some(Password::from("sup3r s3cur3")))
                    .await.is_err());

            log::info!("Decrypting with right password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("bob"))).await.unwrap();

            // Import a new version of Bob's certificate in which the
            // keys are still encrypted, but with a different
            // password.
            log::info!("Importing certificate variant #2");
            let cert2 = Cert::from_bytes(
                $crate::test_framework::data::key("bob-password-smithy.asc"))
                .expect("valid cert");
            assert_eq!(cert.fingerprint(), cert2.fingerprint());
            $import_cert(&mut backend, &cert2).await;

            log::info!("Decrypting with wrong password:");
            assert!($crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("sup3r s3cur3"))).await.is_err());
            log::info!("Decrypting with right password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("smithy"))).await.unwrap();

            // Import a third version of Bob's certificate in which
            // the keys are not encrypt.
            log::info!("Importing certificate variant #3");
            let cert3 = Cert::from_bytes(
                $crate::test_framework::data::key("bob-no-password.asc"))
                .expect("valid cert");
            assert_eq!(cert.fingerprint(), cert3.fingerprint());
            $import_cert(&mut backend, &cert3).await;

            // Note: using the wrong password does not always fail
            // with a key that is not password protected.  For
            // instance, the gpg-agent backend only provides the
            // cached password on demand.  Since the key is not
            // password protected, it won't ask for a password.

            log::info!("Decrypting with right password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), None).await.unwrap();

            Ok(())
        }

        #[tokio::test]
        pub async fn verify() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            async fn test(filename: &str, signing_key: &str,
                          password: Option<&str>,
                          success: bool)
                -> Result<()>
            {
                let mut backend = $init_backend().await;

                let cert = Cert::from_bytes(
                    $crate::test_framework::data::key(filename))
                    .expect("valid cert");
                $import_cert(&mut backend, &cert).await;

                let mut signer = backend.find_key(&signing_key).await?;
                if let Some(password) = password {
                    if let Err(err) = signer.unlock(Some(&password.into())).await {
                        log::warn!("Failed to unlock key: {}", err);
                    }
                } else {
                    // Use the default password (if configured).
                    let default_password: Option<&str> = $default_password;
                    if let Some(default_password) = default_password {
                        let default_password = Password::from(default_password);
                        if let Err(err) = signer.unlock(Some(&default_password)).await {
                            log::warn!("Failed to unlock key: {}", err);
                        }
                    }
                }

                let signing_fpr = Fingerprint::from_hex(signing_key)
                    .expect("valid");

                let expected_signers_;
                let expected_signers: Option<&[ Fingerprint ]> = if success {
                    expected_signers_ = vec![ signing_fpr ];
                    Some(&expected_signers_[..])
                } else {
                    None
                };

                $crate::test_framework::sign_verify(
                    vec![ signer.into() ],
                    vec![ cert.clone() ],
                    expected_signers)?;

                Ok(())
            }

            // A simple test: sign a message using a key with no password.
            test("no-password.asc", "8D46B2975890A615AF4109CB6BA1BF391DF802C8",
                 None, true).await.expect("should pass");

            let can_import_encrypted_secret_key_material: bool
                = $can_import_encrypted_secret_key_material;
            if ! can_import_encrypted_secret_key_material {
                log::debug!("Backend doesn't support importing encrypt secret \
                             material.  Skipping the rest of the test.");
                return Ok(());
            }

            // Sign a message using a key that is password protected,
            // but don't provide the password.  Make sure it fails.
            test("bob-password-bob.asc", "9410E96E68643821794608269CB5006116833EE7",
                 None, false).await.expect("should pass");

            // Now provide the password.
            test("bob-password-bob.asc", "9410E96E68643821794608269CB5006116833EE7",
                 Some("bob"), true).await.expect("should pass");

            // And now provide the wrong password.
            test("bob-password-bob.asc", "9410E96E68643821794608269CB5006116833EE7",
                 Some("wrong password"), false).await.expect("should pass");

            Ok(())
        }

        #[tokio::test]
        pub async fn list_keys() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            let key_sets: Option<usize> = $key_sets;

            let _ = env_logger::Builder::from_default_env().try_init();

            // We first make sure that we can list the empty backend.
            {
                let mut backend = init_backend().await;

                let mut got = Vec::new();
                for device in backend.list().await {
                    for mut key in device.list().await {
                        got.push(key.fingerprint().to_string());
                    }
                }
                assert!(got.is_empty());
            }

            // We have two variants of a certificate with disjoint
            // subkeys.  When we load both certificates, make sure both
            // sets of variants appear.

            let keyring_a: &[u8] = $crate::test_framework::data::key(
                "carol-subkeys-a.asc");
            let mut cert_a = Cert::from_bytes(keyring_a)
                .expect("valid cert");
            if key_sets.is_some() {
                // Strip the primary.
                cert_a = strip_primary_secret(cert_a);
            }
            let keys_a = cert_a.keys().secret().map(|k| k.key().fingerprint())
                .collect::<Vec<Fingerprint>>();

            let keyring_b: &[u8] = $crate::test_framework::data::key(
                "carol-subkeys-b.asc");
            let mut cert_b = Cert::from_bytes(keyring_b)
                .expect("valid cert");
            if key_sets.is_some() {
                // Strip the primary.
                cert_b = strip_primary_secret(cert_b);
            }
            let keys_b = cert_b.keys().secret().map(|k| k.key().fingerprint())
                .collect::<Vec<Fingerprint>>();

            // We expect the same certificate, but with different
            // subkeys.
            assert_eq!(cert_a.fingerprint(), cert_b.fingerprint());
            assert_ne!(keys_a, keys_b);

            let mut all_keys = keys_a.clone();
            all_keys.extend_from_slice(&keys_b);
            all_keys.sort();
            all_keys.dedup();

            // Try with just a, then just b, then both variants.
            for (a, b) in [(true, false), (false, true), (true, true)].iter() {
                if *a && *b {
                    eprintln!("Loading a and b: {:?} {:?}", keys_a, keys_b);
                } else if *a {
                    eprintln!("Loading a: {:?}", keys_a);
                } else if *b {
                    eprintln!("Loading b: {:?}", keys_b);
                }

                let mut keyring: Vec<&Cert> = Vec::new();
                let mut keys: Vec<Fingerprint> = Vec::new();

                if *a {
                    keyring.push(&cert_a);
                    keys.extend_from_slice(&keys_a);
                }
                if *b {
                    keyring.push(&cert_b);
                    keys.extend_from_slice(&keys_b);
                }

                let mut backend = init_backend().await;

                for cert in keyring {
                    $import_cert(&mut backend, cert).await;
                }

                // Check that we can find the cert by iterating.
                let mut got = Vec::new();
                for device in backend.list().await {
                    for mut key in device.list().await {
                        got.push(key.fingerprint());
                    }
                }
                got.sort();

                keys.sort();
                keys.dedup();

                assert_eq!(keys, got,
                    "Expected: {:?}\nGot: {:?}", keys, got);

                // Check that Backend::find_key returns all of the keys.
                for k in all_keys.iter() {
                    let expected = keys.contains(k);
                    eprintln!("Checking find_key({}) => {}",
                              k.to_string(), expected);
                    match (expected, backend.find_key(&k.to_string()).await) {
                        (true, Ok(_)) => {
                        }
                        (true, Err(err)) => {
                            panic!("Didn't find {}, but should have. \
                                    Error message is: {}",
                                   k, err);
                        }
                        (false, Ok(_)) => {
                            panic!("Found {}, but shouldn't have", k);
                        }
                        (false, Err(_err)) => {
                        }
                    }
                }
            }

            Ok(())
        }

        #[tokio::test]
        pub async fn list_only_returns_secret_keys() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // Make sure only keys with secret key material are returned.

            let cert_bytes: &[u8] = test_framework::data::key(
                "dave-not-all-keys-have-secrets.asc");
            let cert = Cert::from_bytes(cert_bytes)
                .expect("valid cert");

            let (keys, public_keys): (Vec<(Fingerprint, bool)> , _) = cert
                .keys()
                .map(|k| (k.key().fingerprint(), k.key().has_secret()))
                .partition(|(_fpr, has_secret)| *has_secret);

            let keys = keys.into_iter().map(|(fpr, _)| fpr)
                .collect::<Vec<Fingerprint>>();
            let public_keys = public_keys.into_iter().map(|(fpr, _)| fpr)
                .collect::<Vec<Fingerprint>>();

            assert!(! keys.is_empty());
            assert!(! public_keys.is_empty());

            let mut backend = init_backend().await;

            $import_cert(&mut backend, &cert).await;

            // List the keys.
            let mut got = Vec::new();
            for device in backend.list().await {
                for key in device.list().await {
                    got.push(key.fingerprint());
                }
            }
            got.sort();

            assert_eq!(keys, got);

            // Check that Backend::find_key returns all of the keys.
            for k in keys.iter() {
                assert!(backend.find_key(&k.to_string()).await.is_ok());
            }
            // And doesn't return the public keys.
            for k in public_keys.iter() {
                assert!(backend.find_key(&k.to_string()).await.is_err());
            }

            Ok(())
        }

        // Check key.export
        #[tokio::test]
        pub async fn key_export() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let can_export: bool = $can_export;
            if ! can_export {
                log::debug!("Backend doesn't support exporting secret key \
                             material.  Skipping test.");
                return Ok(());
            }

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // A simple test: import a certificate.  Make sure the
            // keys are actually imported.

            let mut backend = $init_backend().await;

            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("carol-subkeys-a.asc"))
                .expect("valid cert");

            $import_cert(&mut backend, &cert).await;

            let mut count = 0;
            for key in cert.keys().secret() {
                count += 1;

                let mut found = false;
                'find: for mut d in backend.list().await {
                    for mut k in d.list().await {
                        if k.fingerprint() == key.key().fingerprint() {
                            let exported_key
                                = k.export().await.expect("can export");
                            assert_eq!(&exported_key, key.key());

                            found = true;
                            break 'find;
                        }
                    }
                }

                assert!(found, "{} was not imported", key.key().fingerprint());
            }

            // Make sure we tested something.
            assert!(count > 0);

            Ok(())
        }

        #[tokio::test]
        pub async fn change_password() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let can_change_password: bool = $can_change_password;
            if ! can_change_password {
                log::debug!("Backend doesn't support changing passwords. \
                             Skipping test.");
                return Ok(());
            }

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // Load a certificate where the keys are encrypted with
            // the password "bob".  Make sure we can use it to decrypt
            // a message.  Change the key's password, and relock it.
            // Make sure we can't decrypt the message with the old
            // password, but can decrypt it with the new password.

            let mut backend = $init_backend().await;

            let msg: &[u8] = $crate::test_framework::data::message("bob.asc");
            let fpr_str = "86353DE523CB334DC0B1F0F00A4BA2249168D779";
            let fpr: sequoia_openpgp::Fingerprint
                = fpr_str.parse().expect("valid");
            let keyid = KeyHandle::KeyID(fpr.into());

            // Import Bob's certificate.  They keys are password
            // protected.  Make sure we can use them with the right
            // password, and can't with the wrong one.
            log::info!("Importing certificate variant #1");
            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("bob-password-bob.asc"))
                .expect("valid cert");
            $import_cert(&mut backend, &cert).await;

            log::info!("Decrypting with wrong password:");
            assert!(
                $crate::test_framework::try_decrypt(
                    &mut backend, msg, Some(b"hi bob\n"),
                    keyid.clone(), Some(Password::from("sup3r s3cur3")))
                    .await.is_err());

            log::info!("Decrypting with right password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("bob"))).await.unwrap();

            // Change the password.
            let mut key = backend.find_key(fpr_str)
                .await.expect("key is present");

            if let Err(err) = key.change_password(Some(&"new password".into())).await {
                if let Some($crate::Error::NoInlinePassword(msg))
                    = err.downcast_ref()
                {
                    eprintln!("Changing passwords not supported by \
                               backend: {:?}",
                              msg);
                    return Ok(());
                } else {
                    panic!("Failed to change password");
                }
            }

            // Relock the key.
            key.lock().await.expect("can lock key");

            log::info!("Decrypting with wrong password:");
            assert!($crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("sup3r s3cur3"))).await.is_err());
            log::info!("Decrypting with old password:");
            assert!($crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("smithy"))).await.is_err());
            log::info!("Decrypting with new password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("new password"))).await.unwrap();

            Ok(())
        }


        #[tokio::test]
        pub async fn delete_secret_key_material() -> Result<()> {
            let _ = env_logger::Builder::from_default_env().try_init();

            let can_delete: bool = $can_delete;
            if ! can_delete {
                log::debug!("Backend doesn't support deleting secret key \
                             material.  Skipping test.");
                return Ok(());
            }

            let _serialized = serialize_maybe();

            if ! $preinit() {
                eprintln!("preinit returned false, skipping test.");
                return Ok(());
            }

            // Load a certificate where the keys are encrypted with
            // the password "bob".  Make sure we can use it to decrypt
            // a message.  Delete the primary key.  Make sure we can
            // still decrypt the message.  Delete the decryption key.
            // Make sure we can't decrypt the message anymore.

            let mut backend = $init_backend().await;

            let msg: &[u8] = $crate::test_framework::data::message("bob.asc");

            let fpr_str = "86353DE523CB334DC0B1F0F00A4BA2249168D779";
            let fpr: sequoia_openpgp::Fingerprint
                = fpr_str.parse().expect("valid");
            let keyid = KeyHandle::KeyID(fpr.into());

            // Import Bob's certificate.  They keys are password
            // protected.  Make sure we can use them with the right
            // password, and can't with the wrong one.
            log::info!("Importing certificate variant #1");
            let cert = Cert::from_bytes(
                $crate::test_framework::data::key("bob-password-bob.asc"))
                .expect("valid cert");
            $import_cert(&mut backend, &cert).await;

            log::info!("Decrypting with wrong password:");
            assert!(
                $crate::test_framework::try_decrypt(
                    &mut backend, msg, Some(b"hi bob\n"),
                    keyid.clone(), Some(Password::from("sup3r s3cur3")))
                    .await.is_err());

            log::info!("Decrypting with right password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("bob"))).await.unwrap();

            // Delete the primary key.
            log::info!("Deleting primary key:");
            let primary = cert.fingerprint().to_string();
            let mut key = backend.find_key(&primary)
                .await.expect("key is present");

            if key.password_source().await.is_external_source() {
                eprintln!("Skipping test: backend requires that the user \
                           confirm key deletion");
                return Ok(());
            }

            if let Err(err) = key.delete_secret_key_material().await {
                panic!("Failed to delete {}", primary);
            }

            // Make sure we can't find the primary key any more.
            assert!(backend.find_key(&primary).await.is_err());

            log::info!("Decrypting with right password:");
            $crate::test_framework::try_decrypt(
                &mut backend, msg, Some(b"hi bob\n"),
                keyid.clone(), Some(Password::from("bob"))).await.unwrap();

            log::info!("Deleting decryption key:");
            let mut key = backend.find_key(&fpr_str)
                .await.expect("key is present");

            if let Err(err) = key.delete_secret_key_material().await {
                panic!("Failed to delete {}", primary);
            }

            assert!(
                $crate::test_framework::try_decrypt(
                    &mut backend, msg, Some(b"hi bob\n"),
                    keyid.clone(), Some(Password::from("bob")))
                    .await.is_err());

            // Make sure we can't find the decryption key any more.
            assert!(backend.find_key(&fpr_str).await.is_err());

            Ok(())
        }
    };
}
