/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.symmetric.service;

import static junitparams.JUnitParamsRunner.$;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.Security;

import javax.crypto.SecretKey;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIDerivedKey;
import com.scytl.cryptolib.primitives.primes.bean.TestCryptoDerivedKey;
import com.scytl.cryptolib.primitives.primes.utils.PrimitivesTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.test.tools.bean.TestSecretKey;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of the symmetric service API input validation.
 */
@RunWith(JUnitParamsRunner.class)
public class SymmetricServiceValidationTest {

    static final private int MAXIMUM_DATA_ARRAY_LENGTH = 10;

    private static SymmetricService _symmetricServiceForDefaultPolicy;

    private static SecretKey _secretKeyForEncryption;

    private static SecretKey _secretKeyForMac;

    private static byte[] _data;

    private static byte[][] _dataArray;

    private static byte[] _mac;

    private static byte[] _emptyByteArray;

    private static byte[][] _emptyByteArrayArray;

    private static TestSecretKey _nullContentSecretKey;

    private static TestSecretKey _emptyContentSecretKey;

    private static com.scytl.cryptolib.primitives.primes.bean.TestCryptoDerivedKey _nullContentDerivedKey;

    private static TestCryptoDerivedKey _emptyContentDerivedKey;

    private static byte[][] _dataArrayConsistingOfNullElement;

    private static byte[][] _dataArrayConsistingOfEmptyElement;

    private static byte[][] _dataArrayContainingNullElement;

    private static byte[][] _dataArrayContainingEmptyElement;

    static {
        try {
            setUp();
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException("Setup failed for class "
                + SymmetricServiceValidationTest.class.getSimpleName());
        }
    }

    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _symmetricServiceForDefaultPolicy = new SymmetricService();

        _secretKeyForEncryption =
            _symmetricServiceForDefaultPolicy.getSecretKeyForEncryption();

        _secretKeyForMac =
            _symmetricServiceForDefaultPolicy.getSecretKeyForHmac();

        int dataByteLength =
            CommonTestDataGenerator.getInt(1,
                SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH);

        int dataArrayLength =
            CommonTestDataGenerator.getInt(2, MAXIMUM_DATA_ARRAY_LENGTH);

        _data = PrimitivesTestDataGenerator.getByteArray(dataByteLength);

        _dataArray =
            PrimitivesTestDataGenerator.getByteArrayArray(dataByteLength,
                CommonTestDataGenerator.getInt(2,
                    MAXIMUM_DATA_ARRAY_LENGTH));

        _mac =
            _symmetricServiceForDefaultPolicy.getMac(_secretKeyForMac,
                _data);

        _emptyByteArray = new byte[0];
        _emptyByteArrayArray = new byte[0][0];

        _nullContentSecretKey = new TestSecretKey(null);
        _emptyContentSecretKey = new TestSecretKey(_emptyByteArray);

        _nullContentDerivedKey = new TestCryptoDerivedKey(null);
        _emptyContentDerivedKey =
            new TestCryptoDerivedKey(_emptyByteArray);

        _dataArrayConsistingOfNullElement = new byte[1][dataByteLength];
        _dataArrayConsistingOfNullElement[0] = null;

        _dataArrayConsistingOfEmptyElement = new byte[1][dataByteLength];
        _dataArrayConsistingOfEmptyElement[0] = _emptyByteArray;

        _dataArrayContainingNullElement =
            PrimitivesTestDataGenerator.getByteArrayArray(dataByteLength,
                dataArrayLength);
        _dataArrayContainingNullElement[0] = null;

        _dataArrayContainingEmptyElement =
            PrimitivesTestDataGenerator.getByteArrayArray(dataByteLength,
                dataArrayLength);
        _dataArrayContainingEmptyElement[0] = _emptyByteArray;
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    @Parameters(method = "createSymmetricService")
    public void testSymmetricServiceCreationValidation(String path,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new SymmetricService(path);
    }

    public static Object[] createSymmetricService()
            throws GeneralCryptoLibException {

        return $(
            $(null, "Properties file path is null."),
            $("", "Properties file path is blank."),
            $(CommonTestDataGenerator
                .getWhiteSpaceString(CommonTestDataGenerator.getInt(1,
                    SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH)),
                "Properties file path is blank."));
    }

    @Test
    @Parameters(method = "symmetricallyEncrypt")
    public void testSymmetricEncryptionValidation(SecretKey key,
            byte[] data, String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy.encrypt(key, data);
    }

    public static Object[] symmetricallyEncrypt() {

        return $(
            $(null, _data, "Secret key is null."),
            $(_nullContentSecretKey, _data, "Secret key content is null."),
            $(_emptyContentSecretKey, _data,
                "Secret key content is empty."),
            $(_secretKeyForEncryption, null, "Data is null."),
            $(_secretKeyForEncryption, _emptyByteArray, "Data is empty."));
    }

    @Test
    @Parameters(method = "symmetricallyDecrypt")
    public void testSymmetricDecryptionValidation(SecretKey key,
            byte[] data, String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy.decrypt(key, data);
    }

    public static Object[] symmetricallyDecrypt()
            throws GeneralCryptoLibException {

        byte[] encryptedData =
            _symmetricServiceForDefaultPolicy.encrypt(
                _secretKeyForEncryption, _data);

        return $(
            $(null, encryptedData, "Secret key is null."),
            $(_nullContentSecretKey, encryptedData,
                "Secret key content is null."),
            $(_emptyContentSecretKey, encryptedData,
                "Secret key content is empty."),
            $(_secretKeyForEncryption, null, "Encrypted data is null."),
            $(_secretKeyForEncryption, _emptyByteArray,
                "Encrypted data is empty."));
    }

    @Test
    @Parameters(method = "generateMac")
    public void testMacGenerationValidation(SecretKey key, byte[][] data,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy.getMac(key, data);
    }

    public static Object[] generateMac() {

        return $(
            $(null, _dataArray, "Secret key is null."),
            $(_nullContentSecretKey, _dataArray,
                "Secret key content is null."),
            $(_emptyContentSecretKey, _dataArray,
                "Secret key content is empty."),
            $(_secretKeyForMac, null, "Data element array is null."),
            $(_secretKeyForMac, _emptyByteArrayArray,
                "Data element array is empty."),
            $(_secretKeyForMac, _dataArrayConsistingOfNullElement,
                "Data is null."),
            $(_secretKeyForMac, _dataArrayConsistingOfEmptyElement,
                "Data is empty."),
            $(_secretKeyForMac, _dataArrayContainingNullElement,
                "A data element is null."),
            $(_secretKeyForMac, _dataArrayContainingEmptyElement,
                "A data element is empty."));
    }

    @Test
    @Parameters(method = "verifyMac")
    public void testMacVerificationFromDataInputStreamValidation(
            SecretKey key, byte[] mac, byte[][] data, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy.verifyMac(key, mac, data);
    }

    public static Object[] verifyMac() {

        return $(
            $(null, _mac, _dataArray, "Secret key is null."),
            $(_nullContentSecretKey, _mac, _dataArray,
                "Secret key content is null."),
            $(_emptyContentSecretKey, _mac, _dataArray,
                "Secret key content is empty."),
            $(_secretKeyForMac, null, _dataArray, "MAC is null."),
            $(_secretKeyForMac, _emptyByteArray, _dataArray,
                "MAC is empty."),
            $(_secretKeyForMac, _mac, null, "Data element array is null."),
            $(_secretKeyForMac, _mac, _emptyByteArrayArray,
                "Data element array is empty."),
            $(_secretKeyForMac, _mac, _dataArrayConsistingOfNullElement,
                "Data is null."),
            $(_secretKeyForMac, _mac, _dataArrayConsistingOfEmptyElement,
                "Data is empty."),
            $(_secretKeyForMac, _mac, _dataArrayContainingNullElement,
                "A data element is null."),
            $(_secretKeyForMac, _mac, _dataArrayContainingEmptyElement,
                "A data element is empty."));
    }

    @Test
    @Parameters(method = "generateMacFromDataInputStream")
    public void testMacGenerationFromDataInputStreamValidation(
            SecretKey key, InputStream inStream, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy.getMac(key, inStream);
    }

    public static Object[] generateMacFromDataInputStream() {

        InputStream dataInputStream = new ByteArrayInputStream(_data);

        return $(
            $(null, dataInputStream, "Secret key is null."),
            $(_nullContentSecretKey, dataInputStream,
                "Secret key content is null."),
            $(_emptyContentSecretKey, dataInputStream,
                "Secret key content is empty."),
            $(_secretKeyForMac, null, "Data input stream is null."));
    }

    @Test
    @Parameters(method = "verifyMacFromDataInputStream")
    public void testMacVerificationFromDataInputStreamValidation(
            SecretKey key, byte[] mac, InputStream inStream,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy.verifyMac(key, mac, inStream);
    }

    public static Object[] verifyMacFromDataInputStream() {

        InputStream dataInputStream = new ByteArrayInputStream(_data);

        return $(
            $(null, _mac, dataInputStream, "Secret key is null."),
            $(_nullContentSecretKey, _mac, dataInputStream,
                "Secret key content is null."),
            $(_emptyContentSecretKey, _mac, dataInputStream,
                "Secret key content is empty."),
            $(_secretKeyForMac, null, dataInputStream, "MAC is null."),
            $(_secretKeyForMac, _emptyByteArray, dataInputStream,
                "MAC is empty."),
            $(_secretKeyForMac, _mac, null, "Data input stream is null."));
    }

    @Test
    @Parameters(method = "getSecretKeyForEncryptionFromDerivedKey")
    public void testSecretKeyForEncryptionFromDerivedKeyValidation(
            CryptoAPIDerivedKey key, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy
            .getSecretKeyForEncryptionFromDerivedKey(key);
    }

    public static Object[] getSecretKeyForEncryptionFromDerivedKey() {

        return $($(null, "Derived key is null."),
            $(_nullContentDerivedKey, "Derived key content is null."),
            $(_emptyContentDerivedKey, "Derived key content is empty."));
    }

    @Test
    @Parameters(method = "getSecretKeyForMacFromDerivedKey")
    public void testSecretKeyForMacFromDerivedKeyValidation(
            CryptoAPIDerivedKey key, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy
            .getSecretKeyForMacFromDerivedKey(key);
    }

    public static Object[] getSecretKeyForMacFromDerivedKey() {

        return $($(null, "Derived key is null."),
            $(_nullContentDerivedKey, "Derived key content is null."),
            $(_emptyContentDerivedKey, "Derived key content is empty."));
    }

    @Test
    @Parameters(method = "getSecretKeyForEncryptionFromBytes")
    public void testSecretKeyForEncryptionFromBytesValidation(
            byte[] keyBytes, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _symmetricServiceForDefaultPolicy
            .getSecretKeyForEncryptionFromBytes(keyBytes);
    }

    public static Object[] getSecretKeyForEncryptionFromBytes() {

        return $($(null, "Secret key is null."),
            $(_emptyByteArray, "Secret key is empty."));
    }
}
