// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![allow(clippy::unwrap_used)]

use crypto_provider::aes::BLOCK_SIZE;
use crypto_provider::{CryptoProvider, CryptoRng};
use crypto_provider_default::CryptoProviderImpl;
use ldt::*;
use ldt_tbc::TweakableBlockCipher;
use rand::rngs::StdRng;
use rand::{self, distributions, Rng as _, SeedableRng as _};
use rand_ext::{random_bytes, random_vec};
use xts_aes::{XtsAes128, XtsAes256};

#[test]
fn roundtrip_normal_padder() {
    let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
    let mut rc_rng = rand::rngs::StdRng::from_entropy();
    let plaintext_len_range = distributions::Uniform::new_inclusive(BLOCK_SIZE, BLOCK_SIZE * 2 - 1);

    for _ in 0..100_000 {
        if rc_rng.gen() {
            let ldt_key = LdtKey::from_random::<CryptoProviderImpl>(&mut rng);
            do_roundtrip::<16, _, _, _, CryptoProviderImpl>(
                LdtEncryptCipher::<16, XtsAes128<CryptoProviderImpl>, Swap>::new(&ldt_key),
                LdtDecryptCipher::<16, XtsAes128<CryptoProviderImpl>, Swap>::new(&ldt_key),
                &DefaultPadder,
                &mut rng,
                &plaintext_len_range,
            )
        } else {
            let ldt_key = LdtKey::from_random::<CryptoProviderImpl>(&mut rng);
            do_roundtrip::<16, _, _, _, CryptoProviderImpl>(
                LdtEncryptCipher::<16, XtsAes256<CryptoProviderImpl>, Swap>::new(&ldt_key),
                LdtDecryptCipher::<16, XtsAes256<CryptoProviderImpl>, Swap>::new(&ldt_key),
                &DefaultPadder,
                &mut rng,
                &plaintext_len_range,
            )
        };
    }
}

#[test]
fn roundtrip_xor_padder() {
    let mut rng = <CryptoProviderImpl as CryptoProvider>::CryptoRng::new();
    let mut rc_rng = rand::rngs::StdRng::from_entropy();
    // 2 bytes smaller because we're using a 2 byte salt
    let plaintext_len_range =
        distributions::Uniform::new_inclusive(BLOCK_SIZE, BLOCK_SIZE * 2 - 1 - 2);

    for _ in 0..100_000 {
        let padder: XorPadder<BLOCK_SIZE> =
            random_bytes::<BLOCK_SIZE, CryptoProviderImpl>(&mut rng).into();

        if rc_rng.gen() {
            let ldt_key = LdtKey::from_random::<CryptoProviderImpl>(&mut rng);
            do_roundtrip::<16, _, _, _, CryptoProviderImpl>(
                LdtEncryptCipher::<16, XtsAes128<CryptoProviderImpl>, Swap>::new(&ldt_key),
                LdtDecryptCipher::<16, XtsAes128<CryptoProviderImpl>, Swap>::new(&ldt_key),
                &padder,
                &mut rng,
                &plaintext_len_range,
            )
        } else {
            let ldt_key = LdtKey::from_random::<CryptoProviderImpl>(&mut rng);
            do_roundtrip::<16, _, _, _, CryptoProviderImpl>(
                LdtEncryptCipher::<16, XtsAes256<CryptoProviderImpl>, Swap>::new(&ldt_key),
                LdtDecryptCipher::<16, XtsAes256<CryptoProviderImpl>, Swap>::new(&ldt_key),
                &padder,
                &mut rng,
                &plaintext_len_range,
            )
        };
    }
}

fn do_roundtrip<
    const B: usize,
    T: TweakableBlockCipher<B>,
    P: Padder<B, T>,
    M: Mix,
    C: CryptoProvider,
>(
    ldt_enc: LdtEncryptCipher<B, T, M>,
    ldt_dec: LdtDecryptCipher<B, T, M>,
    padder: &P,
    rng: &mut C::CryptoRng,
    plaintext_len_range: &distributions::Uniform<usize>,
) {
    let mut rng_rc = StdRng::from_entropy();
    let len = rng_rc.sample(plaintext_len_range);
    let plaintext = random_vec::<C>(rng, len);

    let mut ciphertext = plaintext.clone();
    ldt_enc.encrypt(&mut ciphertext, padder).unwrap();

    assert_eq!(plaintext.len(), ciphertext.len());
    assert_ne!(plaintext, ciphertext);

    ldt_dec.decrypt(&mut ciphertext, padder).unwrap();
    assert_eq!(plaintext, ciphertext);
}
