extern crate botan;

use std::str::FromStr;

#[test]
fn test_version() -> Result<(), botan::Error> {
    let version = botan::Version::current()?;

    assert!(version.release_date == 0 || version.release_date >= 20181001);

    assert!(version.ffi_api >= 20180713);

    assert!(botan::Version::supports_version(version.ffi_api));
    assert!(botan::Version::supports_version(20180713));
    assert!(!botan::Version::supports_version(20180712));

    assert!(version.at_least(2, 13));
    assert!(version.at_least(2, 4));
    assert!(version.at_least(1, 100));

    println!("{version:?}");

    if cfg!(botan_ffi_20250506) {
        assert!(version.at_least(3, 8));
    }

    if cfg!(botan_ffi_20240408) {
        assert!(version.at_least(3, 4));
    }

    if cfg!(botan_ffi_20231009) {
        assert!(version.at_least(3, 2));
    }

    if cfg!(botan_ffi_20230711) {
        assert!(version.at_least(3, 1));
    }

    if cfg!(botan_ffi_20230403) {
        assert!(version.at_least(3, 0));
    }

    if cfg!(botan_ffi_20191214) {
        assert!(version.at_least(2, 13));
    }

    Ok(())
}

#[test]
fn test_hash() -> Result<(), botan::Error> {
    let mut hash = botan::HashFunction::new("SHA-384")?;

    assert_eq!(hash.output_length()?, 48);
    assert_eq!(hash.block_size()?, 128);
    assert_eq!(hash.algo_name()?, "SHA-384");

    assert!(hash.update(&[97, 98]).is_ok());

    let mut hash_dup = hash.duplicate()?;

    assert!(hash.update(&[99]).is_ok());
    assert!(hash_dup.update(&[100]).is_ok());

    hash.clear()?;

    hash.update(&[97, 98, 99])?;

    let digest = hash.finish()?;

    assert_eq!(botan::hex_encode(&digest)?,
               "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7");

    let digest_dup = hash_dup.finish()?;

    assert_eq!(botan::hex_encode(&digest_dup)?,
               "5D15BCEBB965FA77926C23471C96E3A326B363F5F105C3EF17CFD033B9734FA46556F81A26BB3044D2DDA50481325EF7");

    let bad_hash = botan::HashFunction::new("BunnyHash9000");

    assert!(bad_hash.is_err());
    assert_eq!(
        bad_hash.as_ref().unwrap_err().error_type(),
        botan::ErrorType::NotImplemented
    );
    Ok(())
}

#[test]
fn test_mac() -> Result<(), botan::Error> {
    let mut mac = botan::MsgAuthCode::new("HMAC(SHA-384)")?;

    let key_spec = mac.key_spec()?;
    assert_eq!(mac.output_length()?, 48);
    assert_eq!(mac.algo_name()?, "HMAC(SHA-384)");

    assert!(key_spec.is_valid_keylength(20));

    mac.set_key(&[0xAA; 20])?;

    mac.update(&[0xDD; 1])?;
    mac.update(&[0xDD; 29])?;
    mac.update(&[0xDD; 20])?;

    let r = mac.finish()?;

    assert_eq!(botan::hex_encode(&r)?,
               "88062608D3E6AD8A0AA2ACE014C8A86F0AA635D947AC9FEBE83EF4E55966144B2A5AB39DC13814B94E3AB6E101A34F27");
    Ok(())
}

#[test]
fn test_block_cipher() -> Result<(), botan::Error> {
    let mut bc = botan::BlockCipher::new("AES-128")?;

    assert_eq!(bc.algo_name()?, "AES-128");
    assert_eq!(bc.block_size()?, 16);

    let key_spec = bc.key_spec()?;

    assert!(!key_spec.is_valid_keylength(20));
    assert!(key_spec.is_valid_keylength(16));

    assert_eq!(
        bc.set_key(&[0; 32]).unwrap_err().error_type(),
        botan::ErrorType::InvalidKeyLength
    );

    bc.set_key(&[0; 16])?;

    let input = vec![0; 16];

    let exp_ctext = "66E94BD4EF8A2C3B884CFA59CA342B2E";

    let ctext = bc.encrypt_blocks(&input)?;
    assert_eq!(botan::hex_encode(&ctext)?, exp_ctext);

    let ptext = bc.decrypt_blocks(&ctext)?;

    assert_eq!(ptext, input);

    let mut buf = input.clone();
    bc.encrypt_in_place(&mut buf)?;
    assert_eq!(botan::hex_encode(&buf)?, exp_ctext);

    bc.decrypt_in_place(&mut buf)?;
    assert_eq!(buf, input);
    Ok(())
}

#[test]
fn test_cipher() -> Result<(), botan::Error> {
    let mut cipher = botan::Cipher::new("AES-128/GCM", botan::CipherDirection::Encrypt)?;

    assert_eq!(cipher.tag_length(), 16);

    let zero16 = vec![0; 16];
    let zero12 = vec![0; 12];

    assert!(cipher.set_associated_data(&[1, 2, 3]).is_err()); // trying to set AD before key is set
    assert_eq!(
        cipher.set_key(&[0; 42]).unwrap_err().error_type(),
        botan::ErrorType::InvalidKeyLength
    );

    cipher.set_key(&zero16)?;

    assert!(cipher.update_granularity() > 0);

    if let Some(i) = cipher.ideal_update_granularity() {
        assert!(i >= cipher.update_granularity());
    }

    cipher.set_associated_data(&[1, 2, 3])?;
    cipher.set_associated_data(&[])?;

    let ctext = cipher.process(&zero12, &zero16)?;

    assert_eq!(
        botan::hex_encode(&ctext)?,
        "0388DACE60B6A392F328C2B971B2FE78AB6E47D42CEC13BDF53A67B21257BDDF"
    );

    let mut cipher = botan::Cipher::new("AES-128/GCM", botan::CipherDirection::Decrypt)?;
    cipher.set_key(&zero16)?;

    let ptext = cipher.process(&zero12, &ctext)?;

    assert_eq!(ptext, zero16);
    Ok(())
}

#[test]
fn test_incremental_cipher() -> Result<(), botan::Error> {
    // This test requires Botan 2.9 or higher to work correctly
    if !botan::Version::current()?.at_least(2, 9) {
        return Ok(());
    }

    // Key    = 00000000000000000000000000000000
    // Nonce  = 0AAC82F3E53C2756034F7BD5827C9EDD
    // In     = 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    // Out    = 38C21B6430D9A3E4BC6749405765653AE91051E96CE0D076141DD7B515EC150FDB8A65EE988D206C9F64874664CDBF61257FFAE521B9A5EB5B35E3745F4232025B269A6CD7DCFE19153ECF7341CE2C6A6A87F95F2109841350DA3D24EEED4E4E32D2BED880737670FFE8ED76DB890FD72A0076300E50914984A777C9F2BC843977396C602B24E7A045F04D15CD2EAC01AD8808064CFE5A2DC1AE9FFFA4BF0A6F0C07668097DEEB9C5CA5EC1F9A52F96A403B73FEA2DBBF44473D355553EE7FB1B4D6630777DAF67804BE213089B9F78652CE970C582FD813F87FF0ECBACCE1CA46247E20D09F3E0B4EF6BFCD13244C6877F25E6646252CAD6EB7DBBA3476AAAC83BC3285FF70B50D6CDEDC8E5921944A

    let key = botan::hex_decode("00000000000000000000000000000000")?;
    let nonce = botan::hex_decode("0AAC82F3E53C2756034F7BD5827C9EDD")?;
    let input = botan::hex_decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")?;
    let output = botan::hex_decode("38C21B6430D9A3E4BC6749405765653AE91051E96CE0D076141DD7B515EC150FDB8A65EE988D206C9F64874664CDBF61257FFAE521B9A5EB5B35E3745F4232025B269A6CD7DCFE19153ECF7341CE2C6A6A87F95F2109841350DA3D24EEED4E4E32D2BED880737670FFE8ED76DB890FD72A0076300E50914984A777C9F2BC843977396C602B24E7A045F04D15CD2EAC01AD8808064CFE5A2DC1AE9FFFA4BF0A6F0C07668097DEEB9C5CA5EC1F9A52F96A403B73FEA2DBBF44473D355553EE7FB1B4D6630777DAF67804BE213089B9F78652CE970C582FD813F87FF0ECBACCE1CA46247E20D09F3E0B4EF6BFCD13244C6877F25E6646252CAD6EB7DBBA3476AAAC83BC3285FF70B50D6CDEDC8E5921944A")?;

    // encode
    let mut cipher = botan::Cipher::new("AES-128/GCM", botan::CipherDirection::Encrypt)?;
    cipher.set_key(&key)?;
    cipher.start(&nonce)?;

    let enc_iter = input.chunks(cipher.update_granularity()).enumerate();
    let chunks = if input.len() % cipher.update_granularity() == 0 {
        input.len() / cipher.update_granularity()
    } else {
        input.len() / cipher.update_granularity() + 1
    };
    let mut enc_out = vec![0; 0];
    for (cnt, v) in enc_iter {
        let mut res = if (cnt + 1) < chunks {
            cipher.update(v)?
        } else {
            cipher.finish(v)?
        };
        enc_out.append(&mut res);
    }

    assert_eq!(botan::hex_encode(&enc_out)?,
               "38C21B6430D9A3E4BC6749405765653AE91051E96CE0D076141DD7B515EC150FDB8A65EE988D206C9F64874664CDBF61257FFAE521B9A5EB5B35E3745F4232025B269A6CD7DCFE19153ECF7341CE2C6A6A87F95F2109841350DA3D24EEED4E4E32D2BED880737670FFE8ED76DB890FD72A0076300E50914984A777C9F2BC843977396C602B24E7A045F04D15CD2EAC01AD8808064CFE5A2DC1AE9FFFA4BF0A6F0C07668097DEEB9C5CA5EC1F9A52F96A403B73FEA2DBBF44473D355553EE7FB1B4D6630777DAF67804BE213089B9F78652CE970C582FD813F87FF0ECBACCE1CA46247E20D09F3E0B4EF6BFCD13244C6877F25E6646252CAD6EB7DBBA3476AAAC83BC3285FF70B50D6CDEDC8E5921944A");

    // Try the same with the allocation-free interface.
    cipher.set_key(&key)?;
    cipher.start(&nonce)?;
    let mut enc_out_prealloc = vec![0; input.len() + cipher.tag_length()];
    let mut written = 0;
    for (cnt, v) in input.chunks(cipher.update_granularity()).enumerate() {
        if (cnt + 1) < chunks {
            written += cipher.update_into(v, &mut enc_out_prealloc[written..])?
        } else {
            written += cipher.finish_into(v, &mut enc_out_prealloc[written..])?
        }
    }
    assert_eq!(enc_out, enc_out_prealloc);

    // decode
    let mut cipher = botan::Cipher::new("AES-128/GCM", botan::CipherDirection::Decrypt)?;
    cipher.set_key(&key)?;
    cipher.start(&nonce)?;
    let chunk_size = cipher.update_granularity();
    let mut dec_iter = output.chunks(chunk_size).enumerate();
    let chunks = if output.len() % chunk_size == 0 {
        output.len() / chunk_size
    } else {
        output.len() / chunk_size + 1
    };
    let mut dec_out = vec![0; 0];
    for (cnt, v) in dec_iter.by_ref() {
        let mut res = cipher.update(v)?;
        dec_out.append(&mut res);
        if (cnt + 3) == chunks {
            break;
        }
    }
    let mut remain = vec![0; 0];
    let (_, v) = dec_iter.next().unwrap(); //  the one before last one
    remain.append(v.to_vec().as_mut());
    let (_, v) = dec_iter.next().unwrap(); //  last one
    remain.append(v.to_vec().as_mut());
    let mut res = cipher.finish(&remain)?;
    dec_out.append(&mut res);
    assert_eq!(botan::hex_encode(&dec_out)?,
               "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");

    // Try the same with the allocation-free interface.
    cipher.set_key(&key)?;
    cipher.start(&nonce)?;
    let mut dec_out_prealloc = vec![0; output.len() - cipher.tag_length()];
    let mut written = 0;
    for (cnt, v) in output.chunks(chunk_size).enumerate() {
        if (cnt + 1) < chunks {
            written += cipher.update_into(v, &mut dec_out_prealloc[written..])?
        } else {
            written += cipher.finish_into(v, &mut dec_out_prealloc[written..])?
        }
    }
    assert_eq!(dec_out, dec_out_prealloc);

    Ok(())
}

#[test]
fn test_chacha() -> Result<(), botan::Error> {
    let mut cipher = botan::Cipher::new("ChaCha20", botan::CipherDirection::Encrypt)?;

    assert_eq!(cipher.tag_length(), 0);

    let key_spec = cipher.key_spec()?;

    assert!(!key_spec.is_valid_keylength(0));
    assert!(key_spec.is_valid_keylength(16));
    assert!(key_spec.is_valid_keylength(32));
    assert!(!key_spec.is_valid_keylength(48));

    let key = vec![0; 32];

    let expected = botan::hex_decode("76B8E0ADA0F13D90405D6AE55386BD28BDD219B8A08DED1AA836EFCC8B770DC7DA41597C5157488D7724E03FB8D84A376A43B8F41518A11CC387B669")?;

    cipher.set_key(&key)?;

    assert!(cipher.set_associated_data(&[1, 2, 3]).is_err()); // not an AEAD
    assert!(cipher.set_associated_data(&[]).is_err());

    let iv = vec![];
    let input = vec![0; expected.len()];

    let ctext = cipher.process(&iv, &input)?;

    assert_eq!(ctext, expected);
    Ok(())
}

#[test]
fn test_kdf() -> Result<(), botan::Error> {
    let salt = botan::hex_decode("000102030405060708090A0B0C")?;
    let label = botan::hex_decode("F0F1F2F3F4F5F6F7F8F9")?;
    let secret = botan::hex_decode("0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B0B")?;
    let expected_output = botan::hex_decode(
        "3CB25F25FAACD57A90434F64D0362F2A2D2D0A90CF1A5A4C5DB02D56ECC4C5BF34007208D5B887185865",
    )?;

    let output = botan::kdf(
        "HKDF(SHA-256)",
        expected_output.len(),
        &secret,
        &salt,
        &label,
    )?;

    assert_eq!(output, expected_output);
    Ok(())
}

#[test]
fn test_pbkdf() -> Result<(), botan::Error> {
    let salt = botan::hex_decode("0001020304050607")?;
    let iterations = 10000;
    let passphrase = "xyz";
    let expected_output =
        botan::hex_decode("DEFD2987FA26A4672F4D16D98398432AD95E896BF619F6A6B8D4ED")?;

    let output = botan::pbkdf(
        "PBKDF2(SHA-256)",
        expected_output.len(),
        passphrase,
        &salt,
        iterations,
    )?;

    assert_eq!(output, expected_output);
    Ok(())
}

#[test]
fn test_scrypt() -> Result<(), botan::Error> {
    let salt = botan::hex_decode("4E61436C")?;
    let n = 1024;
    let r = 8;
    let p = 16;
    let passphrase = "password";
    let expected_output =
        botan::hex_decode("fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622e")?;

    let output = botan::scrypt(expected_output.len(), passphrase, &salt, n, r, p)?;

    assert_eq!(output, expected_output);
    Ok(())
}

#[test]
fn test_pwdhash() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new()?;
    let salt = rng.read(10)?;
    let msec = 30;
    let (key, r, p, n) =
        botan::derive_key_from_password_timed("Scrypt", 32, "passphrase", &salt, msec)?;
    assert_eq!(key.len(), 32);
    let key2 = botan::derive_key_from_password("Scrypt", 32, "passphrase", &salt, n, r, p)?;
    assert_eq!(key, key2);
    Ok(())
}

#[test]
fn test_hex() -> Result<(), botan::Error> {
    let raw = vec![1, 2, 3, 255, 42, 23];
    assert_eq!(botan::hex_encode(&raw)?, "010203FF2A17");
    assert_eq!(botan::hex_decode("010203FF2A17")?, raw);
    Ok(())
}

#[test]
fn test_rng() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let read1 = rng.read(10)?;
    let read2 = rng.read(10)?;

    assert!(read1 != read2);
    Ok(())
}

#[test]
fn test_crl() -> Result<(), botan::Error> {
    let crl_pem = r"-----BEGIN X509 CRL-----
MIIBszCBnAIBATANBgkqhkiG9w0BAQsFADAjMRQwEgYDVQQDDAtUZXN0IFN1YiBD
QTELMAkGA1UEBhMCREUXDTE3MDcyNjEzMjQ0N1oXDTE3MTAyNTEzMjQ0N1owFDAS
AgEBFw0xNzA3MjYwNzI0NDdaoC8wLTAKBgNVHRQEAwIBAjAfBgNVHSMEGDAWgBTg
jBmqaSWaGiUA9rMouhv3DfnPwDANBgkqhkiG9w0BAQsFAAOCAQEASVKeNF4ozBHs
jKss2P4Wor9/yvCi6PH+f3fr774nbpW4hK7BqMDfLt1lyPMMRil/Z0FGsTF4wy9f
CSnI/NNIqDLdPfL/Wq40swJvuR3p7CjGwEZjfYJ3Zbz+JZJBws7Eg6JtBLHAc9JQ
uw9odU3oBt9w9DP0Oh3idfXAQp1Ho/nK+ssXOEo1ADETFjaVooXSpeJ7Khi/6Asq
L8A/gDTbuT6K3bEusSXhMo1juAN3oDj7Ruev+CWx0EkxtM9AUBzw707kL7ELxm3g
8FsPC0ejfaQIPluaEBgTuaHtGCUCKnUeUYc2MHPJZZWSaNpqv9OLoAn/25tvsyvI
pIKYuraHXw==
-----END X509 CRL-----";

    let cert_pem = r"-----BEGIN CERTIFICATE-----
MIIDbzCCAlegAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMRQwEgYDVQQDDAtUZXN0
IFN1YiBDQTELMAkGA1UEBhMCREUwHhcNMTcwNzI3MDUyNDQ2WhcNMTgwNzI3MTMy
NDQ2WjAfMRAwDgYDVQQDDAdUZXN0IEVFMQswCQYDVQQGEwJERTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJWomlxlqcdd/t7IQjIGy2M0nEP8Bah1ooLr
Iq73gzeVFM4qIjKL2OyQYbJDLkMXWUmzmX5P5cGG3pjAJ+Van0ch+OL6Utj8RijJ
Ufc10Oo/TIVzIXbMxIa1oLZ8gQ73nhFkNxQZxzgsKop8wPPTdo41p3DSw/+a9cD0
bmqgjSMDYydfuo/42bLeSmlLYhF18T5C1gUn+JXMvQJSI6kLPczsi+mQ0N0GtV0C
1whWBo1atAUK3OeYGRDzIXnE591vXICYg1JBjjEYe6VNK/B3vCEu/QFRLtF3IYHc
loU4uurI8HfgeEbJ/H3/uDrUwgahuoIqRXtxNIQv1qGXwbAnPBcCAwEAAaOBsTCB
rjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vbG9jYWxob3N0L3N1YmNhL2NybGRw
L2NybC5jcmwwHwYDVR0jBBgwFoAU4IwZqmklmholAPazKLob9w35z8AwHQYDVR0O
BBYEFITwNLOf7HFKC/d5A2X3c8Qdd3sQMA4GA1UdDwEB/wQEAwIHgDAPBgNVHRMB
Af8EBTADAQEAMBQGA1UdEQQNMAuCCWR1bW15aG9zdDANBgkqhkiG9w0BAQsFAAOC
AQEABaLToWh/aCEOlWbi810W1FeAvkwJYSqyYK6hyFYiQ6dn0x4OnqytfVxYKaj6
wp1OOy/aAG9sSak2/Yp18J7U9Nnl/fz66TNBwjsipAo2auHsqfvrxQl4xEyovdzp
OhJY4gLUPrmbecsmI4UTU6Xl9IGMDh9LPAn1ErYUvvolJp9Y6XlSb/jHT/7BDMmq
JwToSRDikiaCKtrjQvtgw5vFUfcBqKlQmy70ZxIHW92E4cq1twugzc1RpO/c0mxj
zjd8FaHJYT9q6z5fRhloqN6w46mS9nbt8xa4As9ULoMcpeVglDXXLh+A8HLLudWD
ZB6LDkS9rU3WAqYfPzNZ5AR06A==
-----END CERTIFICATE-----";

    let crl = botan::CRL::load(crl_pem.as_bytes())?;

    let cert = botan::Certificate::load(cert_pem.as_bytes())?;

    assert!(crl.is_revoked(&cert)?);

    Ok(())
}

#[test]
fn test_certs() -> Result<(), botan::Error> {
    let cert_bits = botan::hex_decode("3082035A30820305A003020102020101300C06082A8648CE3D04030105003050310B3009060355040613024445310D300B060355040A0C0462756E64310C300A060355040B0C03627369310D300B06035504051304343536373115301306035504030C0C637363612D6765726D616E79301E170D3037303731393135323731385A170D3238303131393135313830305A3050310B3009060355040613024445310D300B060355040A0C0462756E64310C300A060355040B0C03627369310D300B06035504051304343536373115301306035504030C0C637363612D6765726D616E79308201133081D406072A8648CE3D02013081C8020101302806072A8648CE3D0101021D00D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF303C041C68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43041C2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B0439040D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD021D00D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F020101033A000401364A4B0F0102E9502AB9DC6855D90B065A6F5E5E48395F8309D57C11ABAFF21756607EF6757EC9886CA222D83CA04B1A99FA43C5A9BCE1A38201103082010C30360603551D11042F302D8118637363612D6765726D616E79406273692E62756E642E646586116661783A2B343932323839353832373232300E0603551D0F0101FF040403020106301D0603551D0E041604140096452DE588F966C4CCDF161DD1F3F5341B71E7301F0603551D230418301680140096452DE588F966C4CCDF161DD1F3F5341B71E730410603551D20043A30383036060904007F0007030101013029302706082B06010505070201161B687474703A2F2F7777772E6273692E62756E642E64652F6373636130120603551D130101FF040830060101FF020100302B0603551D1004243022800F32303037303731393135323731385A810F32303237313131393135313830305A300C06082A8648CE3D0403010500034100303E021D00C6B41E830217FD4C93B59E9E2B13734E09C182FA63FAEE4115A8EDD5021D00D27938DA01B8951A9064A1B696AEDF181B74968829C138F0EB2F623B")?;

    let cert = botan::Certificate::load(&cert_bits)?;

    let key_id = botan::hex_decode("0096452DE588F966C4CCDF161DD1F3F5341B71E7")?;
    assert_eq!(cert.serial_number()?, vec![1]);
    assert_eq!(cert.authority_key_id()?, key_id);
    assert_eq!(cert.subject_key_id()?, key_id);

    assert_eq!(cert.not_before_raw()?, 1184858838);
    assert_eq!(cert.not_after_raw()?, 1831907880);

    #[cfg(feature = "std")]
    {
        assert_eq!(
            cert.not_before()?,
            std::time::UNIX_EPOCH + std::time::Duration::from_secs(1184858838)
        );
        assert_eq!(
            cert.not_after()?,
            std::time::UNIX_EPOCH + std::time::Duration::from_secs(1831907880)
        );
    }

    assert!(cert.allows_usage(botan::CertUsage::CertificateSign)?);
    assert!(cert.allows_usage(botan::CertUsage::CrlSign)?);
    assert!(!(cert.allows_usage(botan::CertUsage::KeyEncipherment)?));

    let pubkey = cert.public_key()?;

    assert_eq!(pubkey.algo_name()?, "ECDSA");

    assert_eq!(
        botan::hex_encode(&pubkey.fingerprint("SHA-256")?)?,
        "110467922A582F7F35E55DF1C787709A6D27B4F581C02586E9076F1A385404B3"
    );
    Ok(())
}

#[test]
fn test_cert_verify() -> Result<(), botan::Error> {
    let ca = b"-----BEGIN CERTIFICATE-----
MIIBkDCCATegAwIBAgIRANQudMcHu/SmX8470nbNlj0wCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEAxMHVGVzdCBDQTAeFw0xODA4MTYyMjMyNDFaFw00NjAxMDEyMjMyNDFa
MBIxEDAOBgNVBAMTB1Rlc3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASN
+LHr9ZN72sxZqi4zcYDIg4xzN3DOF3epvlpGHLnju5ogp8dJ46YydTi3g/SfBGOp
j9jrYP5Jgkkmpo0lMh7ho24wbDAhBgNVHQ4EGgQYLg/lfneWJ36rZdGMoVyKD6Zl
mHkST7ZNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMCMGA1Ud
IwQcMBqAGC4P5X53lid+q2XRjKFcig+mZZh5Ek+2TTAKBggqhkjOPQQDAgNHADBE
AiB30ZIFV1cZbknu5lt1fWrM9tNSgCbj5BN9CI+Q9aq1LQIgD9o/8oGmFgvWLjsx
b39VOu00+Vy9kpNO1Sgx7wSWoIU=
-----END CERTIFICATE-----";

    let ee = b"-----BEGIN CERTIFICATE-----
MIIBoDCCAUagAwIBAgIRAK27a2NlSYEH63xIsAbBA1wwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEAxMHVGVzdCBDQTAeFw0xODA4MTYyMjMzNDBaFw00NjAxMDEyMjMzNDBa
MBoxGDAWBgNVBAMTD1Rlc3QgRW5kIEVudGl0eTBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABDykQMvlV7GyIJeANLWEs5bXReqpvTEFu3zYPBjOhyx784VPVl84h8c5
ycru3Hk8N/SIITSWzpbjPMp9jRbyDy+jdTBzMCEGA1UdDgQaBBjkPzL+BXHtQJDR
ciwvzeHQKuQZOstyM2swGwYDVR0RBBQwEoIQdGVzdC5leGFtcGxlLmNvbTAMBgNV
HRMBAf8EAjAAMCMGA1UdIwQcMBqAGC4P5X53lid+q2XRjKFcig+mZZh5Ek+2TTAK
BggqhkjOPQQDAgNIADBFAiEAowK8jGhosOxQpOCjlRg0nFceQ0ETITQC43fk0CZA
AzMCIEJSRDmXjX8TMTbSfoTLmhaYJnCL+AfHLZLdHlSLDIzh
-----END CERTIFICATE-----";

    // Bit flipped from ee
    let bad_ee = b"-----BEGIN CERTIFICATE-----
MIIBoDCCAUagAwIBAgIRAK27a2NlSYEH63xIsAbBA1wwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEAxMHVGVzdCBDQTAeFw0xODA4MTYyMjMzNDBaFw00NjAxMDEyMjMzNDBa
MBoxGDAWBgNVBAMTD1Rlc3QgrW5kIEVudGl0eTBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABDykQMvlV7GyIJeANLWEs5bXReqpvTEFu3zYPBjOhyx784VPVl84h8c5
ycru3Hk8N/SIITSWzpbjPMp9jRbyDy+jdTBzMCEGA1UdDgQaBBjkPzL+BXHtQJDR
ciwvzeHQKuQZOstyM2swGwYDVR0RBBQwEoIQdGVzdC5leGFtcGxlLmNvbTAMBgNV
HRMBAf8EAjAAMCMGA1UdIwQcMBqAGC4P5X53lid+q2XRjKFcig+mZZh5Ek+2TTAK
BggqhkjOPQQDAgNIADBFAiEAowK8jGhosOxQpOCjlRg0nFceQ0ETITQC43fk0CZA
AzMCIEJSRDmXjX8TMTbSfoTLmhaYJnCL+AfHLZLdHlSLDIzh
-----END CERTIFICATE-----";

    let ca = botan::Certificate::load(ca)?;

    assert!(ca.to_string()?.starts_with("Version: 3"));

    assert_eq!(ca.public_key_bits()?.len(), 89);

    let ee = botan::Certificate::load(ee)?;
    let bad_ee = botan::Certificate::load(bad_ee)?;

    let ca_dup = ca.clone();

    let result = ee.verify(&[], &[&ca], None, None, None)?;
    assert!(result.success());
    assert_eq!(result.to_string(), "Verified");

    let result = ee.verify(&[], &[&ca], None, None, Some(300))?;
    assert!(!result.success());
    assert_eq!(result.to_string(), "Certificate is not yet valid");

    let result = ee.verify(&[], &[&ca], None, Some("no.hostname.com"), None)?;
    assert!(!result.success());
    assert_eq!(
        result.to_string(),
        "Certificate does not match provided name"
    );

    let result = ee.verify(&[], &[], None, None, None)?;
    assert!(!result.success());
    assert_eq!(result.to_string(), "Certificate issuer not found");

    let result = bad_ee.verify(&[], &[&ca_dup], None, None, None)?;
    assert!(!result.success());
    assert_eq!(result.to_string(), "Signature error");
    Ok(())
}

#[test]
fn test_bcrypt() -> Result<(), botan::Error> {
    let pass = "password";
    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let bcrypt1 = botan::bcrypt_hash(pass, &mut rng, 10)?;

    assert_eq!(bcrypt1.len(), 60);

    let bcrypt2 = botan::bcrypt_hash(pass, &mut rng, 10)?;

    assert_eq!(bcrypt2.len(), 60);

    assert!(bcrypt1 != bcrypt2);

    assert!(botan::bcrypt_verify(pass, &bcrypt1)?);
    assert!(botan::bcrypt_verify(pass, &bcrypt2)?);

    assert!(!(botan::bcrypt_verify("passwurd", &bcrypt2)?));
    Ok(())
}

#[test]
fn test_pubkey() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let ecdsa_key = botan::Privkey::create("ECDSA", "secp256r1", &mut rng)?;

    assert!(ecdsa_key.check_key(&mut rng)?);
    assert_eq!(ecdsa_key.algo_name()?, "ECDSA");

    assert!(ecdsa_key.get_field("n").is_err());
    assert_eq!(
        ecdsa_key.get_field("order"),
        botan::MPI::from_str("0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551")
    );

    let pub_key = ecdsa_key.pubkey()?;

    assert_eq!(pub_key.algo_name()?, "ECDSA");

    let bits = ecdsa_key.der_encode()?;
    let pem = ecdsa_key.pem_encode()?;
    assert!(pem.starts_with("-----BEGIN PRIVATE KEY-----\n"));
    assert!(pem.ends_with("-----END PRIVATE KEY-----\n"));

    let pub_bits = pub_key.der_encode()?;
    let pub_pem = pub_key.pem_encode()?;
    assert!(pub_pem.starts_with("-----BEGIN PUBLIC KEY-----\n"));
    assert!(pub_pem.ends_with("-----END PUBLIC KEY-----\n"));

    let loaded_key = botan::Privkey::load_der(&bits)?;
    assert!(loaded_key.check_key(&mut rng)?);

    let loaded_pem_key = botan::Pubkey::load_pem(&pub_pem)?;
    assert!(loaded_pem_key.check_key(&mut rng)?);

    let loaded_bits = loaded_key.der_encode()?;
    let loaded_pub_key = loaded_key.pubkey()?;
    assert_eq!(loaded_pub_key.algo_name()?, "ECDSA");
    let loaded_pub_bits = loaded_pub_key.der_encode()?;

    assert_eq!(bits, loaded_bits);
    assert_eq!(pub_bits, loaded_pub_bits);
    Ok(())
}

#[test]
fn test_x25519() -> Result<(), botan::Error> {
    // Test from RFC 8037
    let a_pub_bits =
        botan::hex_decode("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")?;
    let b_priv_bits =
        botan::hex_decode("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")?;
    let b_pub_bits =
        botan::hex_decode("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")?;
    let expected_shared =
        botan::hex_decode("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")?;

    let a_pub = botan::Pubkey::load_x25519(&a_pub_bits)?;
    assert_eq!(a_pub.get_x25519_key()?, a_pub_bits);

    let b_priv = botan::Privkey::load_x25519(&b_priv_bits)?;
    assert_eq!(b_priv.get_x25519_key()?, b_priv_bits);

    assert_eq!(b_priv.key_agreement_key()?, b_pub_bits);
    assert_eq!(b_priv.pubkey()?.get_x25519_key()?, b_pub_bits);

    let shared = b_priv.agree(&a_pub_bits, 0, &[], "Raw")?;

    assert_eq!(shared, expected_shared);
    Ok(())
}

#[test]
fn test_ed25519() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let msg = vec![23, 42, 69, 6, 66];
    let padding = "Pure";

    let ed_priv = botan::Privkey::create("Ed25519", "", &mut rng)?;

    let signature1 = ed_priv.sign(&msg, padding, &mut rng)?;

    let ed_bits = ed_priv.get_ed25519_key()?;

    let ed_loaded = botan::Privkey::load_ed25519(&ed_bits.1)?;
    let signature2 = ed_loaded.sign(&msg, padding, &mut rng)?;

    let ed_pub = ed_priv.pubkey()?;

    assert!(ed_pub.verify(&msg, &signature1, padding)?);
    assert!(ed_pub.verify(&msg, &signature2, padding)?);

    let ed_loaded = botan::Pubkey::load_ed25519(&ed_bits.0)?;
    assert!(ed_loaded.verify(&msg, &signature1, padding)?);
    assert!(ed_loaded.verify(&msg, &signature2, padding)?);

    assert_eq!(ed_loaded.get_ed25519_key()?, ed_pub.get_ed25519_key()?);

    assert_eq!(signature1, signature2);
    Ok(())
}

#[test]
fn test_rsa() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let padding = "EMSA-PKCS1-v1_5(SHA-256)";
    let msg = rng.read(32)?;

    let privkey = botan::Privkey::create("RSA", "1024", &mut rng)?;
    let pubkey = privkey.pubkey()?;

    assert_eq!(privkey.get_field("e"), botan::MPI::from_str("65537"));
    assert_eq!(privkey.get_field("n")?.bit_count()?, 1024);

    assert_eq!(pubkey.get_field("n"), privkey.get_field("n"));

    let p = privkey.get_field("p")?;
    let q = privkey.get_field("q")?;

    assert_eq!(&p * &q, privkey.get_field("n")?);

    let signature = privkey.sign(&msg, padding, &mut rng)?;

    assert!(pubkey.verify(&msg, &signature, padding)?);

    let pubkey = botan::Pubkey::load_rsa(&privkey.get_field("n")?, &privkey.get_field("e")?)?;
    assert!(pubkey.verify(&msg, &signature, padding)?);
    Ok(())
}

#[test]
fn test_pubkey_encryption() -> Result<(), botan::Error> {
    let padding = "EMSA-PKCS1-v1_5(SHA-256)";
    let msg = [1, 2, 3];

    let mut rng = botan::RandomNumberGenerator::new_system()?;
    let key = botan::Privkey::create("RSA", "1024", &mut rng)?;

    let der = key.der_encode_encrypted("passphrase", &mut rng)?;
    let pem = key.pem_encode_encrypted("pemword", &mut rng)?;

    assert!(pem.starts_with("-----BEGIN ENCRYPTED PRIVATE KEY-----\n"));
    assert!(pem.ends_with("-----END ENCRYPTED PRIVATE KEY-----\n"));

    let sig1 = key.sign(&msg, padding, &mut rng)?;

    //assert!(botan::Privkey::load_encrypted_der(&der, "i forget").is_err());

    let load = botan::Privkey::load_encrypted_der(&der, "passphrase")?;
    let sig2 = load.sign(&msg, padding, &mut rng)?;

    assert_eq!(sig1, sig2);

    let load = botan::Privkey::load_encrypted_pem(&pem, "pemword")?;
    let sig3 = load.sign(&msg, padding, &mut rng)?;

    assert_eq!(sig1, sig3);
    Ok(())
}

#[test]
fn test_pubkey_sign() -> Result<(), botan::Error> {
    let msg = vec![1, 23, 42];

    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let ecdsa_key = botan::Privkey::create("ECDSA", "secp256r1", &mut rng)?;
    assert!(ecdsa_key.key_agreement_key().is_err());

    let signature = ecdsa_key.sign(&msg, "EMSA1(SHA-256)", &mut rng)?;

    let pub_key = ecdsa_key.pubkey()?;

    let mut verifier = botan::Verifier::new(&pub_key, "EMSA1(SHA-256)")?;

    verifier.update(&[1])?;
    verifier.update(&[23, 42])?;

    assert!(verifier.finish(&signature)?);

    verifier.update(&[1])?;
    assert!(!(verifier.finish(&signature)?));

    verifier.update(&[1])?;
    verifier.update(&[23, 42])?;

    assert!(verifier.finish(&signature)?);
    Ok(())
}

#[test]
fn test_pubkey_sign_der() -> Result<(), botan::Error> {
    let msg = vec![1, 23, 42];

    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let ecdsa_key = botan::Privkey::create("ECDSA", "secp256r1", &mut rng)?;
    assert!(ecdsa_key.key_agreement_key().is_err());

    let hash = "EMSA1(SHA-256)";

    let mut signer = botan::Signer::new_with_der_formatted_signatures(&ecdsa_key, hash)?;
    signer.update(&msg)?;
    let signature = signer.finish(&mut rng)?;

    let pub_key = ecdsa_key.pubkey()?;

    let mut verifier = botan::Verifier::new_with_der_formatted_signatures(&pub_key, hash)?;

    verifier.update(&[1])?;
    verifier.update(&[23, 42])?;

    assert!(verifier.finish(&signature)?);

    verifier.update(&[1])?;
    assert!(!(verifier.finish(&signature)?));

    verifier.update(&[1])?;
    verifier.update(&[23, 42])?;

    assert!(verifier.finish(&signature)?);

    // DER Signatures start with a SEQUENCE
    assert_eq!(signature[0], 0x30);

    // The SEQUENCE contains the whole signature (for ECDSA w/ secp256r1, the length can always be encoded with a single byte)
    assert_eq!(signature[1], (signature.len() - 2) as u8);

    // The first element is an INTEGER
    assert_eq!(signature[2], 0x02);
    Ok(())
}

#[test]
fn test_pubkey_encrypt() -> Result<(), botan::Error> {
    let msg = vec![1, 23, 42];

    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let priv_key = botan::Privkey::create("RSA", "2048", &mut rng)?;
    assert!(priv_key.key_agreement_key().is_err());
    let pub_key = priv_key.pubkey()?;

    let mut encryptor = botan::Encryptor::new(&pub_key, "OAEP(SHA-256)")?;

    let ctext = encryptor.encrypt(&msg, &mut rng)?;
    assert_eq!(ctext.len(), 2048 / 8);

    let mut decryptor = botan::Decryptor::new(&priv_key, "OAEP(SHA-256)")?;

    let ptext = decryptor.decrypt(&ctext)?;

    assert_eq!(ptext, msg);
    Ok(())
}

#[test]
fn test_pubkey_key_agreement() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new_system()?;

    let a_priv = botan::Privkey::create("ECDH", "secp384r1", &mut rng)?;
    let b_priv = botan::Privkey::create("ECDH", "secp384r1", &mut rng)?;

    let a_pub = a_priv.key_agreement_key()?;
    let b_pub = b_priv.key_agreement_key()?;

    let mut a_ka = botan::KeyAgreement::new(&a_priv, "KDF2(SHA-384)")?;
    let mut b_ka = botan::KeyAgreement::new(&b_priv, "KDF2(SHA-384)")?;

    let salt = rng.read(16)?;

    let a_key = a_ka.agree(32, &b_pub, &salt)?;
    let b_key = b_ka.agree(32, &a_pub, &salt)?;
    assert_eq!(a_key, b_key);

    let mut a_ka = botan::KeyAgreement::new(&a_priv, "Raw")?;
    let mut b_ka = botan::KeyAgreement::new(&b_priv, "Raw")?;

    let a_key = a_ka.agree(0, &b_pub, &[])?;
    let b_key = b_ka.agree(0, &a_pub, &[])?;

    assert_eq!(a_key, b_key);
    assert_eq!(a_key.len(), 384 / 8);
    Ok(())
}

#[test]
fn test_rfc3394_aes_key_wrap() -> Result<(), botan::Error> {
    let kek =
        botan::hex_decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")?;
    let key =
        botan::hex_decode("00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F")?;

    let wrapped = botan::rfc3394_key_wrap(&kek, &key)?;

    assert_eq!(
        botan::hex_encode(&wrapped)?,
        "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21"
    );

    let unwrapped = botan::rfc3394_key_unwrap(&kek, &wrapped)?;

    assert_eq!(unwrapped, key);
    Ok(())
}

#[test]
fn test_aes_key_wrap() -> Result<(), botan::Error> {
    let kek =
        botan::hex_decode("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F")?;
    let key =
        botan::hex_decode("00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F")?;

    let wrapped = match botan::nist_kw_enc("AES-256", false, &kek, &key) {
        Ok(s) => s,
        Err(e) => {
            assert_eq!(e.error_type(), botan::ErrorType::NotImplemented);
            return Ok(());
        }
    };

    assert_eq!(
        botan::hex_encode(&wrapped)?,
        "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21"
    );
    let unwrapped = botan::nist_kw_dec("AES-256", false, &kek, &wrapped)?;

    assert_eq!(unwrapped, key);

    Ok(())
}

#[test]
fn test_pkcs_hash_id() -> Result<(), botan::Error> {
    assert!(botan::pkcs_hash_id("SHA-192").is_err());

    let id = botan::pkcs_hash_id("SHA-384")?;

    assert_eq!(
        botan::hex_encode(&id)?,
        "3041300D060960864801650304020205000430"
    );
    Ok(())
}

#[test]
fn test_ct_compare() -> Result<(), botan::Error> {
    let a = vec![1, 2, 3];

    assert!(botan::const_time_compare(&a, &[1, 2, 3]));
    assert!(!botan::const_time_compare(&a, &[1, 2, 3, 4]));
    assert!(!botan::const_time_compare(&a, &[1, 2, 4]));
    assert!(botan::const_time_compare(&a, &a));
    assert!(botan::const_time_compare(&a, &[1, 2, 3]));
    Ok(())
}

#[test]
fn test_scrub_mem() -> Result<(), botan::Error> {
    let mut v = vec![1, 2, 3];
    botan::scrub_mem(&mut v);
    assert_eq!(v, vec![0, 0, 0]);

    let mut a = [1u32, 2u32, 3u32, 2049903u32];
    botan::scrub_mem(&mut a);
    assert_eq!(a, [0, 0, 0, 0]);
    Ok(())
}

#[test]
fn test_mp() -> Result<(), botan::Error> {
    let mut a = botan::MPI::new()?;
    let mut b = botan::MPI::new()?;

    assert_eq!(a.to_u32()?, 0);
    assert_eq!(b.to_u32()?, 0);

    a.set_i32(9)?;
    b.set_i32(81)?;

    assert_eq!(a.get_bit(0), Ok(true));
    assert_eq!(a.get_bit(1), Ok(false));

    assert_eq!(a.to_u32()?, 9);
    assert_eq!(b.to_u32()?, 81);

    let mut c = &a + &b;
    assert_eq!(c.to_u32()?, 90);

    let d = botan::MPI::from_str("0x5A")?;
    assert_eq!(c, d);

    c *= &botan::MPI::from_str("1030")?;

    assert_eq!(c.to_string()?, "92700");

    assert_eq!(format!("{c}"), "92700");
    assert_eq!(format!("{c:x}"), "016a1c");
    assert_eq!(format!("{c:X}"), "016A1C");
    assert_eq!(format!("{c:#x}"), "0x016a1c");
    assert_eq!(format!("{c:#X}"), "0x016A1C");
    assert_eq!(c.to_bin()?, vec![0x01, 0x6a, 0x1c]);

    let mut s = &c << 32;
    assert_eq!(s.to_hex()?, "016A1C00000000");

    s <<= 4;
    s += 5;
    assert_eq!(s.to_hex()?, "16A1C000000005");

    let mut s = s - 19;
    assert_eq!(s.to_hex()?, "16A1BFFFFFFFF2");

    s += 14;

    s >>= 8;
    assert_eq!(s.to_hex()?, "16A1C0000000");

    let mut t = &s >> 28;
    assert_eq!(t, c);

    t += &s;
    t <<= 4;
    assert_eq!(t.to_hex()?, "016A1C0016A1C0");

    let ten = botan::MPI::new_from_u32(10)?;
    let d = &t / &ten;
    assert_eq!(d.to_hex()?, "243600024360");

    t /= &ten;
    assert_eq!(d, t);
    t /= &ten;

    let r = &t % &ten;

    assert_eq!(r.to_string()?, "4");

    let t = -t * &ten;

    assert!(t.is_negative()?);

    assert_eq!(format!("{t}"), "-39814346982240");
    Ok(())
}

#[test]
fn test_fpe() -> Result<(), botan::Error> {
    let modulus = botan::MPI::from_str("1000000000")?;
    let input = botan::MPI::from_str("939210311")?;

    let key = vec![0; 32];
    let tweak = vec![0; 8];

    let fpe = botan::FPE::new_fe1(&modulus, &key, 8, false)?;

    let ctext = fpe.encrypt(&input, &tweak)?;

    assert_ne!(ctext, input);

    let ptext = fpe.decrypt(&ctext, &tweak)?;

    assert_eq!(ptext, input);
    Ok(())
}

#[test]
fn test_hotp() -> Result<(), botan::Error> {
    let hotp = botan::HOTP::new(&[0xFF], "SHA-1", 6)?;
    assert_eq!(hotp.generate(23)?, 330795);

    assert!(hotp.check(330795, 23)?);
    assert!(!hotp.check(330795, 22)?);
    assert!(!hotp.check(330796, 23)?);
    Ok(())
}

#[test]
fn test_totp() -> Result<(), botan::Error> {
    let totp = botan::TOTP::new(
        b"1234567890123456789012345678901234567890123456789012345678901234",
        "SHA-512",
        8,
        30,
    )?;

    assert_eq!(totp.generate(59)?, 90693936);
    assert_eq!(totp.generate(1111111109)?, 25091201);
    assert_eq!(totp.generate(1111111111)?, 99943326);

    assert!(totp.check(90693936, 59, 0)?);
    assert!(!totp.check(90693936, 60, 0)?);
    assert!(totp.check(90693936, 59 + 30, 1)?);
    assert!(!totp.check(90693936, 59 + 31, 1)?);
    Ok(())
}

#[test]
fn test_elgamal() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new()?;

    let p_bits = 1024;
    let q_bits = 256;

    let elg = botan::Privkey::create_elgamal(p_bits, q_bits, &mut rng)?;

    // extract the elements:
    let p = elg.get_field("p")?;
    let q = elg.get_field("q")?;
    let g = elg.get_field("g")?;
    let x = elg.get_field("x")?;
    let y = elg.get_field("y")?;

    // check the lengths:
    assert_eq!(p.bit_count()?, p_bits);
    assert_eq!(q.bit_count()?, q_bits);
    assert!(x.bit_count()? <= q_bits);
    assert!(y.bit_count()? <= p_bits);

    // create a public key:
    let elgp = botan::Pubkey::load_elgamal(&p, &g, &y)?;

    // encrypt a message:
    let padding = "PKCS1v15";
    let ptext = rng.read(16)?;
    let ctext = elgp.encrypt(&ptext, padding, &mut rng)?;

    // decrypt with the private key:
    let recovered = elg.decrypt(&ctext, padding)?;
    assert_eq!(recovered, ptext);

    Ok(())
}

#[test]
fn test_dsa() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new()?;

    let p_bits = 1024;
    let q_bits = 256;

    let dsa = botan::Privkey::create_dsa(p_bits, q_bits, &mut rng)?;

    // extract the elements:
    let p = dsa.get_field("p")?;
    let q = dsa.get_field("q")?;
    let g = dsa.get_field("g")?;
    let x = dsa.get_field("x")?;
    let y = dsa.get_field("y")?;

    // check the lengths:
    assert_eq!(p.bit_count()?, p_bits);
    assert_eq!(q.bit_count()?, q_bits);
    assert!(x.bit_count()? <= q_bits);
    assert!(y.bit_count()? <= p_bits);

    // create a public key:
    let dsap = botan::Pubkey::load_dsa(&p, &q, &g, &y)?;

    // sign a message:
    let padding = "EMSA1(SHA-256)";
    let message = rng.read(16)?;

    let signature = dsa.sign(&message, padding, &mut rng)?;

    // verify the signature:

    assert!(dsap.verify(&message, &signature, padding)?);

    Ok(())
}

#[test]
fn test_zfec() -> Result<(), botan::Error> {
    let k = 2;
    let n = 3;
    let input_bytes = b"abcdefghijklmnop";

    let output_shares = match botan::zfec_encode(k, n, input_bytes) {
        Ok(s) => s,
        Err(e) => {
            assert_eq!(e.error_type(), botan::ErrorType::NotImplemented);
            return Ok(());
        }
    };

    assert_eq!(output_shares.len(), n);
    assert_eq!(output_shares[0], b"abcdefgh");
    assert_eq!(output_shares[1], b"ijklmnop");
    assert_eq!(output_shares[2], b"qrstuvwX");

    let shares_for_decoding = [
        (2, output_shares[2].as_ref()),
        (0, output_shares[0].as_ref()),
    ];
    let share_size = output_shares[0].len();

    let recovered = botan::zfec_decode(k, n, &shares_for_decoding, share_size)?;

    assert_eq!(recovered, input_bytes);

    Ok(())
}

#[cfg(botan_ffi_20230403)]
#[test]
fn test_kyber() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new()?;
    let kyber_priv = botan::Privkey::create("Kyber", "Kyber-1024-r3", &mut rng)?;
    let kyber_pub = kyber_priv.pubkey()?;

    let salt = rng.read(12)?;
    let shared_key_len = 32;
    let kdf = "KDF2(SHA-256)";

    let kem_e = botan::KeyEncapsulation::new(&kyber_pub, kdf)?;
    let (shared_key, encap_key) = kem_e.create_shared_key(&mut rng, &salt, shared_key_len)?;

    assert_eq!(shared_key.len(), shared_key_len);
    assert_eq!(encap_key.len(), 1568);

    let kem_d = botan::KeyDecapsulation::new(&kyber_priv, kdf)?;
    let shared_key_d = kem_d.decrypt_shared_key(&encap_key, &salt, shared_key_len)?;

    assert_eq!(shared_key, shared_key_d);

    Ok(())
}

#[cfg(botan_ffi_20230403)]
#[test]
fn test_ml_kem() -> Result<(), botan::Error> {
    let mut rng = botan::RandomNumberGenerator::new()?;

    for kl in [512, 768, 1024] {
        let params = format!("ML-KEM-{kl}");
        let sk = botan::Privkey::create("ML-KEM", &params, &mut rng)?;
        let pk = sk.pubkey()?;

        let pk_bytes = pk.raw_bytes()?;
        let pk2 = botan::Pubkey::load_ml_kem(&pk_bytes)?;

        let salt = rng.read(12)?;
        let shared_key_len = 32;
        let kdf = "KDF2(SHA-256)";

        let kem_e = botan::KeyEncapsulation::new(&pk2, kdf)?;
        let (shared_key, encap_key) = kem_e.create_shared_key(&mut rng, &salt, shared_key_len)?;

        assert_eq!(shared_key.len(), shared_key_len);

        let kem_d = botan::KeyDecapsulation::new(&sk, kdf)?;
        let shared_key_d = kem_d.decrypt_shared_key(&encap_key, &salt, shared_key_len)?;

        assert_eq!(shared_key, shared_key_d);
    }

    Ok(())
}

#[cfg(botan_ffi_20250506)]
#[test]
fn test_asn1_oid() -> Result<(), botan::Error> {
    let oid = botan::OID::from_str("1.2.840.10045.3.1.7")?;

    assert_eq!(oid.as_string()?, "1.2.840.10045.3.1.7");
    assert_eq!(oid.as_name()?, "secp256r1");

    assert_eq!(oid, botan::OID::from_str("secp256r1")?);

    assert_ne!(oid, botan::OID::from_str("1.2.840.113549.1.1.1")?);

    assert!(oid > botan::OID::from_str("1.2.840.10045.3.1.6")?);
    assert!(oid < botan::OID::from_str("1.2.840.10045.3.1.8")?);

    assert!(oid > botan::OID::from_str("1.2.840.10045.3.1")?);
    assert!(oid < botan::OID::from_str("1.2.840.10045.3.2")?);

    assert!(oid > botan::OID::from_str("1.2.840.10045.3")?);
    assert!(oid < botan::OID::from_str("1.2.840.10045.4")?);

    assert!(oid > botan::OID::from_str("1.2.840.10045")?);
    assert!(oid < botan::OID::from_str("1.2.840.10046")?);

    assert!(oid > botan::OID::from_str("1.2.840")?);
    assert!(oid < botan::OID::from_str("1.2.841")?);

    assert!(oid > botan::OID::from_str("1.2")?);
    assert!(oid < botan::OID::from_str("1.3")?);
    Ok(())
}

#[cfg(botan_ffi_20250506)]
#[test]
fn test_ec_group() -> Result<(), botan::Error> {
    let supports_app_groups = botan::EcGroup::supports_application_specific_groups()?;

    let supports_secp256r1 = botan::EcGroup::supports_named_group("secp256r1")?;

    if supports_app_groups {
        assert!(supports_secp256r1);
    }

    assert_eq!(
        botan::EcGroup::supports_named_group("nosuchgroup256r1")?,
        false
    );

    if supports_secp256r1 {
        let secp256r1 = botan::EcGroup::from_name("secp256r1")?;

        let from_pem = botan::EcGroup::from_pem(&secp256r1.pem()?)?;
        let from_der = botan::EcGroup::from_der(&secp256r1.der()?)?;
        let from_oid = botan::EcGroup::from_oid(&secp256r1.oid()?)?;

        assert_eq!(secp256r1, from_pem);
        assert_eq!(secp256r1, from_der);
        assert_eq!(secp256r1, from_oid);

        assert_eq!(
            format!("{:x}", secp256r1.p()?),
            "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff"
        );

        assert_eq!(secp256r1.oid()?.as_string()?, "1.2.840.10045.3.1.7");
    }

    if supports_app_groups {
        let secp256r1 = botan::EcGroup::from_name("secp256r1")?;

        let curve_oid = botan::OID::from_str("1.3.6.1.4.1.25258.4.666")?;
        let mycustomp256 = botan::EcGroup::from_params(
            &curve_oid,
            &secp256r1.p()?,
            &secp256r1.a()?,
            &secp256r1.b()?,
            &secp256r1.g_x()?,
            &secp256r1.g_y()?,
            &secp256r1.order()?,
        )?;

        let from_oid = botan::EcGroup::from_oid(&curve_oid)?;

        assert_eq!(mycustomp256, from_oid);

        // Equality is for just the params and ignores the OIDs
        assert_eq!(mycustomp256, secp256r1);
    }

    Ok(())
}
