// MIT License

// Copyright (c) 2018-2024 The orion Developers

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

extern crate criterion;
extern crate orion;

use criterion::*;

use orion::hazardous::{
    aead::{chacha20poly1305, xchacha20poly1305},
    hash::*,
    kdf::{argon2i, hkdf, pbkdf2},
    mac::{hmac, poly1305},
    stream::*,
};

static INPUT_SIZES: [usize; 3] = [64 * 1024, 128 * 1024, 256 * 1024];

mod mac {
    use super::*;

    pub fn bench_poly1305(c: &mut Criterion) {
        let mut group = c.benchmark_group("Poly1305");
        let key = poly1305::OneTimeKey::generate();

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute mac", *size),
                &input,
                |b, input_message| {
                    b.iter(|| poly1305::Poly1305::poly1305(&key, &input_message).unwrap())
                },
            );
        }
    }

    pub fn bench_hmac_sha256(c: &mut Criterion) {
        let mut group = c.benchmark_group("HMAC-SHA256");
        // NOTE: Setting the key like this will pad it for HMAC.
        // Padding is therefor not included in benchmarks.
        let key = hmac::sha256::SecretKey::generate();

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute mac", *size),
                &input,
                |b, input_message| {
                    b.iter(|| hmac::sha256::HmacSha256::hmac(&key, &input_message).unwrap())
                },
            );
        }
    }

    pub fn bench_hmac_sha512(c: &mut Criterion) {
        let mut group = c.benchmark_group("HMAC-SHA512");
        // NOTE: Setting the key like this will pad it for HMAC.
        // Padding is therefor not included in benchmarks.
        let key = hmac::sha512::SecretKey::generate();

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute mac", *size),
                &input,
                |b, input_message| {
                    b.iter(|| hmac::sha512::HmacSha512::hmac(&key, &input_message).unwrap())
                },
            );
        }
    }

    criterion_group! {
        name = mac_benches;
        config = Criterion::default();
        targets =
        bench_poly1305,
        bench_hmac_sha256,
        bench_hmac_sha512,
    }
}

mod aead {
    use super::*;

    pub fn bench_chacha20poly1305(c: &mut Criterion) {
        let mut group = c.benchmark_group("ChaCha20-Poly1305");
        let key = chacha20poly1305::SecretKey::generate();
        let nonce = chacha20poly1305::Nonce::from_slice(&[0u8; 12]).unwrap();

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];
            let mut out = vec![0u8; input.len() + 16];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("encrypt", *size),
                &input,
                |b, input_message| {
                    b.iter(|| {
                        chacha20poly1305::seal(&key, &nonce, &input_message, None, &mut out)
                            .unwrap()
                    })
                },
            );
        }
    }

    pub fn bench_xchacha20poly1305(c: &mut Criterion) {
        let mut group = c.benchmark_group("XChaCha20-Poly1305");
        let key = xchacha20poly1305::SecretKey::generate();
        let nonce = xchacha20poly1305::Nonce::generate();

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];
            let mut out = vec![0u8; input.len() + 16];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("encrypt", *size),
                &input,
                |b, input_message| {
                    b.iter(|| {
                        xchacha20poly1305::seal(&key, &nonce, &input_message, None, &mut out)
                            .unwrap()
                    })
                },
            );
        }
    }

    criterion_group! {
        name = aead_benches;
        config = Criterion::default();
        targets =
        bench_chacha20poly1305,
        bench_xchacha20poly1305,
    }
}

mod hash {
    use super::*;

    pub fn bench_sha256(c: &mut Criterion) {
        let mut group = c.benchmark_group("SHA256");

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute hash", *size),
                &input,
                |b, input_message| b.iter(|| sha2::sha256::Sha256::digest(&input_message).unwrap()),
            );
        }
    }

    pub fn bench_sha384(c: &mut Criterion) {
        let mut group = c.benchmark_group("SHA384");

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute hash", *size),
                &input,
                |b, input_message| b.iter(|| sha2::sha384::Sha384::digest(&input_message).unwrap()),
            );
        }
    }

    pub fn bench_sha512(c: &mut Criterion) {
        let mut group = c.benchmark_group("SHA512");

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute hash", *size),
                &input,
                |b, input_message| b.iter(|| sha2::sha512::Sha512::digest(&input_message).unwrap()),
            );
        }
    }

    pub fn bench_blake2b_512(c: &mut Criterion) {
        let mut group = c.benchmark_group("BLAKE2b-512");

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("compute hash", *size),
                &input,
                |b, input_message| {
                    b.iter(|| {
                        blake2::blake2b::Hasher::Blake2b512
                            .digest(&input_message)
                            .unwrap()
                    })
                },
            );
        }
    }

    criterion_group! {
        name = hash_benches;
        config = Criterion::default();
        targets =
        bench_sha256,
        bench_sha384,
        bench_sha512,
        bench_blake2b_512,
    }
}

mod stream {
    use super::*;

    pub fn bench_chacha20(c: &mut Criterion) {
        let mut group = c.benchmark_group("ChaCha20");
        let key = chacha20poly1305::SecretKey::generate();
        let nonce = chacha20poly1305::Nonce::from([0u8; 12]);

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];
            let mut out = vec![0u8; input.len()];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("xor-stream", *size),
                &input,
                |b, input_message| {
                    b.iter(|| chacha20::encrypt(&key, &nonce, 0, &input_message, &mut out).unwrap())
                },
            );
        }
    }

    pub fn bench_xchacha20(c: &mut Criterion) {
        let mut group = c.benchmark_group("XChaCha20");
        let key = xchacha20::SecretKey::generate();
        let nonce = xchacha20::Nonce::generate();

        for size in INPUT_SIZES.iter() {
            let input = vec![0u8; *size];
            let mut out = vec![0u8; input.len()];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("xor-stream", *size),
                &input,
                |b, input_message| {
                    b.iter(|| {
                        xchacha20::encrypt(&key, &nonce, 0, &input_message, &mut out).unwrap()
                    })
                },
            );
        }
    }

    criterion_group! {
        name = stream_benches;
        config = Criterion::default();
        targets =
        bench_chacha20,
        bench_xchacha20,
    }
}

mod kdf {
    use super::*;

    static OKM_SIZES: [usize; 1] = [512];
    static PBKDF2_ITERATIONS: [usize; 1] = [10000];

    pub fn bench_hkdf_sha256(c: &mut Criterion) {
        let mut group = c.benchmark_group("HKDF-HMAC-SHA256");

        let ikm = vec![0u8; 64];
        let salt = ikm.clone();

        for size in OKM_SIZES.iter() {
            let mut okm_out = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("derive bytes", *size),
                &ikm,
                |b, input_ikm| {
                    b.iter(|| {
                        hkdf::sha256::derive_key(&salt, input_ikm, None, &mut okm_out).unwrap()
                    })
                },
            );
        }
    }

    pub fn bench_hkdf_sha512(c: &mut Criterion) {
        let mut group = c.benchmark_group("HKDF-HMAC-SHA512");

        let ikm = vec![0u8; 64];
        let salt = ikm.clone();

        for size in OKM_SIZES.iter() {
            let mut okm_out = vec![0u8; *size];

            group.throughput(Throughput::Bytes(*size as u64));
            group.bench_with_input(
                BenchmarkId::new("derive bytes", *size),
                &ikm,
                |b, input_ikm| {
                    b.iter(|| {
                        hkdf::sha512::derive_key(&salt, input_ikm, None, &mut okm_out).unwrap()
                    })
                },
            );
        }
    }

    pub fn bench_pbkdf2_sha256(c: &mut Criterion) {
        let mut group = c.benchmark_group("PBKDF2-HMAC-SHA256");
        // 10 is the lowest acceptable sample size.
        group.sample_size(10);
        group.measurement_time(core::time::Duration::new(30, 0));

        let ikm = vec![0u8; 64];
        let salt = ikm.clone();

        for iterations in PBKDF2_ITERATIONS.iter() {
            let mut dk_out = vec![0u8; 64];

            // NOTE: The password newtype creation is included
            // as this pads the salt for HMAC internally.
            group.bench_with_input(
                BenchmarkId::new("derive 64 bytes", *iterations),
                &iterations,
                |b, iter_count| {
                    b.iter(|| {
                        pbkdf2::sha256::derive_key(
                            &pbkdf2::sha256::Password::from_slice(&salt).unwrap(),
                            &ikm,
                            **iter_count,
                            &mut dk_out,
                        )
                        .unwrap()
                    })
                },
            );
        }
    }

    pub fn bench_pbkdf2_sha512(c: &mut Criterion) {
        let mut group = c.benchmark_group("PBKDF2-HMAC-SHA512");
        // 10 is the lowest acceptable sample size.
        group.sample_size(10);
        group.measurement_time(core::time::Duration::new(30, 0));

        let ikm = vec![0u8; 64];
        let salt = ikm.clone();

        for iterations in PBKDF2_ITERATIONS.iter() {
            let mut dk_out = vec![0u8; 64];

            // NOTE: The password newtype creation is included
            // as this pads the salt for HMAC internally.
            group.bench_with_input(
                BenchmarkId::new("derive 64 bytes", *iterations),
                &iterations,
                |b, iter_count| {
                    b.iter(|| {
                        pbkdf2::sha512::derive_key(
                            &pbkdf2::sha512::Password::from_slice(&salt).unwrap(),
                            &ikm,
                            **iter_count,
                            &mut dk_out,
                        )
                        .unwrap()
                    })
                },
            );
        }
    }

    pub fn bench_argon2i(c: &mut Criterion) {
        let mut group = c.benchmark_group("Argon2i");

        let iter = 3;
        let mem = 128;
        let password = [0u8; 16];
        let salt = [0u8; 16];
        let mut dk_out = [0u8; 32];

        group.throughput(Throughput::Bytes(mem as u64));

        group.bench_with_input(
            BenchmarkId::new(
                "derive bytes",
                format!("iter: {}, mem (KiB): {}", iter, mem),
            ),
            &salt,
            |b, _| {
                b.iter(|| {
                    argon2i::derive_key(&password, &salt, iter, mem, None, None, &mut dk_out)
                        .unwrap()
                })
            },
        );
    }

    criterion_group! {
        name = kdf_benches;
        config = Criterion::default();
        targets =
        bench_argon2i,
        bench_hkdf_sha256,
        bench_hkdf_sha512,
        bench_pbkdf2_sha256,
        bench_pbkdf2_sha512,
    }
}

mod ecc {
    use super::*;
    use core::convert::TryFrom;
    use orion::hazardous::ecc::x25519;

    pub fn bench_x25519(c: &mut Criterion) {
        let mut group = c.benchmark_group("X25519");

        let alice_sk = x25519::PrivateKey::generate();
        let alice_pk = x25519::PublicKey::try_from(&alice_sk).unwrap();

        group.sample_size(100);
        group.bench_function("key_agreement", move |b| {
            b.iter_with_setup(
                || x25519::PrivateKey::generate(),
                |bob_sk| x25519::key_agreement(&bob_sk, &alice_pk).unwrap(),
            )
        });
    }

    criterion_group! {
        name = ecc_benches;
        config = Criterion::default();
        targets =
        bench_x25519
    }
}

criterion_main!(
    mac::mac_benches,
    aead::aead_benches,
    hash::hash_benches,
    stream::stream_benches,
    kdf::kdf_benches,
    ecc::ecc_benches,
);
