#!/usr/bin/env bash

##
## Author......: See docs/credits.txt
## License.....: MIT
##

OPTS="--quiet --potfile-disable --hwmon-disable"

FORCE=0
RUNTIME=400

TDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# List of TrueCrypt modes which have test containers
TC_MODES="6211 6212 6213 6221 6222 6223 6231 6232 6233 6241 6242 6243"

# List of VeraCrypt modes which have test containers
VC_MODES="13711 13712 13713 13721 13722 13723 13731 13732 13733 13741 13742 13743 13751 13752 13753 13761 13762 13763 13771 13772 13773 13781 13782 13783"

# List of modes which either are OPTS_TYPE_PT_NEVERCRACK or produce collisions
NEVER_CRACK="9720 9820 14900 18100"

# List of modes which return a different output hash format than the input hash format
NOCHECK_ENCODING="16800 22000"

# LUKS mode has test containers
LUKS_MODE="14600"

# Cryptoloop mode which have test containers
CL_MODES="14511 14512 14513 14521 14522 14523 14531 14532 14533 14541 14542 14543 14551 14552 14553"

# missing hash types: 5200

HASH_TYPES=$(ls "${TDIR}"/test_modules/*.pm | sed -E 's/.*m0*([0-9]+).pm/\1/')
HASH_TYPES="${HASH_TYPES} ${TC_MODES} ${VC_MODES} ${LUKS_MODE} ${CL_MODES}"
HASH_TYPES=$(echo -n "${HASH_TYPES}" | tr ' ' '\n' | sort -u -n | tr '\n' ' ')

VECTOR_WIDTHS="1 2 4 8 16"

HASHFILE_ONLY=$(grep -l OPTS_TYPE_BINARY_HASHFILE "${TDIR}"/../src/modules/module_*.c | sed -E 's/.*module_0*([0-9]+).c/\1/' | tr '\n' ' ')
SLOW_ALGOS=$(grep -l ATTACK_EXEC_OUTSIDE_KERNEL "${TDIR}"/../src/modules/module_*.c | sed -E 's/.*module_0*([0-9]+).c/\1/' | tr '\n' ' ')

OUTD="test_$(date +%s)"

PACKAGE_CMD="7z a"
PACKAGE_FOLDER=""

EXTRACT_CMD="7z x"

mask_3[0]=""
mask_3[1]="?d"
mask_3[2]="?d?d"
mask_3[3]="?d?d?d"
mask_3[4]="?d?d?d?d"
mask_3[5]="?d?d?d?d?d"
mask_3[6]="?d?d?d?d?d?d"
mask_3[7]="?d?d?d?d?d?d?d"
mask_3[8]="?d?d?d?d?d?d?d?d"
mask_3[9]="?d?d?d?d?d?d?d?d?d"
mask_3[10]="?d?d?d?d?d?d?d?d?d?d"
mask_3[11]="?d?d?d?d?d?d?d?d?d?d?d"
mask_3[12]="?d?d?d?d?d?d?d?d?d?d?d?d"
mask_3[13]="?d?d?d?d?d?d?d?d?d?d?d?d?d"
mask_3[14]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d"
mask_3[15]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d"
mask_3[16]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d0"
mask_3[17]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d00"
mask_3[18]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d000"
mask_3[19]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d0000"
mask_3[20]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d00000"
mask_3[21]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d000000"
mask_3[22]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d0000000"
mask_3[23]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d00000000"
mask_3[24]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d000000000"
mask_3[25]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d0000000000"
mask_3[26]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d00000000000"
mask_3[27]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d000000000000"
mask_3[28]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d0000000000000"
mask_3[29]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d00000000000000"
mask_3[30]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d000000000000000"
mask_3[31]="?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d0000000000000000"

mask_6[0]=""
mask_6[1]=""
mask_6[2]="?d"
mask_6[3]="?d?d"
mask_6[4]="?d?d"
mask_6[5]="?d?d?d"
mask_6[6]="?d?d?d"
mask_6[7]="?d?d?d?d"
mask_6[8]="?d?d?d?d"
mask_6[9]="?d?d?d?d?d"
mask_6[10]="?d?d?d?d?d"
mask_6[11]="?d?d?d?d?d?d"
mask_6[12]="?d?d?d?d?d?d"
mask_6[13]="?d?d?d?d?d?d?d"
mask_6[14]="?d?d?d?d?d?d?d"
mask_6[15]="?d?d?d?d?d?d?d?d"
mask_6[16]="?d?d?d?d?d?d?d?d"
mask_6[17]="?d?d?d?d?d?d?d?d0"
mask_6[18]="?d?d?d?d?d?d?d?d0"
mask_6[19]="?d?d?d?d?d?d?d?d00"
mask_6[20]="?d?d?d?d?d?d?d?d00"
mask_6[21]="?d?d?d?d?d?d?d?d000"
mask_6[22]="?d?d?d?d?d?d?d?d000"
mask_6[23]="?d?d?d?d?d?d?d?d0000"
mask_6[24]="?d?d?d?d?d?d?d?d0000"
mask_6[25]="?d?d?d?d?d?d?d?d00000"
mask_6[26]="?d?d?d?d?d?d?d?d00000"
mask_6[27]="?d?d?d?d?d?d?d?d000000"
mask_6[28]="?d?d?d?d?d?d?d?d000000"
mask_6[29]="?d?d?d?d?d?d?d?d0000000"
mask_6[30]="?d?d?d?d?d?d?d?d0000000"
mask_6[31]="?d?d?d?d?d?d?d?d00000000"

mask_7[0]=""
mask_7[1]=""
mask_7[2]="?d"
mask_7[3]="?d"
mask_7[4]="?d?d"
mask_7[5]="?d?d"
mask_7[6]="?d?d?d"
mask_7[7]="?d?d?d"
mask_7[8]="?d?d?d?d"
mask_7[9]="?d?d?d?d"
mask_7[10]="?d?d?d?d?d"
mask_7[11]="?d?d?d?d?d"
mask_7[12]="?d?d?d?d?d?d"
mask_7[13]="?d?d?d?d?d?d"
mask_7[14]="?d?d?d?d?d?d?d"
mask_7[15]="?d?d?d?d?d?d?d"
mask_7[16]="?d?d?d?d?d?d?d?d"
mask_7[17]="?d?d?d?d?d?d?d?d"
mask_7[18]="?d?d?d?d?d?d?d?d0"
mask_7[19]="?d?d?d?d?d?d?d?d0"
mask_7[20]="?d?d?d?d?d?d?d?d00"
mask_7[21]="?d?d?d?d?d?d?d?d00"
mask_7[22]="?d?d?d?d?d?d?d?d000"
mask_7[23]="?d?d?d?d?d?d?d?d000"
mask_7[24]="?d?d?d?d?d?d?d?d0000"
mask_7[25]="?d?d?d?d?d?d?d?d0000"
mask_7[26]="?d?d?d?d?d?d?d?d00000"
mask_7[27]="?d?d?d?d?d?d?d?d00000"
mask_7[28]="?d?d?d?d?d?d?d?d000000"
mask_7[29]="?d?d?d?d?d?d?d?d000000"
mask_7[30]="?d?d?d?d?d?d?d?d0000000"
mask_7[31]="?d?d?d?d?d?d?d?d0000000"

# Array lookup
# $1: value
# $2: array
# Returns 0 (SUCCESS) if the value is found, 1 otherwise

function is_in_array()
{
  for e in "${@:2}"; do
    [ "$e" = "$1" ] && return 0
  done

  return 1
}

function init()
{
  if [ "${PACKAGE}" -eq 1 ]; then
    echo "[ ${OUTD} ] > Generate tests for hash type $hash_type."
  else
    echo "[ ${OUTD} ] > Init test for hash type $hash_type."
  fi

  rm -rf "${OUTD}/${hash_type}.sh" "${OUTD}/${hash_type}_passwords.txt" "${OUTD}/${hash_type}_hashes.txt"

  # Exclude TrueCrypt, VeraCrypt and CryptoLoop testing modes
  if is_in_array "${hash_type}" ${TC_MODES}; then
    return 0
  fi
  if is_in_array "${hash_type}" ${VC_MODES}; then
    return 0
  fi
  if is_in_array "${hash_type}" ${CL_MODES}; then
    return 0
  fi

  if [ "${hash_type}" -eq ${LUKS_MODE} ]; then
    which 7z &>/dev/null
    if [ $? -eq 1 ]; then
      echo "ATTENTION: 7z is missing. Skipping download and extract luks test files."
      return 0
    fi

    luks_tests_folder="${TDIR}/luks_tests/"

    if [ ! -d "${luks_tests_folder}" ]; then
      mkdir -p "${luks_tests_folder}"
    fi

    luks_first_test_file="${luks_tests_folder}/hashcat_ripemd160_aes_cbc-essiv_128.luks"

    if [ ! -f "${luks_first_test_file}" ]; then
      luks_tests="hashcat_luks_testfiles.7z"
      luks_tests_url="https://hashcat.net/misc/example_hashes/${luks_tests}"

      cd "${TDIR}" || exit

      # if the file already exists, but was not successfully extracted, we assume it's a broken
      # downloaded file and therefore it should be deleted

      if [ -f "${luks_tests}" ]; then
        rm -f "${luks_tests}"
      fi

      echo ""
      echo "ATTENTION: the luks test files (for -m ${hash_type}) are currently missing on your system."
      echo "They will be fetched from ${luks_tests_url}"
      echo "Note: this needs to be done only once and could take a little bit to download/extract."
      echo "These luks test files are not shipped directly with hashcat because the file sizes are"
      echo "particularily large and therefore a bandwidth burner for users who do not run these tests."
      echo ""

      # download:
      wget -q "${luks_tests_url}"

      if [ $? -ne 0 ] || [ ! -f "${luks_tests}" ]; then
        cd - >/dev/null
        echo "ERROR: Could not fetch the luks test files from this url: ${luks_tests_url}"
        return 0
      fi

      # extract:

      ${EXTRACT_CMD} "${luks_tests}" &>/dev/null

      # cleanup:

      rm -f "${luks_tests}"
      cd - >/dev/null || exit

      # just to be very sure, check again that (one of) the files now exist:

      if [ ! -f "${luks_first_test_file}" ]; then
        echo "ERROR: downloading and extracting ${luks_tests} into ${luks_tests_folder} did not complete successfully"
        return 0
      fi
    fi

    return 0
  fi

  # create list of password and hashes of same type
  cmd_file=${OUTD}/${hash_type}.sh

  grep " ${hash_type} '" "${OUTD}/all.sh" > "${cmd_file}" 2>/dev/null

  # create separate list of password and hashes
  sed 's/^echo *|.*$//'       "${cmd_file}" | awk '{print $2}'                  > "${OUTD}/${hash_type}_passwords.txt"
  sed 's/^echo *|/echo "" |/' "${cmd_file}" | awk '{print $10}' | cut -d"'" -f2 > "${OUTD}/${hash_type}_hashes.txt"

  if [ "${hash_type}" -eq 10300 ]; then
    #cat ${OUTD}/${hash_type}.sh | cut -d' ' -f11- | cut -d"'" -f2 > ${OUTD}/${hash_type}_hashes.txt
    cut -d"'" -f2 "${OUTD}/${hash_type}.sh" > "${OUTD}/${hash_type}_hashes.txt"
  fi

  # truncate dicts
  rm -rf "${OUTD}/${hash_type}_dict1" "${OUTD}/${hash_type}_dict2"
  touch "${OUTD}/${hash_type}_dict1" "${OUTD}/${hash_type}_dict2"

  # minimum password length

  min=1         # minimum line number from start of the file
  min_offset=0  # minimum offset starting from ${min} lines

  if   [ "${hash_type}" -eq  2500 ]; then
    min_offset=7 # means length 8, since we start with 0
  elif [ "${hash_type}" -eq 14000 ]; then
    min=0
    min_offset=4
  elif [ "${hash_type}" -eq 14100 ]; then
    min=0
    min_offset=3
  elif [ "${hash_type}" -eq 14900 ]; then
    min=0
    min_offset=5
  elif [ "${hash_type}" -eq 15400 ]; then
    min=0
    min_offset=3
  elif [ "${hash_type}" -eq 16800 ]; then
    min_offset=7 # means length 8, since we start with 0
  elif [ "${hash_type}" -eq 22000 ]; then
    min_offset=7 # means length 8, since we start with 0
  fi

  # foreach password entry split password in 2 (skip first entry, is len 1)

  i=1

  while read -r -u 9 pass; do

    if [ ${i} -gt ${min} ]; then

      # split password, 'i' is the len
      p0=$((i / 2))
      p1=$((p0 + 1))

      # special case (passwords longer than expected)
      pass_len=${#pass}

      if [ "${pass_len}" -gt 1 ]; then

        p1=$((p1 + min_offset))
        p0=$((p0 + min_offset))

        if [ "${p1}" -gt "${pass_len}" ]; then
          p1=${pass_len}
          p0=$((p1 - 1))
        fi

        # add splitted password to dicts
        echo "${pass}" | cut -c -${p0} >> "${OUTD}/${hash_type}_dict1"
        echo "${pass}" | cut -c ${p1}- >> "${OUTD}/${hash_type}_dict2"
      elif [ "${pass_len}" -eq 1 ]; then
        echo "${pass}" >> "${OUTD}/${hash_type}_dict1"
        echo >> "${OUTD}/${hash_type}_dict2"
      else
        echo >> "${OUTD}/${hash_type}_dict1"
        echo >> "${OUTD}/${hash_type}_dict2"
      fi

    fi

    i=$((i + 1))

  done 9< "${OUTD}/${hash_type}_passwords.txt"

  min_len=0

  if   [ "${hash_type}" -eq  2500 ]; then
    min_len=7 # means length 8, since we start with 0
  elif [ "${hash_type}" -eq 14000 ]; then
    min_len=7
  elif [ "${hash_type}" -eq 14100 ]; then
    min_len=23
  elif [ "${hash_type}" -eq 14900 ]; then
    min_len=9
  elif [ "${hash_type}" -eq 15400 ]; then
    min_len=31
  elif [ "${hash_type}" -eq 16800 ]; then
    min_len=7 # means length 8, since we start with 0
  elif [ "${hash_type}" -eq 22000 ]; then
    min_len=7 # means length 8, since we start with 0
  fi

  # generate multiple pass/hash foreach len (2 to 8)
  if [ "${MODE}" -ge 1 ]; then

    i=2

    while [ "$i" -lt 9 ]; do

      cmd_file=${OUTD}/${hash_type}_multi_${i}.txt

      rm -rf "${cmd_file}" "${OUTD}/${hash_type}_passwords_multi_${i}.txt" "${OUTD}/${hash_type}_hashes_multi_${i}.txt"
      rm -rf "${OUTD}/${hash_type}_dict1_multi_${i}" "${OUTD}/${hash_type}_dict2_multi_${i}"
      touch "${OUTD}/${hash_type}_dict1_multi_${i}" "${OUTD}/${hash_type}_dict2_multi_${i}"

      perl tools/test.pl single "${hash_type}" ${i} > "${cmd_file}"

      sed 's/^echo *|.*$//'       "${cmd_file}" | awk '{print $2}'                  > "${OUTD}/${hash_type}_passwords_multi_${i}.txt"
      sed 's/^echo *|/echo "" |/' "${cmd_file}" | awk '{print $10}' | cut -d"'" -f2 > "${OUTD}/${hash_type}_hashes_multi_${i}.txt"

      if [ "${hash_type}" -eq 10300 ]; then
        #cat ${OUTD}/${hash_type}_multi_${i}.txt | cut -d' ' -f11- | cut -d"'" -f2 > ${OUTD}/${hash_type}_hashes_multi_${i}.txt
        cut -d"'" -f2 "${OUTD}/${hash_type}_multi_${i}.txt" > "${OUTD}/${hash_type}_hashes_multi_${i}.txt"
      fi

      # split password, 'i' is the len
      p0=$((i / 2))
      p1=$((p0 + 1))

      p0=$((p0 + min_len))
      p1=$((p1 + min_len))

      while read -r -u 9 pass; do

        # add splitted password to dicts
        echo "${pass}" | cut -c -${p0} >> "${OUTD}/${hash_type}_dict1_multi_${i}"
        echo "${pass}" | cut -c ${p1}- >> "${OUTD}/${hash_type}_dict2_multi_${i}"

      done 9< "${OUTD}/${hash_type}_passwords_multi_${i}.txt"

      i=$((i + 1))

    done

  fi
}

function status()
{
  RET=$1

  cnt=$((cnt + 1))

  if [ "${RET}" -ne 0 ]; then
    case ${RET} in
      246)
        echo "autotune failure, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      248)
        echo "skipped by runtime (mixed backend errors detected), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      249)
        echo "skipped by runtime (Invalid module_extra_buffer_size), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      250)
        echo "skipped by runtime (Too many compute units to keep minimum kernel accel limit), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      251)
        echo "skipped by runtime (main kernel build error), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      252)
        echo "skipped by runtime (memory hit limit), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      253)
        echo "skipped by runtime (module_unstable_warning), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      1)
        if ! is_in_array "${hash_type}" ${NEVER_CRACK_ALGOS}; then
           echo "password not found, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"
           e_nf=$((e_nf + 1))
        fi

        ;;

      4)
        echo "timeout reached, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_to=$((e_to + 1))
        ;;

      10)
        if is_in_array "${hash_type}" ${NEVER_CRACK_ALGOS}; then
          return
        fi

        if [ "${pass_only}" -eq 1 ]; then
          echo "plains not found in output, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"
        else
          echo "hash:plains not matched in output, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.tx"t
        fi

        e_nm=$((e_nm + 1))
        ;;

      20)
        echo "grep out-of-memory (cannot check if plains match in output), cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_ce=$((e_ce + 1))
        e_nm=$((e_nm + 1))
        ;;

      30)
        echo "luks test files are missing, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        e_rs=$((e_rs + 1))
        ;;

      *)
        echo "! unhandled return code ${RET}, cmdline : ${CMD}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"
        echo "! unhandled return code, see ${OUTD}/logfull.txt or ${OUTD}/test_report.log for details."

        e_nf=$((e_nf + 1))
        ;;

    esac
  fi
}

function attack_0()
{
  file_only=0

  if is_in_array "${hash_type}" ${FILE_BASED_ALGOS}; then
    file_only=1
  fi

  # single hash
  if [ "${MODE}" -ne 1 ]; then

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    echo "> Testing hash type $hash_type with attack mode 0, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    max=32

    if is_in_array "${hash_type}" ${TIMEOUT_ALGOS}; then
      max=12
    fi

    i=0

    while read -r -u 9 line; do

      if [ "${i}" -ge ${max} ]; then
        break
      fi

      hash="$(echo "${line}" | cut -d\'  -f2)"
      pass="$(echo "${line}" | cut -d' ' -f2)"

      if [ -z "${hash}" ]; then
        break
      fi

      if [ "${file_only}" -eq 1 ]; then

        temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"

        if [ "${hash_type}" -ne 22000 ]; then
          echo "${hash}" | base64 -d > "${temp_file}"
        else
          echo "${hash}" > "${temp_file}"
        fi

        hash="${temp_file}"

      fi

      pass_old=${pass}

      if [ "${hash_type}" -eq 20510 ]; then # special case for PKZIP Master Key
        pass=$(echo "${pass}" | cut -b 7-) # skip the first 6 chars
      fi

      CMD="echo ${pass} | ./${BIN} ${OPTS} -a 0 -m ${hash_type} '${hash}'"

      echo -n "[ len $((i + 1)) ] " >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

      output=$(echo "${pass}" | ./${BIN} ${OPTS} -a 0 -m ${hash_type} "${hash}" 2>&1)

      ret=${?}

      pass=${pass_old}

      echo "${output}" >> "${OUTD}/logfull.txt"

      if [ "${ret}" -eq 0 ]; then

        if [ "${pass_only}" -eq 1 ]; then
          search=":${pass}"
        else
          search="${hash}:${pass}"
        fi

        echo "${output}" | grep -F "${search}" &>/dev/null

        newRet=$?

        if [ "${newRet}" -eq 2 ]; then

          # out-of-memory, workaround

          echo "${output}" | head -1 > tmp_file_out
          echo "${search}" > tmp_file_search

          out_md5=$(md5sum tmp_file_out | cut -d' ' -f1)
          search_md5=$(md5sum tmp_file_search | cut -d' ' -f1)

          rm tmp_file_out tmp_file_search

          if [ "${out_md5}" == "${search_md5}" ]; then
            newRet=0
          fi
        fi

        if [ "${newRet}" -ne 0 ]; then
          if [ "${newRet}" -eq 2 ]; then
            ret=20
          else
            ret=10
          fi
        fi

      fi

      status ${ret}

      i=$((i + 1))

    done 9< "${OUTD}/${hash_type}.sh"

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 0, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi

  # multihash
  if [ "${MODE}" -ne 0 ]; then

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    echo "> Testing hash type $hash_type with attack mode 0, markov ${MARKOV}, multi hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    hash_file=${OUTD}/${hash_type}_hashes.txt

    # if file_only -> decode all base64 "hashes" and put them in the temporary file

    if [ "${file_only}" -eq 1 ]; then

      temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"
      rm -f "${temp_file}"

      hash_file=${temp_file}

      while read -r file_only_hash; do

        if [ "${hash_type}" -ne 22000 ]; then
          echo -n "${file_only_hash}" | base64 -d >> "${temp_file}"
        else
          echo "${file_only_hash}" >> "${temp_file}"
        fi

      done < "${OUTD}/${hash_type}_hashes.txt"

    fi

    CMD="cat ${OUTD}/${hash_type}_passwords.txt | ./${BIN} ${OPTS} -a 0 -m ${hash_type} ${hash_file}"

    output=$(./${BIN} ${OPTS} -a 0 -m ${hash_type} ${hash_file} < ${OUTD}/${hash_type}_passwords.txt 2>&1)

    ret=${?}

    echo "${output}" >> "${OUTD}/logfull.txt"

    if [ "${ret}" -eq 0 ]; then

      i=1

      while read -r -u 9 hash; do

        pass=$(sed -n ${i}p "${OUTD}/${hash_type}_passwords.txt")

        if [ "${pass_only}" -eq 1 ]; then
          search=":${pass}"
        else
          search="${hash}:${pass}"
        fi

        echo "${output}" | grep -F "${search}" &>/dev/null

        newRet=$?

        if [ "${newRet}" -ne 0 ]; then
          if [ "${newRet}" -eq 2 ]; then
            ret=20
          else
            ret=10
          fi

          break
        fi

        i=$((i + 1))

      done 9< "${OUTD}/${hash_type}_hashes.txt"

    fi

    status ${ret}

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 0, Mode multi,  Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

function attack_1()
{
  file_only=0

  if is_in_array "${hash_type}" ${FILE_BASED_ALGOS}; then
    file_only=1
  fi

  # single hash
  if [ "${MODE}" -ne 1 ]; then

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    min=1
    max=8

    if   [ "${hash_type}" -eq 14000 ]; then
      min=0
      max=5
    elif [ "${hash_type}" -eq 14100 ]; then
      min=0
      max=5
    elif [ "${hash_type}" -eq 14900 ]; then
      min=0
      max=5
    elif [ "${hash_type}" -eq 15400 ]; then
      min=0
      max=5
    elif [ "${hash_type}" -eq 20510 ]; then
      min=2
    fi

    echo "> Testing hash type $hash_type with attack mode 1, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"
    i=1
    while read -r -u 9 hash; do

      if [ $i -gt ${min} ]; then

        if [ "${file_only}" -eq 1 ]; then

          temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"

          if [ "${hash_type}" -ne 22000 ]; then
            echo "${hash}" | base64 -d > "${temp_file}"
          else
            echo "${hash}" > "${temp_file}"
          fi

          hash="${temp_file}"

        fi

        line_nr=1

        if [ "$min" -eq 0 ]; then
          line_nr=$i
        elif [ "${i}" -gt 1 ]; then
          line_nr=$((i - 1))
        fi

        dict1="${OUTD}/${hash_type}_dict1"
        dict2="${OUTD}/${hash_type}_dict2"

        if [ "${hash_type}" -eq 20510 ]; then # special case for PKZIP Master Key
          line_dict1=$(sed -n ${line_nr}p "${dict1}")
          line_dict2=$(sed -n ${line_nr}p "${dict2}")
          line_num=$(wc -l "${dict1}" | sed -E 's/ *([0-9]+) .*$/\1/')

          line_dict1_orig=${line_dict1}
          line_dict2_orig=${line_dict2}

          if [ "${#line_dict1}" -ge 6 ]; then
            line_dict1=$(echo "${line_dict1}" | cut -b 7-) # skip the first 6 chars
          else
            # we need to also "steal" some chars from the second dict
            num_to_steal=$((6 - ${#line_dict1}))
            num_steal_start=$((num_to_steal + 1))

            if [ "${#line_dict2}" -ge 6 ]; then
              num_to_steal_new=$(((${#line_dict2} - num_to_steal) / 2))

              if [ "${num_to_steal_new}" -gt ${num_to_steal} ]; then
                num_to_steal=${num_to_steal_new}
              fi
            fi

            line_chars_stolen=$(echo "${line_dict2}" | cut -b -${num_to_steal} | cut -b ${num_steal_start}-)

            line_dict1="${line_chars_stolen}"
            line_dict2=$(echo "${line_dict2}" | cut -b $((num_to_steal + 1))-)
          fi

          # finally, modify the dicts accordingly:

          tmp_file="${dict1}_mod"
          head -n $((line_nr - 1)) "${dict1}" > "${tmp_file}"
          echo "${line_dict1}" >> "${tmp_file}"
          tail -n $((line_num - line_nr - 1)) "${dict1}" >> "${tmp_file}"

          dict1=${tmp_file}

          tmp_file="${dict2}_mod"

          head -n $((line_nr - 1)) "${dict2}" > "${tmp_file}"
          echo "${line_dict2}" >> "${tmp_file}"
          tail -n $((line_num - line_nr - 1)) "${dict2}" >> "${tmp_file}"

          dict2=${tmp_file}
        fi

        CMD="./${BIN} ${OPTS} -a 1 -m ${hash_type} '${hash}' ${dict1} ${dict2}"

        echo -n "[ len $i ] " >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        output=$(./${BIN} ${OPTS} -a 1 -m ${hash_type} "${hash}" ${dict1} ${dict2} 2>&1)

        ret=${?}

        echo "${output}" >> "${OUTD}/logfull.txt"

        if [ "${ret}" -eq 0 ]; then

          line_dict1=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict1")
          line_dict2=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict2")

          if [ "${pass_only}" -eq 1 ]; then
            search=":${line_dict1}${line_dict2}"
          else
            search="${hash}:${line_dict1}${line_dict2}"
          fi

          echo "${output}" | grep -F "${search}" &>/dev/null

          newRet=$?

          if [ "${newRet}" -eq 2 ]; then

            # out-of-memory, workaround

            echo "${output}" | head -1 > tmp_file_out
            echo "${search}" > tmp_file_search

            out_md5=$(md5sum tmp_file_out | cut -d' ' -f1)
            search_md5=$(md5sum tmp_file_search | cut -d' ' -f1)

            rm tmp_file_out tmp_file_search

            if [ "${out_md5}" == "${search_md5}" ]; then
              newRet=0
            fi
          fi

          if [ "${newRet}" -ne 0 ]; then
            if [ "${newRet}" -eq 2 ]; then
              ret=20
            else
              ret=10
            fi
          fi

        fi

        status ${ret}

      fi

      if [ $i -eq ${max} ]; then break; fi

      i=$((i + 1))

    done 9< "${OUTD}/${hash_type}_hashes.txt"

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 1, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi

  # multihash
  if [ "${MODE}" -ne 0 ]; then

    # no multi hash checks for these modes (because we only have 1 hash for each of them)

    if   [ "${hash_type}" -eq 14000 ]; then
      return
    elif [ "${hash_type}" -eq 14100 ]; then
      return
    elif [ "${hash_type}" -eq 14900 ]; then
      return
    elif [ "${hash_type}" -eq 15400 ]; then
      return
    fi

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    offset=7

    if [ "${hash_type}" -eq  5800 ]; then
      offset=6
    elif [ "${hash_type}" -eq  3000 ]; then
      offset=6
    fi

    hash_file=${OUTD}/${hash_type}_multihash_combi.txt

    tail -n ${offset} "${OUTD}/${hash_type}_hashes.txt" > "${hash_file}"

    # if file_only -> decode all base64 "hashes" and put them in the temporary file

    if [ "${file_only}" -eq 1 ]; then

      temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"
      rm -f "${temp_file}"

      hash_file=${temp_file}

      while read -r file_only_hash; do

        if [ "${hash_type}" -ne 22000 ]; then
          echo -n "${file_only_hash}" | base64 -d >> "${temp_file}"
        else
          echo "${file_only_hash}" >> "${temp_file}"
        fi

      done < "${OUTD}/${hash_type}_multihash_combi.txt"

    fi

    CMD="./${BIN} ${OPTS} -a 1 -m ${hash_type} ${hash_file} ${OUTD}/${hash_type}_dict1 ${OUTD}/${hash_type}_dict2"

    echo "> Testing hash type $hash_type with attack mode 1, markov ${MARKOV}, multi hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    output=$(./${BIN} ${OPTS} -a 1 -m ${hash_type} ${hash_file} ${OUTD}/${hash_type}_dict1 ${OUTD}/${hash_type}_dict2 2>&1)

    ret=${?}

    echo "${output}" >> "${OUTD}/logfull.txt"

    if [ "${ret}" -eq 0 ]; then

      i=0

      while read -r -u 9 hash; do

        line_nr=1

        if [ "${offset}" -gt ${i} ]; then
          line_nr=$((offset - i))
        fi

        line_dict1=$(tail -n ${line_nr} "${OUTD}/${hash_type}_dict1" | head -1)
        line_dict2=$(tail -n ${line_nr} "${OUTD}/${hash_type}_dict2" | head -1)

        if [ "${pass_only}" -eq 1 ]; then
          search=":${line_dict1}${line_dict2}"
        else
          search="${hash}:${line_dict1}${line_dict2}"
        fi

        echo "${output}" | grep -F "${search}" &>/dev/null

        newRet=$?

        if [ "${newRet}" -ne 0 ]; then
          if [ "${newRet}" -eq 2 ]; then
            ret=20
          else
            ret=10
          fi

          break
        fi

        i=$((i + 1))

      done 9< "${OUTD}/${hash_type}_multihash_combi.txt"
    fi

    status ${ret}

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 1, Mode multi,  Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

function attack_3()
{
  file_only=0

  if is_in_array "${hash_type}" ${FILE_BASED_ALGOS}; then
    file_only=1
  fi

  # single hash
  if [ "${MODE}" -ne 1 ]; then

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    echo "> Testing hash type $hash_type with attack mode 3, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    max=8

    # some algos have a minimum password length

    if   [ "${hash_type}" -eq  2500 ]; then
      max=7
    elif [ "${hash_type}" -eq 14000 ]; then
      max=1
    elif [ "${hash_type}" -eq 14100 ]; then
      max=1
    elif [ "${hash_type}" -eq 14900 ]; then
      max=1
    elif [ "${hash_type}" -eq 15400 ]; then
      max=1
    elif [ "${hash_type}" -eq 16800 ]; then
      max=7
    elif [ "${hash_type}" -eq 22000 ]; then
      max=7
    fi

    i=1

    while read -r -u 9 hash; do

      if [ "${i}" -gt 6 ]; then
        if is_in_array "${hash_type}" ${TIMEOUT_ALGOS}; then
          break
        fi
      fi

      if [ "${file_only}" -eq 1 ]; then

        temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"

        if [ "${hash_type}" -ne 22000 ]; then
          echo "${hash}" | base64 -d > "${temp_file}"
        else
          echo "${hash}" > "${temp_file}"
        fi

        hash="${temp_file}"
      fi

      # construct a meaningful mask from the password itself:

      dict="${OUTD}/${hash_type}_passwords.txt"

      pass=$(sed -n ${i}p "${dict}")

      # passwords can't be smaller than mask in -a 3 = mask attack

      if [ "${#pass}" -lt ${i} ]; then
        i=$((i + 1))
        continue
      fi

      pass_part_2=$(echo -n "${pass}" | cut -b  $((i + 1))-)

      mask=""

      if   [ "${hash_type}" -eq 14000 ]; then
        mask="${pass}"
      elif [ "${hash_type}" -eq 14100 ]; then
        mask="${pass}"
      else
        for i in $(seq 1 ${i}); do
          mask="${mask}?d"
        done

        mask="${mask}${pass_part_2}"
      fi

      if [ "${hash_type}" -eq 20510 ]; then # special case for PKZIP Master Key
        if [ "${i}" -le 1 ]; then
          i=$((i + 1))
          continue
        fi

        cut_pos=$((i * 2 + 6 - i + 1)) # skip it in groups of 2 ("?d"), at least 6, offset +1 for cut to work

        if [ "${i}" -gt 6 ]; then
          cut_pos=13 # 6 * ?d + 1 (6 * 2 + 1)
        fi

        mask=$(echo "${mask}" | cut -b ${cut_pos}-)
      fi

      CMD="./${BIN} ${OPTS} -a 3 -m ${hash_type} '${hash}' ${mask}"

      echo -n "[ len $i ] " >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

      output=$(./${BIN} ${OPTS} -a 3 -m ${hash_type} "${hash}" ${mask} 2>&1)

      ret=${?}

      echo "${output}" >> "${OUTD}/logfull.txt"

      if [ "${ret}" -eq 0 ]; then

        line_dict=$(sed -n ${i}p "${dict}")

        if [ "${pass_only}" -eq 1 ]; then
          search=":${line_dict}"
        else
          search="${hash}:${line_dict}"
        fi

        echo "${output}" | grep -F "${search}" &>/dev/null

        newRet=$?

        if [ "${newRet}" -eq 2 ]; then

          # out-of-memory, workaround

          echo "${output}" | head -1 > tmp_file_out
          echo "${search}" > tmp_file_search

          out_md5=$(md5sum tmp_file_out | cut -d' ' -f1)
          search_md5=$(md5sum tmp_file_search | cut -d' ' -f1)

          rm tmp_file_out tmp_file_search

          if [ "${out_md5}" == "${search_md5}" ]; then
            newRet=0
          fi
        fi

        if [ "${newRet}" -ne 0 ]; then
          if [ "${newRet}" -eq 2 ]; then
            ret=20
          else
            ret=10
          fi
        fi

      fi

      status ${ret}

      if [ $i -eq ${max} ]; then break; fi

      i=$((i + 1))

    done 9< "${OUTD}/${hash_type}_hashes.txt"

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 3, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi

  # multihash
  if [ "${MODE}" -ne 0 ]; then

    # no multi hash checks for these modes (because we only have 1 hash for each of them)

    if   [ "${hash_type}" -eq 14000 ]; then
      return
    elif [ "${hash_type}" -eq 14100 ]; then
      return
    elif [ "${hash_type}" -eq 14900 ]; then
      return
    elif [ "${hash_type}" -eq 15400 ]; then
      return
    fi

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    increment_max=8

    if is_in_array "${hash_type}" ${TIMEOUT_ALGOS}; then
      increment_max=5
    fi

    increment_min=1

    if   [ "${hash_type}" -eq  2500 ]; then
      increment_min=8
      increment_max=9
    fi

    if   [ "${hash_type}" -eq 16800 ]; then
      increment_min=8
      increment_max=9
    fi

    if   [ "${hash_type}" -eq 22000 ]; then
      increment_min=8
      increment_max=9
    fi

    # if file_only -> decode all base64 "hashes" and put them in the temporary file

    if [ "${file_only}" -eq 1 ]; then

      temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"
      rm -f "${temp_file}"

      hash_file=${temp_file}

      while read -r file_only_hash; do

        if [ "${hash_type}" -ne 22000 ]; then
          echo -n "${file_only_hash}" | base64 -d >> "${temp_file}"
        else
          echo "${file_only_hash}" >> "${temp_file}"
        fi

      done < "${OUTD}/${hash_type}_multihash_bruteforce.txt"

    fi

    hash_file=${OUTD}/${hash_type}_multihash_bruteforce.txt

    tail_hashes=$(awk "length >= ${increment_min} && length <= ${increment_max}" "${OUTD}/${hash_type}_passwords.txt" | wc -l)
    head_hashes=$(awk                               "length <= ${increment_max}" "${OUTD}/${hash_type}_passwords.txt" | wc -l)

    # in very rare cases (e.g. without -O and long passwords) we need to use .hcmask files with the passwords in it
    # otherwise there are no good masks we can test for such long passwords

    need_hcmask=0

    if [ "${tail_hashes}" -gt "${head_hashes}" ]; then
      need_hcmask=1
    fi

    if [ "${tail_hashes}" -lt 1 ]; then
      need_hcmask=1
    fi

    if [ ${need_hcmask} -eq 0 ]; then
      head -n "${head_hashes}" "${OUTD}/${hash_type}_hashes.txt" | tail -n "${tail_hashes}" > "${hash_file}"
    else
      tail_hashes=$(awk "length >= ${increment_min}" "${OUTD}/${hash_type}_passwords.txt" | wc -l)

      if [ "${tail_hashes}" -lt 1 ]; then
        return
      fi

      tail -n "${tail_hashes}" "${OUTD}/${hash_type}_hashes.txt"  > "${hash_file}"
    fi

    mask_pos=8

    if [ "${increment_min}" -gt ${mask_pos} ]; then
      mask_pos=${increment_min}
    fi

    mask=""
    cracks_offset=0

    if [ ${need_hcmask} -eq 0 ]; then
      cracks_offset=$((head_hashes - tail_hashes))

      mask=${mask_3[${mask_pos}]}
    else
      num_hashes=$(wc -l < "${OUTD}/${hash_type}_hashes.txt")
      cracks_offset=$((num_hashes - tail_hashes))

      mask=${OUTD}/${hash_type}_passwords.txt # fake hcmask file (i.e. the original dict)
    fi

    custom_charsets=""

    # modify "default" mask if needed (and set custom charset to reduce keyspace)

    if [ "${hash_type}" -eq 2500 ]; then

      mask="?d?d?d?d?d?1?2?3?4"

      charset_1=""
      charset_2=""
      charset_3=""
      charset_4=""

      # check positions (here we assume that mask is always composed of non literal chars
      # i.e. something like ?d?l?u?s?1 is possible, but ?d?dsuffix not
      charset_1_pos=$(expr index "${mask}" 1)
      charset_2_pos=$(expr index "${mask}" 2)
      charset_3_pos=$(expr index "${mask}" 3)
      charset_4_pos=$(expr index "${mask}" 4)

      # divide each charset position by 2 since each of them occupies 2 positions in the mask

      charset_1_pos=$((charset_1_pos / 2))
      charset_2_pos=$((charset_2_pos / 2))
      charset_3_pos=$((charset_3_pos / 2))
      charset_4_pos=$((charset_4_pos / 2))

      i=1

      while read -r -u 9 hash; do

        pass=$(sed -n ${i}p "${OUTD}/${hash_type}_passwords.txt")

        # charset 1
        char=$(echo "${pass}" | cut -b ${charset_1_pos})
        charset_1=$(printf "%s\n%s\n" "${charset_1}" "${char}")

        # charset 2
        char=$(echo "${pass}" | cut -b ${charset_2_pos})
        charset_2=$(printf "%s\n%s\n" "${charset_2}" "${char}")

        # charset 3
        char=$(echo "${pass}" | cut -b ${charset_3_pos})
        charset_3=$(printf "%s\n%s\n" "${charset_3}" "${char}")

        # charset 4
        char=$(echo "${pass}" | cut -b ${charset_4_pos})
        charset_4=$(printf "%s\n%s\n" "${charset_4}" "${char}")

        i=$((i + 1))

      done 9< "${OUTD}/${hash_type}_multihash_bruteforce.txt"

      # just make sure that all custom charset fields are initialized

      if [ -z "${charset_1}" ]; then
        charset_1="1"
      fi

      if [ -z "${charset_2}" ]; then
        charset_2="2"
      fi

      if [ -z "${charset_3}" ]; then
        charset_3="3"
      fi

      if [ -z "${charset_4}" ]; then
        charset_4="4"
      fi

      # unique and remove new lines

      charset_1=$(echo "${charset_1}" | sort -u | tr -d '\n')
      charset_2=$(echo "${charset_2}" | sort -u | tr -d '\n')
      charset_3=$(echo "${charset_3}" | sort -u | tr -d '\n')
      charset_4=$(echo "${charset_4}" | sort -u | tr -d '\n')

      custom_charsets="-1 ${charset_1} -2 ${charset_2} -3 ${charset_3} -4 ${charset_4}"
    fi

    if [ "${hash_type}" -eq 16800 ]; then

      mask="?d?d?d?d?d?1?2?3?4"

      charset_1=""
      charset_2=""
      charset_3=""
      charset_4=""

      # check positions (here we assume that mask is always composed of non literal chars
      # i.e. something like ?d?l?u?s?1 is possible, but ?d?dsuffix not
      charset_1_pos=$(expr index "${mask}" 1)
      charset_2_pos=$(expr index "${mask}" 2)
      charset_3_pos=$(expr index "${mask}" 3)
      charset_4_pos=$(expr index "${mask}" 4)

      # divide each charset position by 2 since each of them occupies 2 positions in the mask

      charset_1_pos=$((charset_1_pos / 2))
      charset_2_pos=$((charset_2_pos / 2))
      charset_3_pos=$((charset_3_pos / 2))
      charset_4_pos=$((charset_4_pos / 2))

      i=1

      while read -r -u 9 hash; do

        pass=$(sed -n ${i}p "${OUTD}/${hash_type}_passwords.txt")

        # charset 1
        char=$(echo "${pass}" | cut -b ${charset_1_pos})
        charset_1=$(printf "%s\n%s\n" "${charset_1}" "${char}")

        # charset 2
        char=$(echo "${pass}" | cut -b ${charset_2_pos})
        charset_2=$(printf "%s\n%s\n" "${charset_2}" "${char}")

        # charset 3
        char=$(echo "${pass}" | cut -b ${charset_3_pos})
        charset_3=$(printf "%s\n%s\n" "${charset_3}" "${char}")

        # charset 4
        char=$(echo "${pass}" | cut -b ${charset_4_pos})
        charset_4=$(printf "%s\n%s\n" "${charset_4}" "${char}")

        i=$((i + 1))

      done 9< "${OUTD}/${hash_type}_multihash_bruteforce.txt"

      # just make sure that all custom charset fields are initialized

      if [ -z "${charset_1}" ]; then
        charset_1="1"
      fi

      if [ -z "${charset_2}" ]; then
        charset_2="2"
      fi

      if [ -z "${charset_3}" ]; then
        charset_3="3"
      fi

      if [ -z "${charset_4}" ]; then
        charset_4="4"
      fi

      # unique and remove new lines

      charset_1=$(echo "${charset_1}" | sort -u | tr -d '\n')
      charset_2=$(echo "${charset_2}" | sort -u | tr -d '\n')
      charset_3=$(echo "${charset_3}" | sort -u | tr -d '\n')
      charset_4=$(echo "${charset_4}" | sort -u | tr -d '\n')

      custom_charsets="-1 ${charset_1} -2 ${charset_2} -3 ${charset_3} -4 ${charset_4}"
    fi

    if [ "${hash_type}" -eq 22000 ]; then

      mask="?d?d?d?d?d?1?2?3?4"

      charset_1=""
      charset_2=""
      charset_3=""
      charset_4=""

      # check positions (here we assume that mask is always composed of non literal chars
      # i.e. something like ?d?l?u?s?1 is possible, but ?d?dsuffix not
      charset_1_pos=$(expr index "${mask}" 1)
      charset_2_pos=$(expr index "${mask}" 2)
      charset_3_pos=$(expr index "${mask}" 3)
      charset_4_pos=$(expr index "${mask}" 4)

      # divide each charset position by 2 since each of them occupies 2 positions in the mask

      charset_1_pos=$((charset_1_pos / 2))
      charset_2_pos=$((charset_2_pos / 2))
      charset_3_pos=$((charset_3_pos / 2))
      charset_4_pos=$((charset_4_pos / 2))

      i=1

      while read -r -u 9 hash; do

        pass=$(sed -n ${i}p "${OUTD}/${hash_type}_passwords.txt")

        # charset 1
        char=$(echo "${pass}" | cut -b ${charset_1_pos})
        charset_1=$(printf "%s\n%s\n" "${charset_1}" "${char}")

        # charset 2
        char=$(echo "${pass}" | cut -b ${charset_2_pos})
        charset_2=$(printf "%s\n%s\n" "${charset_2}" "${char}")

        # charset 3
        char=$(echo "${pass}" | cut -b ${charset_3_pos})
        charset_3=$(printf "%s\n%s\n" "${charset_3}" "${char}")

        # charset 4
        char=$(echo "${pass}" | cut -b ${charset_4_pos})
        charset_4=$(printf "%s\n%s\n" "${charset_4}" "${char}")

        i=$((i + 1))

      done 9< "${OUTD}/${hash_type}_multihash_bruteforce.txt"

      # just make sure that all custom charset fields are initialized

      if [ -z "${charset_1}" ]; then
        charset_1="1"
      fi

      if [ -z "${charset_2}" ]; then
        charset_2="2"
      fi

      if [ -z "${charset_3}" ]; then
        charset_3="3"
      fi

      if [ -z "${charset_4}" ]; then
        charset_4="4"
      fi

      # unique and remove new lines

      charset_1=$(echo "${charset_1}" | sort -u | tr -d '\n')
      charset_2=$(echo "${charset_2}" | sort -u | tr -d '\n')
      charset_3=$(echo "${charset_3}" | sort -u | tr -d '\n')
      charset_4=$(echo "${charset_4}" | sort -u | tr -d '\n')

      custom_charsets="-1 ${charset_1} -2 ${charset_2} -3 ${charset_3} -4 ${charset_4}"
    fi

    increment_charset_opts=""

    if [ ${need_hcmask} -eq 0 ]; then # the "normal" case without .hcmask file
      increment_charset_opts="--increment --increment-min ${increment_min} --increment-max ${increment_max}"

      if [ -n "${custom_charsets}" ]; then
        increment_charset_opts="${increment_charset_opts} ${custom_charsets}"
      fi
    fi

    CMD="./${BIN} ${OPTS} -a 3 -m ${hash_type} ${increment_charset_opts} ${hash_file} ${mask} "

    echo "> Testing hash type $hash_type with attack mode 3, markov ${MARKOV}, multi hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt"  2>> "${OUTD}/logfull.txt"

    output=$(./${BIN} ${OPTS} -a 3 -m ${hash_type} ${increment_charset_opts} ${hash_file} ${mask} 2>&1)

    ret=${?}

    echo "${output}" >> "${OUTD}/logfull.txt"

    if [ "${ret}" -eq 0 ]; then

      i=1

      while read -r -u 9 hash; do
        line_nr=$((i + cracks_offset))

        pass=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_passwords.txt")

        if [ "${pass_only}" -eq 1 ]; then
          search=":${pass}"
        else
          search="${hash}:${pass}"
        fi

        echo "${output}" | grep -F "${search}" &>/dev/null

        newRet=$?

        if [ "${newRet}" -ne 0 ]; then
          if [ "${newRet}" -eq 2 ]; then
            ret=20
          else
            ret=10
          fi

          break
        fi

        i=$((i + 1))

      done 9< "${OUTD}/${hash_type}_multihash_bruteforce.txt"

    fi

    status ${ret}

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 3, Mode multi,  Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

function attack_6()
{
  file_only=0

  if is_in_array "${hash_type}" ${FILE_BASED_ALGOS}; then
    file_only=1
  fi

  # single hash
  if [ "${MODE}" -ne 1 ]; then

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    echo "> Testing hash type $hash_type with attack mode 6, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    min=1
    max=8
    mask_offset=0

    if   [ "${hash_type}" -eq  2500 ]; then
      max=6
    elif [ "${hash_type}" -eq 14000 ]; then
      min=0
      max=1
      mask_offset=4
    elif [ "${hash_type}" -eq 14100 ]; then
      min=0
      max=1
      mask_offset=21
    elif [ "${hash_type}" -eq 14900 ]; then
      min=0
      max=1
      mask_offset=5
    elif [ "${hash_type}" -eq 15400 ]; then
      min=0
      max=1
      mask_offset=29
    elif [ "${hash_type}" -eq 16800 ]; then
      max=6
    elif [ "${hash_type}" -eq 22000 ]; then
      max=6
    fi

    # special case: we need to split the first line

    if [ "${min}" -eq 0 ]; then
      pass_part_1=$(sed -n 1p "${OUTD}/${hash_type}_dict1")
      pass_part_2=$(sed -n 1p "${OUTD}/${hash_type}_dict2")

      pass="${pass_part_1}${pass_part_2}"

      echo -n "${pass}" | cut -b -$((mask_offset + 0))  > "${OUTD}/${hash_type}_dict1_custom"
      echo -n "${pass}" | cut -b  $((mask_offset + 1))- > "${OUTD}/${hash_type}_dict2_custom"

      mask_custom=""

      for i in $(seq 1 $((${#pass} - mask_offset))); do

        if   [ "${hash_type}" -eq 14000 ]; then
          char=$(echo -n "${pass}" | cut -b $((i + mask_offset)))
          mask_custom="${mask_custom}${char}"
        elif [ "${hash_type}" -eq 14100 ]; then
          char=$(echo -n "${pass}" | cut -b $((i + mask_offset)))
          mask_custom="${mask_custom}${char}"
        else
          mask_custom="${mask_custom}?d"
        fi

      done
    fi

    i=1

    while read -r -u 9 hash; do

      if [ "${i}" -gt 6 ]; then
        if is_in_array "${hash_type}" ${TIMEOUT_ALGOS}; then
          break
        fi
      fi

      if [ ${i} -gt ${min} ]; then

        if [ "${file_only}" -eq 1 ]; then

          temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"

          if [ "${hash_type}" -ne 22000 ]; then
            echo "${hash}" | base64 -d > "${temp_file}"
          else
            echo "${hash}" > "${temp_file}"
          fi

          hash="${temp_file}"

        fi

        dict1=${OUTD}/${hash_type}_dict1
        dict2=${OUTD}/${hash_type}_dict2

        dict1_a6=${OUTD}/${hash_type}_dict1_a6

        cp "${dict1}" "${dict1_a6}"

        pass=$(sed -n ${i}p "${OUTD}/${hash_type}_passwords.txt")

        if [ "${hash_type}" -eq 20510 ]; then # special case for PKZIP Master Key
          pass=$(echo "${pass}" | cut -b 7-) # skip the first 6 chars
        fi

        if [ ${#pass} -le ${i} ]; then
          i=$((i + 1))
          continue
        fi

        echo "${pass}" | cut -b -$((${#pass} - i)) >> "${dict1_a6}"

        # the block below is just a fancy way to do a "shuf" (or sort -R) because macOS doesn't really support it natively
        # we do not really need a shuf, but it's actually better for testing purposes

        rm -f "${dict1_a6}.txt" # temporary file

        line_num=$(wc -l "${dict1_a6}" | sed -E 's/ *([0-9]+) .*$/\1/')

        sorted_lines=$(seq 1 "${line_num}")

        for lines in $(seq 1 "${line_num}"); do

          random_num=$((RANDOM % line_num))
          random_num=$((random_num + 1)) # sed -n [n]p starts counting with 1 (not 0)

          random_line=$(echo -n "${sorted_lines}" | sed -n ${random_num}p)

          sed -n ${random_line}p "${dict1_a6}" >> "${dict1_a6}.txt"

          # update the temp list of lines

          sorted_lines=$(echo -n "${sorted_lines}" | grep -v "^${random_line}$")

          line_num=$((line_num - 1))

        done

        mv "${dict1_a6}.txt" "${dict1_a6}"

        # end of shuf/sort -R

        mask=""

        for j in $(seq 1 ${i}); do
          mask="${mask}?d"
        done

        CMD="./${BIN} ${OPTS} -a 6 -m ${hash_type} '${hash}' ${dict1_a6} ${mask}"

        echo -n "[ len $i ] " >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        output=$(./${BIN} ${OPTS} -a 6 -m ${hash_type} "${hash}" ${dict1_a6} ${mask} 2>&1)

        ret=${?}

        echo "${output}" >> "${OUTD}/logfull.txt"

        if [ "${ret}" -eq 0 ]; then

          line_nr=1

          if [ "${i}" -gt 1 ]; then
            line_nr=$((i - 1))
          fi

          line_dict1=$(sed -n ${line_nr}p "${dict1}")
          line_dict2=$(sed -n ${line_nr}p "${dict2}")

          if [ "${pass_only}" -eq 1 ]; then
            search=":${line_dict1}${line_dict2}"
          else
            search="${hash}:${line_dict1}${line_dict2}"
          fi

          echo "${output}" | grep -F "${search}" &>/dev/null

          newRet=$?

          if [ "${newRet}" -eq 2 ]; then
            # out-of-memory, workaround

            echo "${output}" | head -1 > tmp_file_out
            echo "${search}" > tmp_file_search

            out_md5=$(md5sum tmp_file_out | cut -d' ' -f1)
            search_md5=$(md5sum tmp_file_search | cut -d' ' -f1)

            rm tmp_file_out tmp_file_search

            if [ "${out_md5}" == "${search_md5}" ]; then
              newRet=0
            fi
          fi

          if [ "${newRet}" -ne 0 ]; then
            if [ "${newRet}" -eq 2 ]; then
              ret=20
            else
              ret=10
            fi
          fi

        fi

        status ${ret}
      fi

      if [ "${i}" -eq ${max} ]; then break; fi

      i=$((i + 1))

    done 9< "${OUTD}/${hash_type}_hashes.txt"

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 6, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"

    rm -f "${OUTD}/${hash_type}_dict1_custom"
    rm -f "${OUTD}/${hash_type}_dict2_custom"
  fi

  # multihash
  if [ "${MODE}" -ne 0 ]; then

    # no multi hash checks for these modes (because we only have 1 hash for each of them)

    if   [ "${hash_type}" -eq 14000 ]; then
      return
    elif [ "${hash_type}" -eq 14100 ]; then
      return
    elif [ "${hash_type}" -eq 14900 ]; then
      return
    elif [ "${hash_type}" -eq 15400 ]; then
      return
    fi

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    max=9

    if   [ "${hash_type}" -eq  2500 ]; then
      max=5
    elif [ "${hash_type}" -eq  3000 ]; then
      max=8
    elif [ "${hash_type}" -eq  7700 ] || [ "${hash_type}" -eq  7701 ]; then
      max=8
    elif [ "${hash_type}" -eq  8500 ]; then
      max=8
    elif [ "${hash_type}" -eq 16800 ]; then
      max=5
    elif [ "${hash_type}" -eq 22000 ]; then
      max=5
    fi

    if is_in_array "${hash_type}" ${TIMEOUT_ALGOS}; then
      max=5
      if [ "${hash_type}" -eq 3200 ]; then
        max=3
      fi
    fi

    i=2
    while [ "$i" -lt "$max" ]; do

      hash_file=${OUTD}/${hash_type}_hashes_multi_${i}.txt

      # if file_only -> decode all base64 "hashes" and put them in the temporary file

      if [ "${file_only}" -eq 1 ]; then

        temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"
        rm -f "${temp_file}"

        hash_file=${temp_file}

        while read -r file_only_hash; do

          if [ "${hash_type}" -ne 22000 ]; then
            echo -n "${file_only_hash}" | base64 -d >> "${temp_file}"
          else
            echo "${file_only_hash}" >> "${temp_file}"
          fi

        done < "${OUTD}/${hash_type}_hashes_multi_${i}.txt"

      fi

      mask=${mask_6[$i]}

      CMD="./${BIN} ${OPTS} -a 6 -m ${hash_type} ${hash_file} ${OUTD}/${hash_type}_dict1_multi_${i} ${mask}"

      echo "> Testing hash type $hash_type with attack mode 6, markov ${MARKOV}, multi hash with word len ${i}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

      output=$(./${BIN} ${OPTS} -a 6 -m ${hash_type} ${hash_file} ${OUTD}/${hash_type}_dict1_multi_${i} ${mask} 2>&1)

      ret=${?}

      echo "${output}" >> "${OUTD}/logfull.txt"

      if [ "${ret}" -eq 0 ]; then

        j=1

        while read -r -u 9 hash; do

          line_dict1=$(sed -n ${j}p "${OUTD}/${hash_type}_dict1_multi_${i}")
          line_dict2=$(sed -n ${j}p "${OUTD}/${hash_type}_dict2_multi_${i}")

          if [ "${pass_only}" -eq 1 ]; then
            search=":${line_dict1}${line_dict2}"
          else
            search="${hash}:${line_dict1}${line_dict2}"
          fi

          echo "${output}" | grep -F "${search}" &>/dev/null

          newRet=$?

          if [ "${newRet}" -ne 0 ]; then
            if [ "${newRet}" -eq 2 ]; then
              ret=20
            else
              ret=10
            fi

            break
          fi

          j=$((j + 1))

        done 9< "${OUTD}/${hash_type}_hashes_multi_${i}.txt"
      fi

      status ${ret}
      i=$((i + 1))

    done

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 6, Mode multi,  Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

function attack_7()
{
  file_only=0

  if is_in_array "${hash_type}" ${FILE_BASED_ALGOS}; then
    file_only=1
  fi

  # single hash
  if [ "${MODE}" -ne 1 ]; then

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    echo "> Testing hash type $hash_type with attack mode 7, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    min=1
    max=8

    mask_offset=0

    if   [ "${hash_type}" -eq  2500 ]; then
      max=5
    elif [ "${hash_type}" -eq 14000 ]; then
      mask_offset=4
      min=0
      max=1
    elif [ "${hash_type}" -eq 14100 ]; then
      mask_offset=3
      min=0
      max=1
    elif [ "${hash_type}" -eq 14900 ]; then
      mask_offset=5
      min=0
      max=1
    elif [ "${hash_type}" -eq 15400 ]; then
      mask_offset=3
      min=0
      max=1
    elif [ "${hash_type}" -eq 16800 ]; then
      max=5
    elif [ "${hash_type}" -eq 22000 ]; then
      max=5
    fi

    # special case: we need to split the first line

    if [ "${min}" -eq 0 ]; then

      pass_part_1=$(sed -n 1p "${OUTD}/${hash_type}_dict1")
      pass_part_2=$(sed -n 1p "${OUTD}/${hash_type}_dict2")

      pass="${pass_part_1}${pass_part_2}"

      echo -n "${pass}" | cut -b -$((mask_offset + 0))  > "${OUTD}/${hash_type}_dict1_custom"
      echo -n "${pass}" | cut -b  $((mask_offset + 1))- > "${OUTD}/${hash_type}_dict2_custom"

      mask_custom=""

      for i in $(seq 1 ${mask_offset}); do

        if   [ "${hash_type}" -eq 14000 ]; then
          char=$(echo -n "${pass}" | cut -b ${i})
          mask_custom="${mask_custom}${char}"
        elif [ "${hash_type}" -eq 14100 ]; then
          char=$(echo -n "${pass}" | cut -b ${i})
          mask_custom="${mask_custom}${char}"
        else
          mask_custom="${mask_custom}?d"
        fi

      done

    fi

    i=1

    while read -r -u 9 hash; do

      if [ ${i} -gt ${min} ]; then

        if [ "${file_only}" -eq 1 ]; then

          temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"

          if [ "${hash_type}" -ne 22000 ]; then
            echo "${hash}" | base64 -d > "${temp_file}"
          else
            echo "${hash}" > "${temp_file}"
          fi

          hash="${temp_file}"

        fi

        mask=${mask_7[$i]}

        # adjust mask if needed

        line_nr=1

        if [ "${i}" -gt 1 ]; then
          line_nr=$((i - 1))
        fi

        if [ "${hash_type}" -eq 2500 ]; then

          pass_part_1=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict1")
          pass_part_2=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict2")

          pass_part_2_len=${#pass_part_2}

          pass=${pass_part_1}${pass_part_2}

          pass_len=${#pass}

          # add first x chars of password to mask and append the (old) mask

          mask_len=${#mask}
          mask_len=$((mask_len / 2))

          mask_prefix=$(echo ${pass} | cut -b -$((pass_len - mask_len - pass_part_2_len)))
          mask=${mask_prefix}${mask}

        fi

        if [ "${hash_type}" -eq 16800 ]; then

          pass_part_1=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict1")
          pass_part_2=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict2")

          pass_part_2_len=${#pass_part_2}

          pass=${pass_part_1}${pass_part_2}
          pass_len=${#pass}

          # add first x chars of password to mask and append the (old) mask

          mask_len=${#mask}
          mask_len=$((mask_len / 2))

          mask_prefix=$(echo "${pass}" | cut -b -$((pass_len - mask_len - pass_part_2_len)))
          mask=${mask_prefix}${mask}

        fi

        if [ "${hash_type}" -eq 22000 ]; then

          pass_part_1=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict1")
          pass_part_2=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict2")

          pass_part_2_len=${#pass_part_2}

          pass=${pass_part_1}${pass_part_2}
          pass_len=${#pass}

          # add first x chars of password to mask and append the (old) mask

          mask_len=${#mask}
          mask_len=$((mask_len / 2))

          mask_prefix=$(echo "${pass}" | cut -b -$((pass_len - mask_len - pass_part_2_len)))
          mask=${mask_prefix}${mask}

        fi

        if [ "${hash_type}" -eq 20510 ]; then

          pass_part_1=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict1")
          pass_part_2=$(sed -n ${line_nr}p "${OUTD}/${hash_type}_dict2")

          pass=${pass_part_1}${pass_part_2}

          pass_len=${#pass}

          if [ "${pass_len}" -le 6 ]; then
            i=$((i + 1))
            continue
          fi

          pass_old=${pass}

          pass=$(echo "${pass}" | cut -b 7-) # skip the first 6 chars

          mask_len=$((${#mask} / 2))

          echo "${pass_old}" | cut -b -$((6 + mask_len)) > "${OUTD}/${hash_type}_dict1_custom"
          echo "${pass}"     | cut -b $((mask_len + 1))- > "${OUTD}/${hash_type}_dict2_custom"

          min=0 # hack to use the custom dict
          mask_custom=${mask}

        fi

        dict1=${OUTD}/${hash_type}_dict1
        dict2=${OUTD}/${hash_type}_dict2

        if [ "${min}" -eq 0 ]; then
          mask=${mask_custom}

          dict1=${OUTD}/${hash_type}_dict1_custom
          dict2=${OUTD}/${hash_type}_dict2_custom
        fi

        CMD="./${BIN} ${OPTS} -a 7 -m ${hash_type} '${hash}' ${mask} ${dict2}"

        echo -n "[ len $i ] " >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

        output=$(./${BIN} ${OPTS} -a 7 -m ${hash_type} "${hash}" ${mask} ${dict2} 2>&1)

        ret=${?}

        echo "${output}" >> "${OUTD}/logfull.txt"

        if [ "${ret}" -eq 0 ]; then

          line_nr=1

          if [ "${i}" -gt 1 ]; then
            line_nr=$((i - 1))
          fi

          line_dict1=$(sed -n ${line_nr}p "${dict1}")
          line_dict2=$(sed -n ${line_nr}p "${dict2}")

          if [ "${pass_only}" -eq 1 ]; then
            search=":${line_dict1}${line_dict2}"
          else
            search="${hash}:${line_dict1}${line_dict2}"
          fi

          echo "${output}" | grep -F "${search}" &>/dev/null

          newRet=$?

          if [ "${newRet}" -eq 2 ]; then

            # out-of-memory, workaround

            echo "${output}" | head -1 > tmp_file_out
            echo "${search}" > tmp_file_search

            out_md5=$(md5sum tmp_file_out | cut -d' ' -f1)
            search_md5=$(md5sum tmp_file_search | cut -d' ' -f1)

            rm tmp_file_out tmp_file_search

            if [ "${out_md5}" == "${search_md5}" ]; then
              newRet=0
            fi
          fi

          if [ "${newRet}" -ne 0 ]; then

            if [ "${newRet}" -eq 2 ]; then
              ret=20
            else
              ret=10
            fi
          fi

        fi

        status ${ret}
      fi

      if [ $i -eq ${max} ]; then break; fi

      i=$((i + 1))

    done 9< "${OUTD}/${hash_type}_hashes.txt"

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 7, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"

    rm -f "${OUTD}/${hash_type}_dict1_custom"
    rm -f "${OUTD}/${hash_type}_dict2_custom"
  fi

  # multihash
  if [ "${MODE}" -ne 0 ]; then

    # no multi hash checks for these modes (because we only have 1 hash for each of them)

    if   [ "${hash_type}" -eq 14000 ]; then
      return
    elif [ "${hash_type}" -eq 14100 ]; then
      return
    elif [ "${hash_type}" -eq 14900 ]; then
      return
    elif [ "${hash_type}" -eq 15400 ]; then
      return
    fi

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    max=9

    if   [ "${hash_type}" -eq  2500 ]; then
      max=5
    elif [ "${hash_type}" -eq  3000 ]; then
      max=8
    elif [ "${hash_type}" -eq  7700 ] || [ "${hash_type}" -eq  7701 ]; then
      max=8
    elif [ "${hash_type}" -eq  8500 ]; then
      max=8
    elif [ "${hash_type}" -eq 14000 ]; then
      max=5
    elif [ "${hash_type}" -eq 14100 ]; then
      max=5
    elif [ "${hash_type}" -eq 14900 ]; then
      max=5
    elif [ "${hash_type}" -eq 15400 ]; then
      max=5
    elif [ "${hash_type}" -eq 16800 ]; then
      max=5
    elif [ "${hash_type}" -eq 22000 ]; then
      max=5
    fi

    if is_in_array "${hash_type}" ${TIMEOUT_ALGOS}; then
      max=7
      if [ "${hash_type}" -eq 3200 ]; then
        max=4
      fi
    fi

    i=2
    while [ "$i" -lt "$max" ]; do

      hash_file=${OUTD}/${hash_type}_hashes_multi_${i}.txt
      dict_file=${OUTD}/${hash_type}_dict2_multi_${i}

      mask=${mask_7[$i]}

      # if file_only -> decode all base64 "hashes" and put them in the temporary file

      if [ "${file_only}" -eq 1 ]; then

        temp_file="${OUTD}/${hash_type}_filebased_only_temp.txt"
        rm -f "${temp_file}"

        hash_file=${temp_file}

        while read -r file_only_hash; do

          if [ "${hash_type}" -ne 22000 ]; then
            echo -n "${file_only_hash}" | base64 -d >> "${temp_file}"
          else
            echo "${file_only_hash}" >> "${temp_file}"
          fi

        done < "${OUTD}/${hash_type}_hashes_multi_${i}.txt"

        # a little hack: since we don't want to have a very large mask (and wpa has minimum length of 8),
        # we need to create a temporary dict file on-the-fly and use it like this: [small mask] [long(er) words in dict]

        dict_file=${OUTD}/${hash_type}_dict2_multi_${i}_longer
        rm -f "${dict_file}"

        mask_len=${#mask}
        mask_len=$((mask_len / 2))

        j=1

        while read -r -u 9 hash; do

          pass_part_1=$(sed -n ${j}p "${OUTD}/${hash_type}_dict1_multi_${i}")
          pass_part_2=$(sed -n ${j}p "${OUTD}/${hash_type}_dict2_multi_${i}")

          pass="${pass_part_1}${pass_part_2}"

          pass_suffix=$(echo "${pass}" | cut -b $((mask_len + 1))-)

          echo "${pass_suffix}" >> "${dict_file}"

          j=$((j + 1))

        done 9< "${OUTD}/${hash_type}_hashes_multi_${i}.txt"

      fi

      CMD="./${BIN} ${OPTS} -a 7 -m ${hash_type} ${hash_file} ${mask} ${dict_file}"

      echo "> Testing hash type $hash_type with attack mode 7, markov ${MARKOV}, multi hash with word len ${i}." >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

      output=$(./${BIN} ${OPTS} -a 7 -m ${hash_type} ${hash_file} ${mask} ${dict_file} 2>&1)

      ret=${?}

      echo "${output}" >> "${OUTD}/logfull.txt"

      if [ "${ret}" -eq 0 ]; then

        j=1

        while read -r -u 9 hash; do

          line_dict1=$(sed -n ${j}p "${OUTD}/${hash_type}_dict1_multi_${i}")
          line_dict2=$(sed -n ${j}p "${OUTD}/${hash_type}_dict2_multi_${i}")

          if [ "${pass_only}" -eq 1 ]; then
            search=":${line_dict1}${line_dict2}"
          else
            search="${hash}:${line_dict1}${line_dict2}"
          fi

          echo "${output}" | grep -F "${search}" &>/dev/null

          newRet=$?

          if [ "${newRet}" -ne 0 ]; then
            if [ "${newRet}" -eq 2 ]; then
              ret=20
            else
              ret=10
            fi

            break
          fi

          j=$((j + 1))

        done 9< "${OUTD}/${hash_type}_hashes_multi_${i}.txt"
      fi

      status ${ret}
      i=$((i + 1))

    done

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 7, Mode multi,  Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

function cryptoloop_test()
{
  hashType=$1
  keySize=$2
  CMD="unset"

  mkdir -p ${OUTD}/cl_tests
  chmod u+x "${TDIR}/cryptoloop2hashcat.py"

  case $hashType in

    14511)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha1_aes_${keySize}.img\" --hash sha1 --cipher aes --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha1_aes_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha1_aes_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14512)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha1_serpent_${keySize}.img\" --hash sha1 --cipher serpent --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha1_serpent_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha1_serpent_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14513)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha1_twofish_${keySize}.img\" --hash sha1 --cipher twofish --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha1_twofish_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha1_twofish_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14521)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha256_aes_${keySize}.img\" --hash sha256 --cipher aes --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha256_aes_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha256_aes_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14522)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha256_serpent_${keySize}.img\" --hash sha256 --cipher serpent --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha256_serpent_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha256_serpent_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14523)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha256_twofish_${keySize}.img\" --hash sha256 --cipher twofish --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha256_twofish_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha256_twofish_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14531)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha512_aes_${keySize}.img\" --hash sha512 --cipher aes --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha512_aes_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha512_aes_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14532)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha512_serpent_${keySize}.img\" --hash sha512 --cipher serpent --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha512_serpent_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha512_serpent_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14533)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_sha512_twofish_${keySize}.img\" --hash sha512 --cipher twofish --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_sha512_twofish_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_sha512_twofish_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14541)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_ripemd160_aes_${keySize}.img\" --hash ripemd160 --cipher aes --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_ripemd160_aes_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_ripemd160_aes_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14542)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_ripemd160_serpent_${keySize}.img\" --hash ripemd160 --cipher serpent --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_ripemd160_serpent_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_ripemd160_serpent_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14543)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_ripemd160_twofish_${keySize}.img\" --hash ripemd160 --cipher twofish --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_ripemd160_twofish_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_ripemd160_twofish_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14551)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_whirlpool_aes_${keySize}.img\" --hash whirlpool --cipher aes --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_whirlpool_aes_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_whirlpool_aes_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14552)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py\" --source \"${TDIR}/cl_tests/hashcat_whirlpool_serpent_${keySize}.img\" --hash whirlpool --cipher serpent --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_whirlpool_serpent_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_whirlpool_serpent_${keySize}.hash hashca?l"
          ;;
      esac
      ;;

    14553)
      case $keySize in
        128|192|256)
          eval \"${TDIR}/cryptoloop2hashcat.py --source ${TDIR}/cl_tests/hashcat_whirlpool_twofish_${keySize}.img\" --hash whirlpool --cipher twofish --keysize ${keySize} > ${OUTD}/cl_tests/hashcat_whirlpool_twofish_${keySize}.hash
          CMD="./${BIN} ${OPTS} -a 3 -m 14500 ${OUTD}/cl_tests/hashcat_whirlpool_twofish_${keySize}.hash hashca?l"
          ;;
      esac
      ;;
  esac

  if [ ${#CMD} -gt 5 ]; then
    echo "> Testing hash type $hashType with attack mode 3, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, Key-Size ${keySize}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    output=$(${CMD} 2>&1)

    ret=${?}

    echo "${output}" >> "${OUTD}/logfull.txt"

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    status ${ret}

    cnt=1

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 3, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, Key-Size ${keySize} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

function truecrypt_test()
{
  hashType=$1
  tcMode=$2
  CMD="unset"

  case $hashType in

    6211)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6211 '${TDIR}/tc_tests/hashcat_ripemd160_aes.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6211 '${TDIR}/tc_tests/hashcat_ripemd160_serpent.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6211 '${TDIR}/tc_tests/hashcat_ripemd160_twofish.tc' hashca?l"
          ;;
      esac
      ;;

    6212)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6212 '${TDIR}/tc_tests/hashcat_ripemd160_aes-twofish.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6212 '${TDIR}/tc_tests/hashcat_ripemd160_serpent-aes.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6212 '${TDIR}/tc_tests/hashcat_ripemd160_twofish-serpent.tc' hashca?l"
          ;;
      esac
      ;;

    6213)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6213 '${TDIR}/tc_tests/hashcat_ripemd160_aes-twofish-serpent.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6213 '${TDIR}/tc_tests/hashcat_ripemd160_serpent-twofish-aes.tc' hashca?l"
          ;;
      esac
      ;;

    6221)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6221 '${TDIR}/tc_tests/hashcat_sha512_aes.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6221 '${TDIR}/tc_tests/hashcat_sha512_serpent.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6221 '${TDIR}/tc_tests/hashcat_sha512_twofish.tc' hashca?l"
          ;;
      esac
      ;;

    6222)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6222 '${TDIR}/tc_tests/hashcat_sha512_aes-twofish.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6222 '${TDIR}/tc_tests/hashcat_sha512_serpent-aes.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6222 '${TDIR}/tc_tests/hashcat_sha512_twofish-serpent.tc' hashca?l"
          ;;
      esac
      ;;

    6223)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6223 '${TDIR}/tc_tests/hashcat_sha512_aes-twofish-serpent.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6223 '${TDIR}/tc_tests/hashcat_sha512_serpent-twofish-aes.tc' hashca?l"
          ;;
      esac
      ;;

    6231)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6231 '${TDIR}/tc_tests/hashcat_whirlpool_aes.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6231 '${TDIR}/tc_tests/hashcat_whirlpool_serpent.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6231 '${TDIR}/tc_tests/hashcat_whirlpool_twofish.tc' hashca?l"
          ;;
      esac
      ;;

    6232)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6232 '${TDIR}/tc_tests/hashcat_whirlpool_aes-twofish.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6232 '${TDIR}/tc_tests/hashcat_whirlpool_serpent-aes.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6232 '${TDIR}/tc_tests/hashcat_whirlpool_twofish-serpent.tc' hashca?l"
          ;;
      esac
      ;;

    6233)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6233 '${TDIR}/tc_tests/hashcat_whirlpool_aes-twofish-serpent.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6233 '${TDIR}/tc_tests/hashcat_whirlpool_serpent-twofish-aes.tc' hashca?l"
          ;;
      esac
      ;;

    6241)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6241 '${TDIR}/tc_tests/hashcat_ripemd160_aes_boot.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6241 '${TDIR}/tc_tests/hashcat_ripemd160_serpent_boot.tc' hashca?l"
          ;;
        2)
          CMD="./${BIN} ${OPTS} -a 3 -m 6241 '${TDIR}/tc_tests/hashcat_ripemd160_twofish_boot.tc' hashca?l"
          ;;
      esac
      ;;

    6242)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6242 '${TDIR}/tc_tests/hashcat_ripemd160_aes-twofish_boot.tc' hashca?l"
          ;;
        1)
          CMD="./${BIN} ${OPTS} -a 3 -m 6242 '${TDIR}/tc_tests/hashcat_ripemd160_serpent-aes_boot.tc' hashca?l"
          ;;
      esac
      ;;

    6243)
      case $tcMode in
        0)
          CMD="./${BIN} ${OPTS} -a 3 -m 6243 '${TDIR}/tc_tests/hashcat_ripemd160_aes-twofish-serpent_boot.tc' hashca?l"
          ;;
      esac
      ;;
  esac

  if [ ${#CMD} -gt 5 ]; then
    echo "> Testing hash type $hashType with attack mode 3, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, tcMode ${tcMode}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

    output=$(eval ${CMD} 2>&1)

    ret=${?}

    echo "${output}" >> "${OUTD}/logfull.txt"

    e_ce=0
    e_rs=0
    e_to=0
    e_nf=0
    e_nm=0
    cnt=0

    status ${ret}

    cnt=1

    msg="OK"

    if [ "${e_ce}" -ne 0 ]; then
      msg="Compare Error"
    elif [ "${e_rs}" -ne 0 ]; then
      msg="Skip"
    elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
      msg="Error"
    elif [ "${e_to}" -ne 0 ]; then
      msg="Warning"
    fi

    echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 3, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, tcMode ${tcMode} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
  fi
}

# Compose and execute hashcat command on a VeraCrypt test container
# Must not be called for hash types other than 137XY
# $1: cipher variation, can be 0-6
function veracrypt_test()
{
  cipher_variation=$1

  hash_function=""

  hash_digit="${hash_type:3:1}"
  [ "$hash_digit" -eq "1" ] && hash_function="ripemd160"
  [ "$hash_digit" -eq "2" ] && hash_function="sha512"
  [ "$hash_digit" -eq "3" ] && hash_function="whirlpool"
  [ "$hash_digit" -eq "5" ] && hash_function="sha256"
  [ "$hash_digit" -eq "7" ] && hash_function="streebog"

  [ -n "$hash_function" ] || return

  cipher_cascade=""

  cipher_digit="${hash_type:4:1}"
  case $cipher_digit in
    1)
      [ "$cipher_variation" -eq "0" ] && cipher_cascade="aes"
      [ "$cipher_variation" -eq "1" ] && cipher_cascade="serpent"
      [ "$cipher_variation" -eq "2" ] && cipher_cascade="twofish"
      [ "$cipher_variation" -eq "3" ] && cipher_cascade="camellia"
      [ "$cipher_variation" -eq "5" ] && cipher_cascade="kuznyechik"
      ;;
    2)
      [ "$cipher_variation" -eq "0" ] && cipher_cascade="aes-twofish"
      [ "$cipher_variation" -eq "1" ] && cipher_cascade="serpent-aes"
      [ "$cipher_variation" -eq "2" ] && cipher_cascade="twofish-serpent"
      [ "$cipher_variation" -eq "3" ] && cipher_cascade="camellia-kuznyechik"
      [ "$cipher_variation" -eq "4" ] && cipher_cascade="camellia-serpent"
      [ "$cipher_variation" -eq "5" ] && cipher_cascade="kuznyechik-aes"
      [ "$cipher_variation" -eq "6" ] && cipher_cascade="kuznyechik-twofish"
      ;;
    3)
      [ "$cipher_variation" -eq "0" ] && cipher_cascade="aes-twofish-serpent"
      [ "$cipher_variation" -eq "1" ] && cipher_cascade="serpent-twofish-aes"
      [ "$cipher_variation" -eq "5" ] && cipher_cascade="kuznyechik-serpent-camellia"
      ;;
  esac

  [ -n "$cipher_cascade" ] || return

  filename="${TDIR}/vc_tests/hashcat_${hash_function}_${cipher_cascade}.vc"

  # The hash-cipher combination might be invalid (e.g. RIPEMD-160 + Kuznyechik)
  [ -f "${filename}" ] || return

  CMD="./${BIN} ${OPTS} -a 3 -m ${hash_type} '${filename}' hashc?lt"

  echo "> Testing hash type ${hash_type} with attack mode 0, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, Cipher ${cipher_cascade}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

  output=$(eval ${CMD} 2>&1)

  ret=${?}

  echo "${output}" >> "${OUTD}/logfull.txt"

  e_ce=0
  e_rs=0
  e_to=0
  e_nf=0
  e_nm=0
  cnt=0

  status ${ret}

  cnt=1

  msg="OK"

  if [ "${e_ce}" -ne 0 ]; then
    msg="Compare Error"
  elif [ "${e_rs}" -ne 0 ]; then
    msg="Skip"
  elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
    msg="Error"
  elif [ "${e_to}" -ne 0 ]; then
    msg="Warning"
  fi

  echo "[ ${OUTD} ] [ Type ${hash_type}, Attack 0, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, Cipher ${cipher_cascade} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"
}

function luks_test()
{
  hashType=$1
  attackType=$2

  # if -m all was set let us default to -a 3 only. You could specify the attack type directly, e.g. -m 0
  # the problem with defaulting to all=0,1,3,6,7 is that it could take way too long

  if [ "${attackType}" -eq 65535 ]; then
    attackType=3
  fi

  #LUKS_HASHES="sha1 sha256 sha512 ripemd160 whirlpool"
  LUKS_HASHES="sha1 sha256 sha512 ripemd160"
  LUKS_CIPHERS="aes serpent twofish"
  LUKS_MODES="cbc-essiv cbc-plain64 xts-plain64"
  LUKS_KEYSIZES="128 256 512"

  LUKS_PASSWORD=$(cat "${TDIR}/luks_tests/pw" 2>/dev/null)

  for luks_h in ${LUKS_HASHES}; do
    for luks_c in ${LUKS_CIPHERS}; do
      for luks_m in ${LUKS_MODES}; do
        for luks_k in ${LUKS_KEYSIZES}; do

          CMD=""

          # filter out not supported combinations:

          case "${luks_k}" in
            128)
              case "${luks_m}" in
                cbc-essiv|cbc-plain64)
                ;;
                *)
                  continue
                ;;
              esac
            ;;
            256)
              case "${luks_m}" in
                cbc-essiv|cbc-plain64|xts-plain64)
                ;;
                *)
                  continue
                ;;
              esac
            ;;
            512)
              case "${luks_m}" in
                xts-plain64)
                ;;
                *)
                  continue
                ;;
              esac
            ;;
          esac

          luks_mode="${luks_h}-${luks_c}-${luks_m}-${luks_k}"
          luks_file="${TDIR}/luks_tests/hashcat_${luks_h}_${luks_c}_${luks_m}_${luks_k}.luks"
          luks_main_mask="?l"
          luks_mask="${luks_main_mask}"

          # for combination or hybrid attacks
          luks_pass_part_file1="${OUTD}/${hashType}_dict1"
          luks_pass_part_file2="${OUTD}/${hashType}_dict2"

          case $attackType in
            0)
              CMD="./${BIN} ${OPTS} -a 0 -m ${hashType} '${luks_file}' '${TDIR}/luks_tests/pw'"
              ;;
            1)
              luks_pass_part1_len=$((${#LUKS_PASSWORD} / 2))
              luks_pass_part2_start=$((luks_pass_part1_len + 1))

              echo "${LUKS_PASSWORD}" | cut -c-${luks_pass_part1_len} > "${luks_pass_part_file1}" 2>/dev/null
              echo "${LUKS_PASSWORD}" | cut -c${luks_pass_part2_start}- > "${luks_pass_part_file2}" 2>/dev/null

              CMD="./${BIN} ${OPTS} -a 6 -m ${hashType} '${luks_file}' ${luks_pass_part_file1} ${luks_pass_part_file2}"
              ;;
            3)
              luks_mask_fixed_len=$((${#LUKS_PASSWORD} - 1))

              luks_mask="$(echo "${LUKS_PASSWORD}" | cut -c-${luks_mask_fixed_len} 2>/dev/null)"
              luks_mask="${luks_mask}${luks_main_mask}"

              CMD="./${BIN} ${OPTS} -a 3 -m ${hashType} '${luks_file}' ${luks_mask}"
              ;;
            6)
              luks_pass_part1_len=$((${#LUKS_PASSWORD} - 1))

              echo "${LUKS_PASSWORD}" | cut -c-${luks_pass_part1_len} > "${luks_pass_part_file1}" 2>/dev/null

              CMD="./${BIN} ${OPTS} -a 6 -m ${hashType} '${luks_file}' ${luks_pass_part_file1} ${luks_mask}"
              ;;
            7)
              echo "${LUKS_PASSWORD}" | cut -c2- > "${luks_pass_part_file1}" 2>/dev/null

              CMD="./${BIN} ${OPTS} -a 7 -m ${hashType} '${luks_file}' ${luks_mask} ${luks_pass_part_file1}"
              ;;
          esac

          if [ -n "${CMD}" ]; then
            echo "> Testing hash type ${hashType} with attack mode ${attackType}, markov ${MARKOV}, single hash, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, luksMode ${luks_mode}" >> "${OUTD}/logfull.txt" 2>> "${OUTD}/logfull.txt"

            if [ -f "${luks_first_test_file}" ]; then
              output=$(eval ${CMD} 2>&1)
              ret=${?}

              echo "${output}" >> "${OUTD}/logfull.txt"
            else
              ret=30
            fi

            e_ce=0
            e_rs=0
            e_to=0
            e_nf=0
            e_nm=0
            cnt=0

            status ${ret}

            cnt=1

            msg="OK"

            if [ "${e_ce}" -ne 0 ]; then
              msg="Compare Error"
            elif [ "${e_rs}" -ne 0 ]; then
              msg="Skip"
            elif [ "${e_nf}" -ne 0 ] || [ "${e_nm}" -ne 0 ]; then
              msg="Error"
            elif [ "${e_to}" -ne 0 ]; then
              msg="Warning"
            fi

            echo "[ ${OUTD} ] [ Type ${hash_type}, Attack ${attackType}, Mode single, Device-Type ${DEVICE_TYPE}, Kernel-Type ${KERNEL_TYPE}, Vector-Width ${VECTOR}, luksMode ${luks_mode} ] > $msg : ${e_nf}/${cnt} not found, ${e_nm}/${cnt} not matched, ${e_to}/${cnt} timeout, ${e_rs}/${cnt} skipped"

            status ${ret}
          fi
        done
      done
    done
  done
}

function usage()
{
cat << EOF
> Usage : ${0} <options>

OPTIONS:

  -V    Backend vector-width (either 1, 2, 4 or 8), overrides value from device query :
        '1'         => vector-width 1
        '2'         => vector-width 2 (default)
        '4'         => vector-width 4
        '8'         => vector-width 8
        'all'       => test sequentially vector-width ${VECTOR_WIDTHS}

  -t    Select test mode :
        'single'    => single hash (default)
        'multi'     => multi hash
        'all'       => single and multi hash

  -m    Select hash type :
        'all'       => all hash type supported
        (int)       => hash type integer code (default : 0)
        (int)-(int) => hash type integer range

  -a    Select attack mode :
        'all'       => all attack modes
        (int)       => attack mode integer code (default : 0)

  -x    Select cpu architecture :
        '32'        => 32 bit architecture
        '64'        => 64 bit architecture (default)

  -o    Select operating system :
        'win'       => Windows operating system (use .exe file extension)
        'linux'     => Linux operating system (use .bin file extension)
        'macos'     => macOS operating system (use .app file extension)

  -d    Select the Backend device :
        (int)[,int] => comma separated list of devices (default : 1)

  -D    Select the OpenCL device types :
        '1'         => CPU
        '2'         => GPU (default)
        '3'         => FPGA, DSP, Co-Processor
        (int)[,int] => multiple comma separated device types from the list above

  -O    Use optimized kernels (default : -O)

  -P    Use pure kernels instead of optimized kernels (default : -O)

  -s    Use this session name instead of the default one (default : "hashcat")

  -c    Disables markov-chains

  -f    Use --force to ignore hashcat warnings (default : disabled)

  -r    Setup max runtime limit (default: 400)

  -p    Package the tests into a .7z file

  -F    Use this folder as test folder instead of the default one
        (string)    => path to folder

  -I    Use this folder as input/output folder for packaged tests
        (string)    => path to folder

  -h    Show this help

EOF

  exit 1
}

BIN="hashcat"
MARKOV="enabled"
ATTACK=0
MODE=0
DEVICE_TYPE="null"
KERNEL_TYPE="Optimized"
VECTOR="default"
HT=0
PACKAGE=0
OPTIMIZED=1

while getopts "V:t:m:a:b:hcpd:x:o:d:D:F:POI:s:fr:" opt; do

  case ${opt} in
    "V")
      if [ "${OPTARG}" = "1" ]; then
        VECTOR=1
      elif [ "${OPTARG}" = "2" ]; then
        VECTOR=2
      elif [ "${OPTARG}" = "4" ]; then
        VECTOR=4
      elif [ "${OPTARG}" = "8" ]; then
        VECTOR=8
      elif [ "${OPTARG}" = "16" ]; then
        VECTOR=16
      elif [ "${OPTARG}" = "all" ]; then
        VECTOR="all"
      else
        usage
      fi
      ;;

    "t")
      if [ "${OPTARG}" = "single" ]; then
        MODE=0
      elif [ "${OPTARG}" = "multi" ]; then
        MODE=1
      elif [ "${OPTARG}" = "all" ]; then
        MODE=2
      else
        usage
      fi
      ;;

    "m")
      if [ "${OPTARG}" = "all" ]; then
        HT=65535
      else
        HT=${OPTARG}
      fi
      ;;

    "a")
      if [ "${OPTARG}" = "all" ]; then
        ATTACK=65535
      elif [ "${OPTARG}" = "0" ]; then
        ATTACK=0
      elif [ "${OPTARG}" = "1" ]; then
        ATTACK=1
      elif [ "${OPTARG}" = "3" ]; then
        ATTACK=3
      elif [ "${OPTARG}" = "6" ]; then
        ATTACK=6
      elif [ "${OPTARG}" = "7" ]; then
        ATTACK=7
      else
        usage
      fi
      ;;

    "c")
      OPTS="${OPTS} --markov-disable"
      MARKOV="disabled"
      ;;

    "I")
      PACKAGE_FOLDER=$( echo "${OPTARG}" | sed 's!/$!!g' )
      ;;

    "s")
      OPTS="${OPTS} --session \"${OPTARG}\""
      ;;

    "p")
      PACKAGE=1
      ;;

    "x")
      if [ "${OPTARG}" = "32" ]; then
        ARCHITECTURE=32
      elif [ "${OPTARG}" = "64" ]; then
        ARCHITECTURE=64
      else
        usage
      fi
      ;;

    "o")
      if [ "${OPTARG}" = "win" ]; then
        EXTENSION="exe"
      elif [ "${OPTARG}" = "linux" ]; then
        EXTENSION="bin"
      elif [ "${OPTARG}" = "macos" ]; then
        EXTENSION="app"
      else
        usage
      fi
      ;;

    "O")
      # optimized is already default, ignore it
      ;;

    "d")
      OPTS="${OPTS} -d ${OPTARG}"
      ;;

    "D")
      if [ "${OPTARG}" = "1" ]; then
        OPTS="${OPTS} -D 1"
        DEVICE_TYPE="Cpu"
      elif [ "${OPTARG}" = "2" ]; then
        OPTS="${OPTS} -D 2"
        DEVICE_TYPE="Gpu"
      else
        OPTS="${OPTS} -D ${OPTARG}"
        DEVICE_TYPE="Cpu + Gpu"
      fi
      ;;

    "F")
      OUTD=$( echo "${OPTARG}" | sed 's!/$!!g' )
      ;;

    "P")
      OPTIMIZED=0
      KERNEL_TYPE="Pure"
      ;;

    "f")
      FORCE=1
      ;;

    "r")
      RUNTIME=${OPTARG}
      ;;

    \?)
      usage
      ;;

    "h")
      usage
      ;;
  esac

done

# handle Apple Silicon

IS_APPLE_SILICON=0

if [ $(uname) == "Darwin" ]; then
  BIN_sysctl=$(which sysctl)
  if [ $? -eq 0 ]; then
    CPU_TYPE=$(sysctl hw.cputype | awk '{print $2}')

    if [ ${CPU_TYPE} -eq 16777228 ]; then
      IS_APPLE_SILICON=1
    fi
  fi
fi

export IS_OPTIMIZED=${OPTIMIZED}

if [ "${OPTIMIZED}" -eq 1 ]; then
  OPTS="${OPTS} -O"
fi

# set max-runtime

OPTS="${OPTS} --runtime ${RUNTIME}"

# set default device-type to CPU with Apple Intel, else GPU

if [ "${DEVICE_TYPE}" = "null" ]; then
  if [ $(uname) == "Darwin" ] && [ ${IS_APPLE_SILICON} -eq 0 ]; then
    OPTS="${OPTS} -D 1"
    DEVICE_TYPE="Cpu"
  else
    OPTS="${OPTS} -D 2"
    DEVICE_TYPE="Gpu"
  fi
fi

if [ ${FORCE} -eq 1 ]; then
  OPTS="${OPTS} --force"
fi

if [ -n "${ARCHITECTURE}" ]; then
  BIN="${BIN}${ARCHITECTURE}"
fi

if [ -n "${EXTENSION}" ]; then
  BIN="${BIN}.${EXTENSION}"
fi

if [ -n "${PACKAGE_FOLDER}" ]; then
  if [ ! -e "${PACKAGE_FOLDER}" ]; then
    echo "! folder '${PACKAGE_FOLDER}' does not exist"
    exit 1
  fi
fi

if [ "${PACKAGE}" -eq 0 ] || [ -z "${PACKAGE_FOLDER}" ]; then

  # check existence of binary
  if [ ! -e "${BIN}" ]; then
    echo "! ${BIN} not found, please build binary before run test."
    exit 1
  fi

  HT_MIN=0
  HT_MAX=0

  if echo -n "${HT}" | grep -q '^[0-9]\+$'; then
    HT_MIN=${HT}
    HT_MAX=${HT}
  elif echo -n "${HT}" | grep -q '^[0-9]\+-[1-9][0-9]*$'; then
    HT_MIN=$(echo -n ${HT} | sed "s/-.*//")
    HT_MAX=$(echo -n ${HT} | sed "s/.*-//")

    if [ "${HT_MIN}" -gt "${HT_MAX}" ]; then
      echo "! hash type range -m ${HT} is not valid ..."
      usage
    fi
  else
    echo "! hash type is not a number ..."
    usage
  fi

  HT=${HT_MIN}

  # filter by hash_type
  if [ "${HT}" -ne 65535 ]; then

    # validate filter

    if ! is_in_array "${HT_MIN}" ${HASH_TYPES}; then
      echo "! invalid hash type selected ..."
      usage
    fi

    if ! is_in_array "${HT_MAX}" ${HASH_TYPES}; then
      echo "! invalid hash type selected ..."
      usage
    fi
  fi

  if [ -z "${PACKAGE_FOLDER}" ]; then

    # make new dir
    mkdir -p "${OUTD}"

    # generate random test entry
    if [ "${HT}" -eq 65535 ]; then
      for TMP_HT in ${HASH_TYPES}; do
        if [ "${TMP_HT}" -ne ${LUKS_MODE} ]; then
          if ! is_in_array "${TMP_HT}" ${TC_MODES}; then
            if ! is_in_array "${TMP_HT}" ${VC_MODES}; then
              if ! is_in_array "${TMP_HT}" ${CL_MODES}; then
                perl tools/test.pl single "${TMP_HT}" >> "${OUTD}/all.sh"
              fi
            fi
          fi
        fi
      done
    else
      for TMP_HT in $(seq "${HT_MIN}" "${HT_MAX}"); do
        if ! is_in_array "${TMP_HT}" ${HASH_TYPES}; then
          continue
        fi

        if [ "${TMP_HT}" -ne ${LUKS_MODE} ]; then
          # Exclude TrueCrypt and VeraCrypt testing modes
          if ! is_in_array "${TMP_HT}" ${TC_MODES}; then
            if ! is_in_array "${TMP_HT}" ${VC_MODES}; then
              if ! is_in_array "${TMP_HT}" ${CL_MODES}; then
                perl tools/test.pl single "${TMP_HT}" >> "${OUTD}/all.sh"
              fi
            fi
          fi
        fi
      done
    fi

  else

    OUTD=${PACKAGE_FOLDER}

  fi

  rm -rf "${OUTD}/logfull.txt" && touch "${OUTD}/logfull.txt"

  # populate array of hash types where we only should check if pass is in output (not both hash:pass)
  IFS=';' read -ra PASS_ONLY <<< "${HASHFILE_ONLY} ${NOCHECK_ENCODING}"
  IFS=';' read -ra TIMEOUT_ALGOS <<< "${SLOW_ALGOS}"

  IFS=';' read -ra NEVER_CRACK_ALGOS <<< "${NEVER_CRACK}"

  # for these particular algos we need to save the output to a temporary file
  IFS=';' read -ra FILE_BASED_ALGOS <<< "${HASHFILE_ONLY}"

  for hash_type in $HASH_TYPES; do

    if [ "${HT}" -ne 65535 ]; then
      # check if the loop variable "hash_type" is between HT_MIN and HT_MAX (both included)

      if   [ "${hash_type}" -lt "${HT_MIN}" ]; then
        continue
      elif [ "${hash_type}" -gt "${HT_MAX}" ]; then
        # we are done because hash_type is larger than range:
        break
      fi
    fi

    if [ "${hash_type}" -eq 20510 ]; then # special case for PKZIP Master Key
      if [ "${MODE}" -eq 1 ]; then # if "multi" was forced we need to skip it
        if [ "${HT_MIN}" -eq 20510 ]; then
          if [ "${HT_MAX}" -eq 20510 ]; then
            echo "ERROR: -m 20510 = PKZIP Master Key can only be run with a single hash"
          fi
        fi

        continue
      fi
    fi

    # skip deprecated hash-types
    if [ "${hash_type}" -eq 2500 ] || [ "${hash_type}" -eq 2501 ] || [ "${hash_type}" -eq 16800 ] || [ "${hash_type}" -eq 16801 ] ; then
      continue
    fi

    # test.pl produce wrong hashes with Apple
    # would be necessary to investigate to understand why
    if [ "${hash_type}" -eq 1800 ]; then
      if [[ "$OSTYPE" == "darwin"* ]]; then
        continue
      fi
    fi

    # Digest::BLAKE2 is broken on Apple Silicon
    if [ "${hash_type}" -eq 600 ]; then
      if [ "${IS_APPLE_SILICON}" -eq 1 ]; then
        continue
      fi
    fi

    # Digest::GOST is broken on Apple Silicon
    if [ "${hash_type}" -eq 6900 ]; then
      if [ "${IS_APPLE_SILICON}" -eq 1 ]; then
        continue
      fi
    fi

    # Crypt::GCrypt is broken on Apple
    if [ "${hash_type}" -eq 18600 ]; then
      if [[ "$OSTYPE" == "darwin"* ]]; then
        continue
      fi
    fi

    if [ -z "${PACKAGE_FOLDER}" ]; then
      # init test data
      init
    else
      echo "[ ${OUTD} ] > Run packaged test for hash type $hash_type."
    fi

    if [ "${PACKAGE}" -eq 0 ]; then

      # should we check only the pass?
      pass_only=0
      is_in_array "${hash_type}"  ${PASS_ONLY} && pass_only=1

      IS_SLOW=0
      is_in_array "${hash_type}" ${SLOW_ALGOS} && IS_SLOW=1

      # we use phpass as slow hash for testing the AMP kernel
      [ "${hash_type}" -eq 400 ] && IS_SLOW=0

      OPTS_OLD=${OPTS}
      VECTOR_OLD=${VECTOR}
      MODE_OLD=${MODE}

      if [ "${hash_type}" -eq 20510 ]; then # special case for PKZIP Master Key
        if [ "${MODE}" -eq 1 ]; then # if "multi" was forced we need to skip it
          continue
        fi

        MODE=0 # force single only
      fi

      for CUR_WIDTH in $VECTOR_WIDTHS; do

        if [ "${VECTOR_OLD}" = "all" ] || [ "${VECTOR_OLD}" = "default" ] || [ "${VECTOR_OLD}" = "${CUR_WIDTH}" ]; then

          if [ "${VECTOR_OLD}" = "default" ] && \
             [ "${CUR_WIDTH}" != "1" ] && \
             [ "${CUR_WIDTH}" != "4" ]; then

             continue
          fi

          VECTOR=${CUR_WIDTH}
          OPTS="${OPTS_OLD} --backend-vector-width ${VECTOR}"

          if [ ${IS_SLOW} -eq 1 ]; then

            # Look up if this is one of supported VeraCrypt modes
            if is_in_array "${hash_type}" ${VC_MODES}; then
              veracrypt_test 0 # aes
              veracrypt_test 1 # serpent
              veracrypt_test 2 # twofish
              veracrypt_test 3 # camellia
              veracrypt_test 4 # camellia (alternative cascade)
              veracrypt_test 5 # kuznyechik
              veracrypt_test 6 # kuznyechik (alternative cascade)
            elif is_in_array "${hash_type}" ${TC_MODES}; then
              # run truecrypt tests
              truecrypt_test "${hash_type}" 0
              truecrypt_test "${hash_type}" 1
              truecrypt_test "${hash_type}" 2
            elif [ "${hash_type}" -eq ${LUKS_MODE} ]; then
              # run luks tests
              luks_test "${hash_type}" ${ATTACK}
            else
              # run attack mode 0 (stdin)
              if [ ${ATTACK} -eq 65535 ] || [ ${ATTACK} -eq 0 ]; then attack_0; fi
            fi

          else

            if is_in_array "${hash_type}" ${CL_MODES}; then
              # run cryptoloop tests
              cryptoloop_test "${hash_type}" 128
              cryptoloop_test "${hash_type}" 192
              cryptoloop_test "${hash_type}" 256
            else
              # run attack mode 0 (stdin)
              if [ ${ATTACK} -eq 65535 ] || [ ${ATTACK} -eq 0 ]; then attack_0; fi

              # run attack mode 1 (combinator)
              if [ ${ATTACK} -eq 65535 ] || [ ${ATTACK} -eq 1 ]; then attack_1; fi

              # run attack mode 3 (bruteforce)
              if [ ${ATTACK} -eq 65535 ] || [ ${ATTACK} -eq 3 ]; then attack_3; fi

              # run attack mode 6 (dict+mask)
              if [ ${ATTACK} -eq 65535 ] || [ ${ATTACK} -eq 6 ]; then attack_6; fi

              # run attack mode 7 (mask+dict)
              if [ ${ATTACK} -eq 65535 ] || [ ${ATTACK} -eq 7 ]; then attack_7; fi
            fi

          fi
        fi
      done
      OPTS="${OPTS_OLD}"
      VECTOR="${VECTOR_OLD}"
      MODE=${MODE_OLD}
    fi
  done

else

  OUTD=${PACKAGE_FOLDER}

fi

# fix logfile
if [ "${PACKAGE}" -eq 0 ]; then
  cat -vet "${OUTD}/logfull.txt" | sed -e 's/\^M                                             \^M//g' | sed -e 's/\$$//g' > "${OUTD}/test_report.log"
fi

rm -rf "${OUTD}/logfull.txt"

if [ "${PACKAGE}" -eq 1 ]; then

  echo "[ ${OUTD} ] > Generate package ${OUTD}/${OUTD}.7z"

  cp "${BASH_SOURCE[0]}" "${OUTD}/test.sh"

  copy_luks_dir=0
  copy_tc_dir=0
  copy_vc_dir=0
  copy_cl_dir=0

  if [ "${HT}" -eq 65535 ]; then
    copy_luks_dir=1
    copy_tc_dir=1
    copy_vc_dir=1
    copy_cl_dir=1
  else
    for TMP_HT in $(seq "${HT_MIN}" "${HT_MAX}"); do
      if [ "${TMP_HT}" -eq "${LUKS_MODE}" ]; then
        copy_luks_dir=1
      elif is_in_array "${TMP_HT}" ${TC_MODES}; then
        copy_tc_dir=1
      elif is_in_array "${TMP_HT}" ${VC_MODES}; then
        copy_vc_dir=1
      elif is_in_array "${TMP_HT}" ${CL_MODES}; then
        copy_cl_dir=1
      fi
    done
  fi

  if [ "${copy_luks_dir}" -eq 1 ]; then
    mkdir "${OUTD}/luks_tests/"
    cp ${TDIR}/luks_tests/* "${OUTD}/luks_tests/"
  fi

  if [ "${copy_tc_dir}" -eq 1 ]; then
    mkdir "${OUTD}/tc_tests/"
    cp ${TDIR}/tc_tests/* "${OUTD}/tc_tests/"
  fi

  if [ "${copy_vc_dir}" -eq 1 ]; then
    mkdir "${OUTD}/vc_tests/"
    cp ${TDIR}/vc_tests/* "${OUTD}/vc_tests/"
  fi

  if [ "${copy_cl_dir}" -eq 1 ]; then
    mkdir "${OUTD}/cl_tests/"
    cp ${TDIR}/cl_tests/* "${OUTD}/cl_tests/"
  fi

  # if we package from a given folder, we need to check if e.g. the files needed for multi mode are there

  if [ -n "${PACKAGE_FOLDER}" ]; then

    MODE=2

    ls "${PACKAGE_FOLDER}"/*multi* &>/dev/null

    if [ "${?}" -ne 0 ]; then
      MODE=0
    fi

    HT=$(grep -o -- "-m  *[0-9]*" "${PACKAGE_FOLDER}/all.sh" | sort -u | sed 's/-m  //' 2> /dev/null)

    if [ -n "${HT}" ]; then
      HT_COUNT=$(echo "${HT}" | wc -l)

      if [ "${HT_COUNT}" -gt 1 ]; then
        HT=65535
      fi
    fi

    #ATTACK=65535 # more appropriate ?
  fi

  # for convenience: 'run package' is default action for packaged test.sh ( + add other defaults too )

  SED_IN_PLACE='-i'

  UNAME=$(uname -s)

  # of course macOS requires us to implement a special case (sed -i "" for the backup file)
  if [ "${UNAME}" = "Darwin" ] ; then
    SED_IN_PLACE='-i ""'
  fi

  HT_PACKAGED=${HT}

  if [ "${HT_MIN}" -ne "${HT_MAX}" ]; then
    HT_PACKAGED=${HT_MIN}-${HT_MAX}
  fi

  HASH_TYPES_PACKAGED=$(   echo "${HASH_TYPES}"    | tr '\n' ' ' | sed 's/ $//')
  HASHFILE_ONLY_PACKAGED=$(echo "${HASHFILE_ONLY}" | tr '\n' ' ' | sed 's/ $//')
  NEVER_CRACK_PACKAGED=$(  echo "${NEVER_CRACK}"   | tr '\n' ' ' | sed 's/ $//')
  SLOW_ALGOS_PACKAGED=$(   echo "${SLOW_ALGOS}"    | tr '\n' ' ' | sed 's/ $//')

  sed "${SED_IN_PLACE}" -e 's/^\(PACKAGE_FOLDER\)=""/\1="$( echo "${BASH_SOURCE[0]}" | sed \"s!test.sh\\$!!\" )"/' \
    -e "s/^\(HASH_TYPES\)=\$(.*/\1=\"${HASH_TYPES_PACKAGED}\"/" \
    -e "s/^\(HASHFILE_ONLY\)=\$(.*/\1=\"${HASHFILE_ONLY_PACKAGED}\"/" \
    -e "s/^\(NEVER_CRACK\)=\$(.*/\1=\"${NEVER_CRACK_PACKAGED}\"/" \
    -e "s/^\(SLOW_ALGOS\)=\$(.*/\1=\"${SLOW_ALGOS_PACKAGED}\"/" \
    -e "s/^\(HT\)=0/\1=${HT_PACKAGED}/" \
    -e "s/^\(MODE\)=0/\1=${MODE}/" \
    -e "s/^\(ATTACK\)=0/\1=${ATTACK}/" \
    "${OUTD}/test.sh"

  ${PACKAGE_CMD} "${OUTD}/${OUTD}.7z" "${OUTD}/" &>/dev/null
fi