/*
 * 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.primitives.service;

import static java.util.Arrays.fill;
import static junitparams.JUnitParamsRunner.$;

import java.io.InputStream;
import java.math.BigInteger;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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.CryptoAPIKDFDeriver;
import com.scytl.cryptolib.derivation.cryptoapi.CryptoAPIPBKDFDeriver;
import com.scytl.cryptolib.primitives.derivation.constants.DerivationConstants;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomBytes;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomInteger;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomString;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

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

    private static final int RANDOM_LIST_LENGTH = 50;

    private static final int BLACK_LIST_ELEMENT_LENGTH = 10;

    private static final int BLACK_LIST_LENGTH = 10;

    private static PrimitivesService _primitivesServiceForDefaultPolicy;

    private static String _whiteSpaceString;

    private static CryptoAPIRandomBytes _cryptoRandomBytes;

    private static CryptoAPIRandomInteger _cryptoRandomInteger;

    private static CryptoAPIRandomString _crypto32CharRandomString;

    private static CryptoAPIRandomString _crypto64CharRandomString;

    private static CryptoAPIKDFDeriver _cryptoKdfDeriver;

    private static CryptoAPIPBKDFDeriver _cryptoPbkdfDeriver;

    private static int _kdfLengthInBytes;

    private static byte[] _kdfSeed;

    private static String _pbkdfPassword;

    private static byte[] _pbkdfSalt;

    private static byte[] _emptyByteArray;

    private static char[] _emptyCharArray;

    private static int _aboveMaxByteArrayLength;

    private static int _aboveMaxBigIntegerDigitLength;

    private static int _aboveMaxStringLength;

    private static String _belowMinLengthPbKdfPassword;

    private static String _aboveMaxLengthPbKdfPassword;

    private static byte[] _belowMinLengthPbkdfSalt;

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

    public static void setUp() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());

        _primitivesServiceForDefaultPolicy = new PrimitivesService();

        char[] whiteSpaceChars = new char[CommonTestDataGenerator.getInt(1,
            SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH)];
        fill(whiteSpaceChars, ' ');
        _whiteSpaceString = new String(whiteSpaceChars);

        _cryptoRandomBytes =
            _primitivesServiceForDefaultPolicy.getCryptoRandomBytes();
        _cryptoRandomInteger =
            _primitivesServiceForDefaultPolicy.getCryptoRandomInteger();
        _crypto32CharRandomString =
            _primitivesServiceForDefaultPolicy
                .get32CharAlphabetCryptoRandomString();
        _crypto64CharRandomString =
            _primitivesServiceForDefaultPolicy
                .get64CharAlphabetCryptoRandomString();
        _cryptoKdfDeriver =
            _primitivesServiceForDefaultPolicy.getKDFDeriver();
        _cryptoPbkdfDeriver =
            _primitivesServiceForDefaultPolicy.getPBKDFDeriver();

        _kdfLengthInBytes =
            CommonTestDataGenerator.getInt(1,
                SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH);
        _kdfSeed =
            _cryptoRandomBytes
                .nextRandom(CommonTestDataGenerator
                    .getInt(
                        1,
                        SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH));

        _pbkdfPassword =
            _crypto64CharRandomString
                .nextRandom(CommonTestDataGenerator
                    .getInt(
                        DerivationConstants.MINIMUM_PBKDF_PASSWORD_LENGTH,
                        SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH));
        _pbkdfSalt =
            _cryptoRandomBytes
                .nextRandom(CommonTestDataGenerator
                    .getInt(
                        DerivationConstants.MINIMUM_PBKDF_SALT_LENGTH_IN_BYTES,
                        SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH));

        _emptyByteArray = new byte[0];
        _emptyCharArray = new char[0];

        _aboveMaxByteArrayLength =
            SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH + 1;
        _aboveMaxBigIntegerDigitLength =
            SecureRandomConstants.MAXIMUM_GENERATED_BIG_INTEGER_DIGIT_LENGTH + 1;
        _aboveMaxStringLength =
            SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH + 1;

        _belowMinLengthPbKdfPassword =
            _crypto64CharRandomString
                .nextRandom(CommonTestDataGenerator
                    .getInt(
                        1,
                        (DerivationConstants.MINIMUM_PBKDF_PASSWORD_LENGTH - 1)));
        _aboveMaxLengthPbKdfPassword =
            CommonTestDataGenerator.getAlphanumeric(
                DerivationConstants.MAXIMUM_PBKDF_PASSWORD_LENGTH + 1);
        _belowMinLengthPbkdfSalt =
            _cryptoRandomBytes
                .nextRandom(CommonTestDataGenerator
                    .getInt(
                        1,
                        (DerivationConstants.MINIMUM_PBKDF_SALT_LENGTH_IN_BYTES - 1)));
    }

    @AfterClass
    public static void removeBouncyCastleProvider() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

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

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

    public static Object[] createPrimitivesService() {

        return $($(null, "Properties file path is null."),
            $("", "Properties file path is blank."),
            $(_whiteSpaceString, "Properties file path is blank."));
    }

    @Test
    @Parameters(method = "generateHash")
    public void testHashGenerationValidation(byte[] data, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _primitivesServiceForDefaultPolicy.getHash(data);
    }

    public static Object[] generateHash() {

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

    @Test
    @Parameters(method = "generateHashForDataInputStream")
    public void testHashGenerationForDataInputStreamValidation(
            InputStream inStream, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _primitivesServiceForDefaultPolicy.getHash(inStream);
    }

    public static Object[] generateHashForDataInputStream() {

        return $($(null, "Data input stream is null."));
    }

    @Test
    @Parameters(method = "performShuffle")
    public void testShuffleValidation(List<?> list, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _primitivesServiceForDefaultPolicy.shuffle(list);
    }

    public static Object[] performShuffle()
            throws GeneralCryptoLibException {

        List<?> listWithNullElement =
            generateListWithNullElement(
                SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH,
                SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH);

        return $(
            $(null, "List is null."),
            $(new ArrayList<Object>(), "List is empty."),
            $(listWithNullElement,
                "List contains one or more null elements."));
    }

    @Test
    @Parameters(method = "generateRandomBytes")
    public void testRandomBytesGenerationValidation(int byteLength,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomBytes.nextRandom(byteLength);
    }

    public static Object[] generateRandomBytes() {

        return $(
            $(0,
                "Length in bytes must be greater than or equal to : 1; Found 0"),
            $(_aboveMaxByteArrayLength,
                "Length in bytes must be less than or equal to maximum allowed value for secure random byte arrays: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH
                    + "; Found " + _aboveMaxByteArrayLength));
    }

    @Test
    @Parameters(method = "generateRandomBytesWithBlackList")
    public void testRandomBytesGenerationWithBlackListValidation(
            int byteLength, Set<Byte[]> blackList, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomBytes.nextRandom(byteLength, blackList);
    }

    public static Object[] generateRandomBytesWithBlackList() {

        Set<Byte[]> emptyBlacklist = new HashSet<>();
        Set<Byte[]> byteArrayBlackList =
            generateByteArrayBlackList(BLACK_LIST_ELEMENT_LENGTH,
                BLACK_LIST_LENGTH);

        return $(
            $(0, emptyBlacklist,
                "Length in bytes must be greater than or equal to : 1; Found 0"),
            $(_aboveMaxByteArrayLength,
                emptyBlacklist,
                "Length in bytes must be less than or equal to maximum allowed value for secure random byte arrays: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH
                    + "; Found " + _aboveMaxByteArrayLength),
            $(1, null,
                "The black list should be strictly less than total amount of elements"),
            $(1, byteArrayBlackList,
                "The black list should be strictly less than total amount of elements"));
    }

    @Test
    @Parameters(method = "generateRandomBigInteger")
    public void testRandomBigIntegerGenerationValidation(int digitLength,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomInteger.nextRandom(digitLength);
    }

    public static Object[] generateRandomBigInteger() {

        return $(
            $(0,
                "Length in digits must be greater than or equal to : 1; Found 0"),
            $(_aboveMaxBigIntegerDigitLength,
                "Length in digits must be less than or equal to maximum allowed value for secure random BigIntegers: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_BIG_INTEGER_DIGIT_LENGTH
                    + "; Found " + _aboveMaxBigIntegerDigitLength));
    }

    @Test
    @Parameters(method = "generateRandomBigIntegerWithBlackList")
    public void testRandomBigIntegerGenerationWithBlackListValidation(
            int digitLength, Set<BigInteger> blackList, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomInteger.nextRandom(digitLength, blackList);
    }

    public static Object[] generateRandomBigIntegerWithBlackList() {

        Set<BigInteger> emptyBlacklist = new HashSet<>();
        Set<BigInteger> bigIntegerBlackList =
            generateBigIntegerBlackList(BLACK_LIST_LENGTH);

        return $(
            $(0, emptyBlacklist,
                "Length in digits must be a positive integer; Found 0"),
            $(_aboveMaxBigIntegerDigitLength,
                emptyBlacklist,
                "Length in digits must be less than or equal to maximum allowed value for secure random BigIntegers: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_BIG_INTEGER_DIGIT_LENGTH
                    + "; Found " + _aboveMaxBigIntegerDigitLength),
            $(1, null,
                "The black list should be strictly less than total amount of elements"),
            $(1, bigIntegerBlackList,
                "The black list should be strictly less than total amount of elements"));
    }

    @Test
    @Parameters(method = "generateRandomBigIntegerByBits")
    public void testRandomBigIntegerGenerationByBitsValidation(
            int bitLength, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomInteger.nextRandomByBits(bitLength);
    }

    public static Object[] generateRandomBigIntegerByBits() {

        return $($(0, "Length in bits must be a positive integer; Found 0"));
    }

    @Test
    @Parameters(method = "generateRandomProbablePrimeBigInteger")
    public void testRandomProbablePrimeBigIntegerGenerationValidation(
            int bitLength, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomInteger.getRandomProbablePrimeBigInteger(bitLength);
    }

    public static Object[] generateRandomProbablePrimeBigInteger() {

        return $($(
            1,
            "Length in bits must be greater than or equal to minimum allowed value for probable prime BigIntegers: 2; Found 1"));
    }

    @Test
    @Parameters(method = "generateRandomProbablePrimeBigIntegerWithCertainty")
    public void testRandomProbablePrimeBigIntegerWithCertaintyGenerationValidation(
            int bitLength, int certainty, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoRandomInteger
            .getRandomProbablePrimeBigIntegerWithSetCertainty(bitLength,
                certainty);
    }

    public static Object[] generateRandomProbablePrimeBigIntegerWithCertainty() {

        return $(
            $(1,
                1,
                "Length in bits must be greater than or equal to minimum allowed value for probable prime BigIntegers: 2; Found 1"),
            $(2, 0, "Certainty must be a positive integer; Found 0"));
    }

    @Test
    @Parameters(method = "generate32CharRandomString")
    public void test32CharRandomStringGenerationValidation(int charLength,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _crypto32CharRandomString.nextRandom(charLength);
    }

    public static Object[] generate32CharRandomString() {

        return $(
            $(0,
                "Length in characters must be greater than or equal to : 1; Found 0"),
            $(_aboveMaxStringLength,
                "Length in characters must be less than or equal to maximum allowed value for secure random Strings: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH
                    + "; Found " + _aboveMaxStringLength));
    }

    @Test
    @Parameters(method = "generate32CharRandomStringWithBlackList")
    public void test32CharRandomStringGenerationWithBlackListValidation(
            int charLength, Set<String> blackList, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _crypto32CharRandomString.nextRandom(charLength, blackList);
    }

    public static Object[] generate32CharRandomStringWithBlackList() {

        Set<String> emptyBlacklist = new HashSet<>();
        Set<String> blackListTooLong =
            generateStringBlackList(BLACK_LIST_LENGTH, 2, 'A');
        Set<String> blackListBadChar =
            generateStringBlackList(1, BLACK_LIST_ELEMENT_LENGTH, '@');

        return $(
            $(0, emptyBlacklist,
                "Length in characters must be a positive integer; Found 0"),
            $(_aboveMaxStringLength,
                emptyBlacklist,
                "Length in characters must be less than or equal to maximum allowed value for secure random Strings: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH
                    + "; Found " + _aboveMaxStringLength),
            $(1, null, "Blacklist is null."),
            $(1, blackListTooLong,
                "Blacklist strings length should be equal to 1"),
            $(1, blackListBadChar,
                "Blacklist contains characters that are not in alphabet"));
    }

    @Test
    @Parameters(method = "generate64CharRandomString")
    public void test64CharRandomStringGenerationValidation(int charLength,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _crypto64CharRandomString.nextRandom(charLength);
    }

    public static Object[] generate64CharRandomString() {

        return $(
            $(0,
                "Length in characters must be greater than or equal to : 1; Found 0"),
            $(_aboveMaxStringLength,
                "Length in characters must be less than or equal to maximum allowed value for secure random Strings: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH
                    + "; Found " + _aboveMaxStringLength));
    }

    @Test
    @Parameters(method = "generate64CharRandomStringWithBlackList")
    public void test64CharRandomStringGenerationWithBlackListValidation(
            int charLength, Set<String> blackList, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _crypto32CharRandomString.nextRandom(charLength, blackList);
    }

    public static Object[] generate64CharRandomStringWithBlackList() {

        Set<String> emptyBlacklist = new HashSet<>();
        Set<String> blackListTooLong =
            generateStringBlackList(BLACK_LIST_LENGTH, 2, 'A');
        Set<String> blackListBadChar =
            generateStringBlackList(1, BLACK_LIST_ELEMENT_LENGTH, '@');

        return $(
            $(0, emptyBlacklist,
                "Length in characters must be a positive integer; Found 0"),
            $(_aboveMaxStringLength,
                emptyBlacklist,
                "Length in characters must be less than or equal to maximum allowed value for secure random Strings: "
                    + SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH
                    + "; Found " + _aboveMaxStringLength),
            $(1, null, "Blacklist is null."),
            $(1, blackListTooLong,
                "Blacklist strings length should be equal to 1"),
            $(1, blackListBadChar,
                "Blacklist contains characters that are not in alphabet"));
    }

    @Test
    @Parameters(method = "deriveKdf")
    public void testKdfDerivationValidation(byte[] seed,
            int lengthInBytes, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoKdfDeriver.deriveKey(seed, lengthInBytes);
    }

    public static Object[] deriveKdf() {

        return $(
            $(null, _kdfLengthInBytes, "Seed is null."),
            $(_emptyByteArray, _kdfLengthInBytes, "Seed is empty."),
            $(_kdfSeed, 0,
                "Length in bytes must be a positive integer; Found 0"));
    }

    @Test
    @Parameters(method = "derivePbkdf")
    public void testPbkdfDerivationValidation(char[] password,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoPbkdfDeriver.deriveKey(password);
    }

    public static Object[] derivePbkdf() {

        return $(
            $(null, "Password is null."),
            $(_emptyCharArray, "Password is blank."),
            $(_whiteSpaceString.toCharArray(), "Password is blank."),
            $(_belowMinLengthPbKdfPassword.toCharArray(),
                "Password length must be greater than or equal to : "
                    + DerivationConstants.MINIMUM_PBKDF_PASSWORD_LENGTH
                    + "; Found " + _belowMinLengthPbKdfPassword.length()),
            $(_aboveMaxLengthPbKdfPassword.toCharArray(),
                "Password length must be less than or equal to : "
                    + DerivationConstants.MAXIMUM_PBKDF_PASSWORD_LENGTH
                    + "; Found " + _aboveMaxLengthPbKdfPassword.length()));
    }

    @Test
    @Parameters(method = "derivePbkdfWithSalt")
    public void testPbkdfDerivationWithSaltValidation(char[] password,
            byte[] salt, String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _cryptoPbkdfDeriver.deriveKey(password, salt);
    }

    public static Object[] derivePbkdfWithSalt() {

        return $(
            $(null, _pbkdfSalt, "Password is null."),
            $(_emptyCharArray, _pbkdfSalt, "Password is blank."),
            $(_whiteSpaceString.toCharArray(), _pbkdfSalt,
                "Password is blank."),
            $(_belowMinLengthPbKdfPassword.toCharArray(), _pbkdfSalt,
                "Password length must be greater than or equal to : "
                    + DerivationConstants.MINIMUM_PBKDF_PASSWORD_LENGTH
                    + "; Found " + _belowMinLengthPbKdfPassword.length()),
            $(_aboveMaxLengthPbKdfPassword.toCharArray(), _pbkdfSalt,
                "Password length must be less than or equal to : "
                    + DerivationConstants.MAXIMUM_PBKDF_PASSWORD_LENGTH
                    + "; Found " + _aboveMaxLengthPbKdfPassword.length()),
            $(_pbkdfPassword.toCharArray(),
                _belowMinLengthPbkdfSalt,
                "Salt length must be greater than or equal to minimum allowed PBKDF salt length: "
                    + DerivationConstants.MINIMUM_PBKDF_SALT_LENGTH_IN_BYTES
                    + "; Found " + _belowMinLengthPbkdfSalt.length));
    }

    private static List<?> generateListWithNullElement(
            final int byteArrayLength, final int stringLength)
            throws GeneralCryptoLibException {

        Object[] objectArray = new Object[RANDOM_LIST_LENGTH + 1];
        for (int i = 0; i < (RANDOM_LIST_LENGTH / 2); i++) {
            objectArray[i] =
                _cryptoRandomBytes.nextRandom(byteArrayLength);
        }
        for (int i = (RANDOM_LIST_LENGTH / 2); i < RANDOM_LIST_LENGTH; i++) {
            objectArray[i] =
                _crypto64CharRandomString.nextRandom(stringLength);
        }
        objectArray[RANDOM_LIST_LENGTH] = null;

        return Arrays.asList(objectArray);
    }

    private static Set<Byte[]> generateByteArrayBlackList(
            final int arrayLength, final int listLength) {

        Set<Byte[]> blackList = new HashSet<>();
        for (int i = 0; i < listLength; i++) {
            Byte[] byteObjectArray = new Byte[arrayLength];
            Arrays.fill(byteObjectArray, (byte) 0);

            blackList.add(byteObjectArray);
        }

        return blackList;
    }

    private static Set<BigInteger> generateBigIntegerBlackList(
            final int listLength) {

        Set<BigInteger> blackList = new HashSet<>();
        for (int i = 0; i < listLength; i++) {
            blackList.add(new BigInteger(Integer.toString(i)));
        }

        return blackList;
    }

    private static Set<String> generateStringBlackList(
            final int stringLength, final int listLength,
            final char fillChar) {

        Set<String> blackList = new HashSet<>();
        for (int i = 0; i < listLength; i++) {
            char[] chars = new char[stringLength];
            fill(chars, fillChar);
            blackList.add(new String(chars));
        }

        return blackList;
    }
}
