#!/bin/bash  -
#===============================================================================
#
#          FILE: cfScanner.sh
#
#         USAGE: ./cfScanner.sh [Argumets]
#
#   DESCRIPTION: Scan all 1.5 Mil CloudFlare IP addresses
#
#       OPTIONS: -h, --help
#  REQUIREMENTS: getopt, jq, git, tput, bc, curl, parallel (version > 20220515), shuf
#        AUTHOR: Morteza Bashsiz (mb), morteza.bashsiz@gmail.com
#  ORGANIZATION: Linux
#       CREATED: 01/24/2023 07:36:57 PM
#      REVISION: nomadzzz, armgham, beh-rouz, amini8, mahdibahramih, armineslami, miytiy, F4RAN 
#===============================================================================

export TOP_PID=$$

# Function fncLongIntToStr
# converts IP in long integer format to a string 
fncLongIntToStr() {
    local IFS=. num quad ip e
    num=$1
    for e in 3 2 1
    do
        (( quad = 256 ** e))
        (( ip[3-e] = num / quad ))
        (( num = num % quad ))
    done
    ip[3]=$num
    echo "${ip[*]}"
}
# End of Function fncLongIntToStr

# Function fncIpToLongInt
# converts IP to long integer 
fncIpToLongInt() {
    local IFS=. ip num e
    # shellcheck disable=SC2206
    ip=($1)
    for e in 3 2 1
    do
        (( num += ip[3-e] * 256 ** e ))
    done
    (( num += ip[3] ))
    echo $num
}
# End of Function fncIpToLongInt

# Function fncSubnetToIP
# converts subnet to IP list
fncSubnetToIP() {
  # shellcheck disable=SC2206
  local network=(${1//\// })
  # shellcheck disable=SC2206
  local iparr=(${network[0]//./ })
  local mask=32
  [[ $((${#network[@]})) -gt 1 ]] && mask=${network[1]}

  local maskarr
  # shellcheck disable=SC2206
  if [[ ${mask} = '\.' ]]; then  # already mask format like 255.255.255.0
    maskarr=(${mask//./ })
  else                           # assume CIDR like /24, convert to mask
    if [[ $((mask)) -lt 8 ]]; then
      maskarr=($((256-2**(8-mask))) 0 0 0)
    elif  [[ $((mask)) -lt 16 ]]; then
      maskarr=(255 $((256-2**(16-mask))) 0 0)
    elif  [[ $((mask)) -lt 24 ]]; then
      maskarr=(255 255 $((256-2**(24-mask))) 0)
    elif [[ $((mask)) -lt 32 ]]; then
      maskarr=(255 255 255 $((256-2**(32-mask))))
    elif [[ ${mask} == 32 ]]; then
      maskarr=(255 255 255 255)
    fi
  fi

  # correct wrong subnet masks (e.g. 240.192.255.0 to 255.255.255.0)
  [[ ${maskarr[2]} == 255 ]] && maskarr[1]=255
  [[ ${maskarr[1]} == 255 ]] && maskarr[0]=255

  # generate list of ip addresses
  if [[ "$randomNumber" != "NULL" ]]
  then
    local bytes=(0 0 0 0)
    for i in $(seq 0 $((255-maskarr[0]))); do
      bytes[0]="$(( i+(iparr[0] & maskarr[0]) ))"
      for j in $(seq 0 $((255-maskarr[1]))); do
        bytes[1]="$(( j+(iparr[1] & maskarr[1]) ))"
        for k in $(seq 0 $((255-maskarr[2]))); do
          bytes[2]="$(( k+(iparr[2] & maskarr[2]) ))"
          for l in $(seq 1 $((255-maskarr[3]))); do
            bytes[3]="$(( l+(iparr[3] & maskarr[3]) ))"
            ipList+=("$(printf "%d.%d.%d.%d" "${bytes[@]}")")
          done
        done
      done
    done
    # Choose random IP addresses from generated IP list
    if [[ "$osVersion" == "Linux" ]]
    then
      mapfile -t ipList < <(shuf -e "${ipList[@]}")
      mapfile -t ipList < <(shuf -e "${ipList[@]:0:$randomNumber}")
    elif [[ "$osVersion" == "Mac"  ]]
    then
      # shellcheck disable=SC2207
      ipList=($(printf '%s\n' "${ipList[@]}" | shuf))
      # shellcheck disable=SC2207
      ipList=($(printf '%s\n' "${ipList[@]:0:$randomNumber}" | shuf))
    else
      echo "OS not supported only Linux or Mac"
      exit 1
    fi
    for i in "${ipList[@]}"; do 
      echo "$i"
    done
  elif [[ "$randomNumber" == "NULL" ]]
  then
    local bytes=(0 0 0 0)
    for i in $(seq 0 $((255-maskarr[0]))); do
      bytes[0]="$(( i+(iparr[0] & maskarr[0]) ))"
      for j in $(seq 0 $((255-maskarr[1]))); do
        bytes[1]="$(( j+(iparr[1] & maskarr[1]) ))"
        for k in $(seq 0 $((255-maskarr[2]))); do
          bytes[2]="$(( k+(iparr[2] & maskarr[2]) ))"
          for l in $(seq 1 $((255-maskarr[3]))); do
            bytes[3]="$(( l+(iparr[3] & maskarr[3]) ))"
            printf "%d.%d.%d.%d\n" "${bytes[@]}"
          done
        done
      done
    done
  fi
}
# End of Function fncSubnetToIP

# Function fncShowProgress
# Progress bar maker function (based on https://www.baeldung.com/linux/command-line-progress-bar)
function fncShowProgress {
  barCharDone="="
  barCharTodo=" "
  barSplitter='>'
  barPercentageScale=2
  current="$1"
  total="$2"

  barSize="$(($(tput cols)-70))" # 70 cols for description characters

  # calculate the progress in percentage 
  percent=$(bc <<< "scale=$barPercentageScale; 100 * $current / $total" )
  # The number of done and todo characters
  done=$(bc <<< "scale=0; $barSize * $percent / 100" )
  todo=$(bc <<< "scale=0; $barSize - $done")
  # build the done and todo sub-bars
  doneSubBar=$(printf "%${done}s" | tr " " "${barCharDone}")
  todoSubBar=$(printf "%${todo}s" | tr " " "${barCharTodo} - 1") # 1 for barSplitter
  spacesSubBar=$(printf "%${todo}s" | tr " " " ")

  # output the bar
  progressBar="| Progress bar of main IPs: [${doneSubBar}${barSplitter}${todoSubBar}] ${percent}%${spacesSubBar}" # Some end space for pretty formatting
}
# End of Function showProgress

# Function fncCheckIPList
# Check Subnet
function fncCheckIPList {
  local ipList scriptDir resultFile timeoutCommand domainFronting downOK upOK
  ipList="${1}"
  resultFile="${3}"
  scriptDir="${4}"
  configId="${5}"
  configHost="${6}"
  configPort="${7}"
  configPath="${8}"
  fileSize="${9}"
  osVersion="${10}"
  clientCommand="${11}"
  tryCount="${12}"
  downThreshold="${13}"
  upThreshold="${14}"
  downloadOrUpload="${15}"
  vpnOrNot="${16}"
  quickOrNot="${17}"
  binDir="$scriptDir/../bin"
  tempConfigDir="$scriptDir/tempConfig"
  uploadFile="$tempConfigDir/upload_file"
  configPath=$(echo "$configPath" | sed 's/\//\\\//g')
  # set proper command for linux
  if command -v timeout >/dev/null 2>&1; 
  then
      timeoutCommand="timeout"
  else
    # set proper command for mac
    if command -v gtimeout >/dev/null 2>&1; 
    then
        timeoutCommand="gtimeout"
    else
        echo >&2 "I require 'timeout' command but it's not installed. Please install 'timeout' or an alternative command like 'gtimeout' and try again."
        exit 1
    fi
  fi
  if [[ "$vpnOrNot" == "YES" ]]
  then
    for ip in ${ipList}
      do
        if [[  "$downloadOrUpload" == "BOTH" ]]
        then
          downOK="NO"
          upOK="NO"
        elif [[ "$downloadOrUpload" == "UP" ]]
        then
          downOK="YES"
          upOK="NO"
        elif [[ "$downloadOrUpload" == "DOWN" ]]
        then
          downOK="NO"
          upOK="YES"
        fi
        if $timeoutCommand 1 bash -c "</dev/tcp/$ip/443" > /dev/null 2>&1;
        then
          if [[ "$quickOrNot" == "NO" ]]
          then
            domainFronting=$($timeoutCommand 1 curl -k -s --tlsv1.2 -H "Host: speed.cloudflare.com" --resolve "speed.cloudflare.com:443:$ip" "https://speed.cloudflare.com/__down?bytes=10")
          elif [[ "$quickOrNot" == "YES" ]]
          then
            domainFronting="0000000000"
          fi
          if [[ "$domainFronting" == "0000000000" ]]
          then
            mainDomain=$(echo "$configHost" | awk -F '.' '{ print $2"."$3}')
            if [[ "$osVersion" == "Linux" ]]
                then
                  randomUUID=$(cat /proc/sys/kernel/random/uuid)
              elif [[ "$osVersion" == "Mac"  ]]
                then
                  randomUUID=$(uuidgen | tr '[:upper:]' '[:lower:]')
              else
                  echo "OS not supported only Linux or Mac"
                  exit 1
            fi
            configServerName="$randomUUID.$mainDomain"
            ipConfigFile="$tempConfigDir/config.json.$ip".json
            cp "$scriptDir"/config.json.temp "$ipConfigFile"
            ipO1=$(echo "$ip" | awk -F '.' '{print $1}')
            ipO2=$(echo "$ip" | awk -F '.' '{print $2}')
            ipO3=$(echo "$ip" | awk -F '.' '{print $3}')
            ipO4=$(echo "$ip" | awk -F '.' '{print $4}')
            port=$((ipO1 + ipO2 + ipO3 + ipO4))
            if [[ "$osVersion" == "Mac" ]]
            then
              sed -i "" "s/IP.IP.IP.IP/$ip/g" "$ipConfigFile"
              sed -i "" "s/PORTPORT/3$port/g" "$ipConfigFile"
              sed -i "" "s/IDID/$configId/g" "$ipConfigFile"
              sed -i "" "s/HOSTHOST/$configHost/g" "$ipConfigFile"
              sed -i "" "s/CFPORTCFPORT/$configPort/g" "$ipConfigFile"
              sed -i "" "s/ENDPOINTENDPOINT/$configPath/g" "$ipConfigFile"
              sed -i "" "s/RANDOMHOST/$configServerName/g" "$ipConfigFile"
            elif [[ "$osVersion" == "Linux" ]]
            then
              sed -i "s/IP.IP.IP.IP/$ip/g" "$ipConfigFile"
              sed -i "s/PORTPORT/3$port/g" "$ipConfigFile"
              sed -i "s/IDID/$configId/g" "$ipConfigFile"
              sed -i "s/HOSTHOST/$configHost/g" "$ipConfigFile"
              sed -i "s/CFPORTCFPORT/$configPort/g" "$ipConfigFile"
              sed -i "s/ENDPOINTENDPOINT/$configPath/g" "$ipConfigFile"
              sed -i "s/RANDOMHOST/$configServerName/g" "$ipConfigFile"
            fi
            # shellcheck disable=SC2009
            pid=$(ps aux | grep config.json."$ip" | grep -v grep | awk '{ print $2 }')
            if [[ "$pid" ]]
            then
              kill -9 "$pid" > /dev/null 2>&1
            fi
            downTotalTime=0
            upTotalTime=0
            downAvgStr=""
            upAvgStr=""
            downSuccessedCount=0
            upSuccessedCount=0
            nohup "$binDir"/"$clientCommand" -c "$ipConfigFile" > /dev/null &
            sleep 2
            for i in $(seq 1 "$tryCount");
            do
              downTimeMil=0
              upTimeMil=0
              if [[ "$downloadOrUpload" == "DOWN" ]] || [[  "$downloadOrUpload" == "BOTH" ]]
              then
                downTimeMil=$($timeoutCommand 2 curl -x "socks5://127.0.0.1:3$port" -s -w "TIME: %{time_total}\n" --resolve "speed.cloudflare.com:443:$ip" "https://speed.cloudflare.com/__down?bytes=$fileSize" --output /dev/null | grep "TIME" | tail -n 1 | awk '{print $2}' | xargs -I {} echo "{} * 1000 /1" | bc )
                if [[ $downTimeMil -gt 100 ]]
                then
                  downSuccessedCount=$(( downSuccessedCount+1 ))
                else
                  downTimeMil=0
                fi
              fi
              if [[ "$downloadOrUpload" == "UP" ]] || [[  "$downloadOrUpload" == "BOTH" ]]
              then
                result=$($timeoutCommand 2 curl -x "socks5://127.0.0.1:3$port" -s -w "\nTIME: %{time_total}\n" --resolve "speed.cloudflare.com:443:$ip" --data "@$uploadFile" https://speed.cloudflare.com/__up | grep "TIME" | tail -n 1 | awk '{print $2}' | xargs -I {} echo "{} * 1000 /1" | bc)
                if [[ "$result" ]]
                then
                  upTimeMil="$result"
                  if [[ $upTimeMil -gt 100 ]]
                  then
                    upSuccessedCount=$(( upSuccessedCount+1 ))
                  else
                    upTimeMil=0
                  fi
                fi
              fi
              downTotalTime=$(( downTotalTime+downTimeMil ))
              upTotalTime=$(( upTotalTime+upTimeMil ))
              downAvgStr="$downAvgStr $downTimeMil"
              upAvgStr="$upAvgStr $upTimeMil"
            done
            if [[ $downSuccessedCount -ge $downThreshold ]] && [[ "$downloadOrUpload" != "UP" ]]
            then
              downOK="YES"
              downRealTime=$(( downTotalTime/downSuccessedCount ))
            else
              downRealTime=0
            fi
            if [[ $upSuccessedCount -ge $upThreshold ]] && [[ "$downloadOrUpload" != "DOWN" ]]
            then
              upOK="YES"
              upRealTime=$(( upTotalTime/upSuccessedCount ))
            else
              upRealTime=0
            fi
            # shellcheck disable=SC2009
            pid=$(ps aux | grep config.json."$ip" | grep -v grep | awk '{ print $2 }')
            if [[ "$pid" ]]
            then
              kill -9 "$pid" > /dev/null 2>&1
            fi
            if [[ "$downOK" == "YES" ]] && [[ "$upOK" == "YES" ]]
            then
              if [[ "$downRealTime" && $downRealTime -gt 100 ]] || [[ "$upRealTime" && $upRealTime -gt 100 ]]
              then
                echo -e "${GREEN}OK${NC} $ip ${BLUE}DOWN: Avg $downRealTime $downAvgStr ${ORANGE}UP: Avg $upRealTime, $upAvgStr${NC}" 
                if [[ "$downRealTime" && $downRealTime -gt 100 ]]
                then
                  #echo "${GREEN}OK${NC} $ip ${BLUE}DOWN: Avg $downRealTime $downAvgStr${NC}" 
                  echo "$downRealTime, $downAvgStr DOWN FOR IP $ip" >> "$resultFile"
                fi
                if [[ "$upRealTime" && $upRealTime -gt 100 ]]
                then
                  #echo "${GREEN}OK${NC} $ip ${BLUE}UP: $upRealTime, $upAvgStr${NC}" 
                  echo "$upRealTime, $upAvgStr UP FOR IP $ip" >> "$resultFile"
                fi
              else
                echo -e "${RED}FAILED${NC} $ip"
              fi
            else
              echo -e "${RED}FAILED${NC} $ip"
            fi
          else
            echo -e "${RED}FAILED${NC} $ip"
          fi
        else
          echo -e "${RED}FAILED${NC} $ip"
        fi
    done
  elif [[ "$vpnOrNot" == "NO" ]]
  then
    for ip in ${ipList}
      do
        if [[  "$downloadOrUpload" == "BOTH" ]]
        then
          downOK="NO"
          upOK="NO"
        elif [[ "$downloadOrUpload" == "UP" ]]
        then
          downOK="YES"
          upOK="NO"
        elif [[ "$downloadOrUpload" == "DOWN" ]]
        then
          downOK="NO"
          upOK="YES"
        fi
        if $timeoutCommand 1 bash -c "</dev/tcp/$ip/443" > /dev/null 2>&1;
        then
          if [[ "$quickOrNot" == "NO" ]]
          then
            domainFronting=$($timeoutCommand 1 curl -k -s --tlsv1.2 -H "Host: speed.cloudflare.com" --resolve "speed.cloudflare.com:443:$ip" "https://speed.cloudflare.com/__down?bytes=10")
          elif [[ "$quickOrNot" == "YES" ]]
          then
            domainFronting="0000000000"
          fi
          if [[ "$domainFronting" == "0000000000" ]]
          then
            downTotalTime=0
            upTotalTime=0
            downAvgStr=""
            upAvgStr=""
            downSuccessedCount=0
            upSuccessedCount=0
            for i in $(seq 1 "$tryCount");
            do
              downTimeMil=0
              upTimeMil=0
              if [[ "$downloadOrUpload" == "DOWN" ]] || [[  "$downloadOrUpload" == "BOTH" ]]
              then
                downTimeMil=$($timeoutCommand 2 curl -s -w "TIME: %{time_total}\n" -H "Host: speed.cloudflare.com" --resolve "speed.cloudflare.com:443:$ip" "https://speed.cloudflare.com/__down?bytes=$fileSize" --output /dev/null | grep "TIME" | tail -n 1 | awk '{print $2}' | xargs -I {} echo "{} * 1000 /1" | bc )
                if [[ $downTimeMil -gt 100 ]]
                then
                  downSuccessedCount=$(( downSuccessedCount+1 ))
                else
                  downTimeMil=0
                fi
              fi
              if [[ "$downloadOrUpload" == "UP" ]] || [[  "$downloadOrUpload" == "BOTH" ]]
              then
                result=$($timeoutCommand 2 curl -s -w "\nTIME: %{time_total}\n" -H "Host: speed.cloudflare.com" --resolve "speed.cloudflare.com:443:$ip" --data "@$uploadFile" https://speed.cloudflare.com/__up | grep "TIME" | tail -n 1 | awk '{print $2}' | xargs -I {} echo "{} * 1000 /1" | bc)
                if [[ "$result" ]]
                then
                  upTimeMil="$result"
                  if [[ $upTimeMil -gt 100 ]]
                  then
                    upSuccessedCount=$(( upSuccessedCount+1 ))
                  else
                    upTimeMil=0
                  fi
                fi
              fi
              downTotalTime=$(( downTotalTime+downTimeMil ))
              upTotalTime=$(( upTotalTime+upTimeMil ))
              downAvgStr="$downAvgStr $downTimeMil"
              upAvgStr="$upAvgStr $upTimeMil"
            done
            if [[ $downSuccessedCount -ge $downThreshold ]] && [[ "$downloadOrUpload" != "UP" ]]
            then
              downOK="YES"
              downRealTime=$(( downTotalTime/downSuccessedCount ))
            else
              downRealTime=0
            fi
            if [[ $upSuccessedCount -ge $upThreshold ]] && [[ "$downloadOrUpload" != "DOWN" ]]
            then
              upOK="YES"
              upRealTime=$(( upTotalTime/upSuccessedCount ))
            else
              upRealTime=0
            fi
            if [[ "$downOK" == "YES" ]] && [[ "$upOK" == "YES" ]]
            then
              if [[ "$downRealTime" && $downRealTime -gt 100 ]] || [[ "$upRealTime" && $upRealTime -gt 100 ]]
              then
                echo -e "${GREEN}OK${NC} $ip ${BLUE}DOWN: Avg $downRealTime $downAvgStr ${ORANGE}UP: Avg $upRealTime, $upAvgStr${NC}" 
                if [[ "$downRealTime" && $downRealTime -gt 100 ]]
                then
                  #echo -e "${GREEN}OK${NC} $ip ${BLUE}DOWN: Avg $downRealTime $downAvgStr${NC}" 
                  echo "$downRealTime, $downAvgStr DOWN FOR IP $ip" >> "$resultFile"
                fi
                if [[ "$upRealTime" && $upRealTime -gt 100 ]]
                then
                  #echo -e "${GREEN}OK${NC} $ip ${BLUE}UP: $upRealTime, $upAvgStr${NC}" 
                  echo "$upRealTime, $upAvgStr UP FOR IP $ip" >> "$resultFile"
                fi
              else
                echo -e "${RED}FAILED${NC} $ip"
              fi
            else
              echo -e "${RED}FAILED${NC} $ip"
            fi
          else
            echo -e "${RED}FAILED${NC} $ip"
          fi
        else
          echo -e "${RED}FAILED${NC} $ip"
        fi
    done
  fi
}
# End of Function fncCheckIPList
export -f fncCheckIPList

# Function fncCheckDpnd
# Check for dipendencies
function fncCheckDpnd {
  osVersion="NULL"
  if [[ "$(uname)" == "Linux" ]]; then
      command -v jq >/dev/null 2>&1 || { echo >&2 "I require 'jq' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      command -v parallel >/dev/null 2>&1 || { echo >&2 "I require 'parallel' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      command -v bc >/dev/null 2>&1 || { echo >&2 "I require 'bc' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      command -v timeout >/dev/null 2>&1 || { echo >&2 "I require 'timeout' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      osVersion="Linux"
  elif [[ "$(uname)" == "Darwin" ]];then
      command -v jq >/dev/null 2>&1 || { echo >&2 "I require 'jq' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      command -v parallel >/dev/null 2>&1 || { echo >&2 "I require 'parallel' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      command -v bc >/dev/null 2>&1 || { echo >&2 "I require 'bc' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      command -v gtimeout >/dev/null 2>&1 || { echo >&2 "I require 'gtimeout' but it's not installed. Please install it and try again."; kill -s 1 "$TOP_PID"; }
      osVersion="Mac"
  fi
  echo "$osVersion"
}
# End of Function fncCheckDpnd

# Function fncValidateConfig
# Install packages on destination host
function fncValidateConfig {
  local config
  config="$1"
  if [[ -f "$config" ]]
  then
    echo "reading config ..."
    configId=$(jq --raw-output .id "$config") 
    configHost=$(jq --raw-output .host "$config") 
    configPort=$(jq --raw-output .port "$config") 
    configPath=$(jq --raw-output .path "$config") 
    if ! [[ "$configId" ]] || ! [[ $configHost ]] || ! [[ $configPort ]] || ! [[ $configPath ]]
    then
      echo "config is not correct"
      exit 1
    fi
  else
    echo "config file does not exist $config"
    exit 1
  fi
}
# End of Function fncValidateConfig

# Function fncCreateDir
# creates needed directory
function fncCreateDir {
  local dirPath
  dirPath="${1}"
  if [ ! -d "$dirPath" ]; then
    mkdir -p "$dirPath"
  fi
}
# End of Function fncCreateDir

# Function fncMainCFFindSubnet
# main Function for Subnet
function fncMainCFFindSubnet {
  local threads progressBar resultFile scriptDir configId configHost configPort configPath fileSize osVersion parallelVersion subnetsFile breakedSubnets network netmask downloadOrUpload tryCount downThreshold upThreshold vpnOrNot quickOrNot
  threads="${1}"
  progressBar="${2}"
  resultFile="${3}"
  scriptDir="${4}"
  configId="${5}"
  configHost="${6}"
  configPort="${7}"
  configPath="${8}"
  fileSize="${9}"
  osVersion="${10}"
  subnetsFile="${11}"
  tryCount="${12}"
  downThreshold="${13}"
  upThreshold="${14}"
  downloadOrUpload="${15}"
  vpnOrNot="${16}"
  quickOrNot="${17}"

  if [[ "$osVersion" == "Linux" ]]
  then
    if [[  "$clientCore" == "XRAY" ]]
    then
      clientCommand="xray"
    else
      clientCommand="v2ray"
    fi
  elif [[ "$osVersion" == "Mac"  ]]
  then
    clientCommand="v2ray-mac"
  else
    echo "OS not supported only Linux or Mac"
    exit 1
  fi
  
  parallelVersion=$(parallel --version | head -n1 | grep -Ewo '[0-9]{8}')
  defaultSubnetsFileUrl="https://raw.githubusercontent.com/MortezaBashsiz/CFScanner/main/config/cf.local.iplist"

  if [[ "$subnetsFile" == "NULL" ]] 
  then
    defaultSubnetsFileUrlResult=$(curl -I -L -s "$defaultSubnetsFileUrl" | grep "^HTTP" | grep 200 | awk '{ print $2 }')
    if [[ "$defaultSubnetsFileUrlResult" == "200" ]]
    then
      defaultSubnetsFile=$(curl -s "$defaultSubnetsFileUrl")
      echo "Reading subnets from $defaultSubnetsFileUrl"
      cfSubnetList="$defaultSubnetsFile"
    else
      echo "URL $defaultSubnetsFileUrl is not available. This URL contains the latest subnet file"
      echo "Reading subnets from file $scriptDir/../config/cf.local.iplist"
      cfSubnetList=$(cat "$scriptDir/../config/cf.local.iplist")
    fi
  else
    echo "Reading subnets from file $subnetsFile"
    cfSubnetList=$(cat "$subnetsFile")
  fi
  
  ipListLength="0"
  for subNet in ${cfSubnetList}
  do
    breakedSubnets=
    maxSubnet=24
    network=${subNet%/*}
    netmask=${subNet#*/}
    if [[ ${netmask} -ge ${maxSubnet} ]]
    then
      breakedSubnets="${breakedSubnets} ${network}/${netmask}"
    else
      for i in $(seq 0 $(( $(( 2 ** (maxSubnet - netmask) )) - 1 )) )
      do
        breakedSubnets="${breakedSubnets} $( fncLongIntToStr $(( $( fncIpToLongInt "${network}" ) + $(( 2 ** ( 32 - maxSubnet ) * i )) )) )/${maxSubnet}"
      done
    fi
    breakedSubnets=$(echo "${breakedSubnets}"|tr ' ' '\n')
    for breakedSubnet in ${breakedSubnets}
    do
      ipListLength=$(( ipListLength+1 ))
    done
  done

  passedIpsCount=0
  for subNet in ${cfSubnetList}
  do
    breakedSubnets=
    maxSubnet=24
    network=${subNet%/*}
    netmask=${subNet#*/}
    if [[ ${netmask} -ge ${maxSubnet} ]]
    then
      breakedSubnets="${breakedSubnets} ${network}/${netmask}"
    else
      for i in $(seq 0 $(( $(( 2 ** (maxSubnet - netmask) )) - 1 )) )
      do
        breakedSubnets="${breakedSubnets} $( fncLongIntToStr $(( $( fncIpToLongInt "${network}" ) + $(( 2 ** ( 32 - maxSubnet ) * i )) )) )/${maxSubnet}"
      done
    fi
    breakedSubnets=$(echo "${breakedSubnets}"|tr ' ' '\n')
    for breakedSubnet in ${breakedSubnets}
    do
      fncShowProgress "$passedIpsCount" "$ipListLength"
      killall v2ray > /dev/null 2>&1
      ipList=$(fncSubnetToIP "$breakedSubnet")
      tput cuu1; tput ed # rewrites Parallel's bar
      if [[ $parallelVersion -gt 20220515 ]];
      then
        parallel --ll --bar -j "$threads" fncCheckIPList ::: "$ipList" ::: "$progressBar" ::: "$resultFile" ::: "$scriptDir" ::: "$configId" ::: "$configHost" ::: "$configPort" ::: "$configPath" ::: "$fileSize" ::: "$osVersion" ::: "$clientCommand" ::: "$tryCount" ::: "$downThreshold" ::: "$upThreshold" ::: "$downloadOrUpload" ::: "$vpnOrNot" ::: "$quickOrNot"
      else
        echo -e "${RED}$progressBar${NC}"
        parallel -j "$threads" fncCheckIPList ::: "$ipList" ::: "$progressBar" ::: "$resultFile" ::: "$scriptDir" ::: "$configId" ::: "$configHost" ::: "$configPort" ::: "$configPath" ::: "$fileSize" ::: "$osVersion" ::: "$clientCommand" ::: "$tryCount" ::: "$downThreshold" ::: "$upThreshold" ::: "$downloadOrUpload" ::: "$vpnOrNot" ::: "$quickOrNot"
      fi
      killall v2ray > /dev/null 2>&1
      passedIpsCount=$(( passedIpsCount+1 ))
    done
  done
  sort -n -k1 -t, "$resultFile" -o "$resultFile"
}
# End of Function fncMainCFFindSubnet

# Function fncMainCFFindIP
# main Function for IP
function fncMainCFFindIP {
  local threads progressBar resultFile scriptDir configId configHost configPort configPath fileSize osVersion parallelVersion IPFile downloadOrUpload downThreshold upThreshold vpnOrNot quickOrNot
  threads="${1}"
  progressBar="${2}"
  resultFile="${3}"
  scriptDir="${4}"
  configId="${5}"
  configHost="${6}"
  configPort="${7}"
  configPath="${8}"
  fileSize="${9}"
  osVersion="${10}"
  IPFile="${11}"
  tryCount="${12}"
  downThreshold="${13}" 
  upThreshold="${14}"
  downloadOrUpload="${15}"
  vpnOrNot="${16}"
  quickOrNot="${17}"

  if [[ "$osVersion" == "Linux" ]]
  then
    if [[  "$clientCore" == "XRAY" ]]
    then
      clientCommand="xray"
    else
      clientCommand="v2ray"
    fi
  elif [[ "$osVersion" == "Mac"  ]]
  then
    clientCommand="v2ray-mac"
  else
    echo "OS not supported only Linux or Mac"
    exit 1
  fi

  parallelVersion=$(parallel --version | head -n1 | grep -Ewo '[0-9]{8}')

  cfIPList=$(cat "$IPFile")
  killall v2ray > /dev/null 2>&1
  tput cuu1; tput ed # rewrites Parallel's bar
  if [[ $parallelVersion -gt 20220515 ]];
  then
    parallel --ll --bar -j "$threads" fncCheckIPList ::: "$cfIPList" ::: "$progressBar" ::: "$resultFile" ::: "$scriptDir" ::: "$configId" ::: "$configHost" ::: "$configPort" ::: "$configPath" ::: "$fileSize" ::: "$osVersion" ::: "$clientCommand" ::: "$tryCount" ::: "$downThreshold" ::: "$upThreshold" ::: "$downloadOrUpload" ::: "$vpnOrNot" ::: "$quickOrNot"
  else
    echo -e "${RED}$progressBar${NC}"
    parallel -j "$threads" fncCheckIPList ::: "$cfIPList" ::: "$progressBar" ::: "$resultFile" ::: "$scriptDir" ::: "$configId" ::: "$configHost" ::: "$configPort" ::: "$configPath" ::: "$fileSize" ::: "$osVersion" ::: "$clientCommand" ::: "$tryCount" ::: "$downThreshold" ::: "$upThreshold" ::: "$downloadOrUpload" ::: "$vpnOrNot" ::: "$quickOrNot"
  fi
  killall v2ray > /dev/null 2>&1
  sort -n -k1 -t, "$resultFile" -o "$resultFile"
}
# End of Function fncMainCFFindIP

clientConfigFile="https://raw.githubusercontent.com/MortezaBashsiz/CFScanner/main/config/ClientConfig.json"
subnetIPFile="NULL"

# Function fncUsage
# usage function
function fncUsage {
  if [[ "$osVersion" == "Mac" ]]
  then 
    echo -e "Usage: cfScanner [ -v YES/NO ]
      [ -m SUBNET/IP ] 
      [ -t DOWN/UP/BOTH ]
      [ -p <int> ] threads
      [ -n <int> ] trycount
      [ -c <configfile> ]
      [ -s <int> ] speed
      [ -r <int> ] randomness
      [ -d <int> ] download threshold
      [ -u <int> ] upload threshold
      [ -f <custome-ip-file> (if you chose IP mode)]
      [ -q YES/NO]
      [ -h ] help\n"
    exit 2
  elif [[ "$osVersion" == "Linux" ]]
  then
    echo -e "Usage: cfScanner [ -x|--core V2RAY/XRAY ]
      [ -v|--vpn-mode YES/NO ]
      [ -m|--mode  SUBNET/IP ] 
      [ -t|--test-type  DOWN/UP/BOTH ]
      [ -p|--thread <int> ]
      [ -n|--tryCount <int> ]
      [ -c|--config <configfile> ]
      [ -s|--speed <int> ] 
      [ -r|--random <int> ]
      [ -d|--down-threshold <int> ]
      [ -u|--up-threshold <int> ]
      [ -f|--file <custome-ip-file> (if you chose IP mode)]
      [ -q|--quick YES/NO]
      [ -h|--help ]\n"
     exit 2
  fi
}
# End of Function fncUsage

clientCore="XRAY"
randomNumber="NULL"
downThreshold="1"
upThreshold="1"
osVersion="$(fncCheckDpnd)"
vpnOrNot="NO"
subnetOrIP="SUBNET"
downloadOrUpload="BOTH"
threads="4"
tryCount="1"
config="NULL"
speed="100"
quickOrNot="NO"

if [[ "$osVersion" == "Mac" ]]
then
  parsedArguments=$(getopt v:m:t:p:n:c:s:r:d:u:f:q:h "$@")
elif [[ "$osVersion" == "Linux" ]]
then
  parsedArguments=$(getopt -a -n cfScanner -o x:v:m:t:p:n:c:s:r:d:u:f:q:h --long core:,vpn-mode:,mode:,test-type:,thread:,tryCount:,config:,speed:,random:,down-threshold:,up-threshold:,file:,quick:,help -- "$@")
fi

eval set -- "$parsedArguments"
if [[ "$osVersion" == "Mac" ]]
then
  while :
  do
    case "$1" in
      -v) vpnOrNot="$2" ; shift 2 ;;
      -m) subnetOrIP="$2" ; shift 2 ;;
      -t) downloadOrUpload="$2" ; shift 2 ;;
      -p) threads="$2" ; shift 2 ;;
      -n) tryCount="$2" ; shift 2 ;;
      -c) config="$2" ; shift 2 ;;
      -s) speed="$2" ; shift 2 ;;
      -r) randomNumber="$2" ; shift 2 ;;
      -d) downThreshold="$2" ; shift 2 ;;
      -u) upThreshold="$2" ; shift 2 ;;
      -f) subnetIPFile="$2" ; shift 2 ;;
      -q) quickOrNot="$2" ; shift 2 ;;
      -h) fncUsage ;;
      --) shift; break ;;
      *) echo "Unexpected option: $1 is not acceptable"
      fncUsage ;;
    esac
  done
elif [[ "$osVersion" == "Linux" ]]
then
  while :
  do
    case "$1" in
      -x|--core) clientCore="$2" ; shift 2 ;;
      -v|--vpn-mode) vpnOrNot="$2" ; shift 2 ;;
      -m|--mode) subnetOrIP="$2" ; shift 2 ;;
      -t|--test-type) downloadOrUpload="$2" ; shift 2 ;;
      -p|--thread) threads="$2" ; shift 2 ;;
      -n|--tryCount) tryCount="$2" ; shift 2 ;;
      -c|--config) config="$2" ; shift 2 ;;
      -s|--speed) speed="$2" ; shift 2 ;;
      -r|--random) randomNumber="$2" ; shift 2 ;;
      -d|--down-threshold) downThreshold="$2" ; shift 2 ;;
      -u|--up-threshold) upThreshold="$2" ; shift 2 ;;
      -f|--file) subnetIPFile="$2" ; shift 2 ;;
      -q|--quick) quickOrNot="$2" ; shift 2 ;;
      -h|--help) fncUsage ;;
      --) shift; break ;;
      *) echo "Unexpected option: $1 is not acceptable"
      fncUsage ;;
    esac
  done
fi

validArguments=$?
if [ "$validArguments" != "0" ]; then
  echo "error validate"
  exit 2
fi

if [[ "$clientCore" != "XRAY" && "$clientCore" != "V2RAY" ]] 
then
  echo "Wrong value: $clientCore Must be XRAY or V2RAY"
  exit 2
fi
if [[ "$vpnOrNot" != "YES" && "$vpnOrNot" != "NO" ]] 
then
  echo "Wrong value: $vpnOrNot Must be YES or NO"
  exit 2
fi
if [[ "$subnetOrIP" != "SUBNET" && "$subnetOrIP" != "IP" ]] 
then
  echo "Wrong value: $subnetOrIP Must be SUBNET or IP"
  exit 2
fi
if [[ "$downloadOrUpload" != "DOWN" && "$downloadOrUpload" != "UP" && "$downloadOrUpload" != "BOTH" ]] 
then
  echo "Wrong value: $downloadOrUpload Must be DOWN or UP or BOTH"
  exit 2
fi

if [[ "$subnetIPFile" != "NULL" ]]
then
  if ! [[ -f "$subnetIPFile" ]]
  then
    echo "file does not exists: $subnetIPFile"
    exit 1
  fi
fi

now=$(date +"%Y%m%d-%H%M%S")
scriptDir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
resultDir="$scriptDir/result"
resultFile="$resultDir/$now-result.cf"
tempConfigDir="$scriptDir/tempConfig"
filesDir="$tempConfigDir"

uploadFile="$filesDir/upload_file"

configId="NULL"
configHost="NULL"
configPort="NULL"
configPath="NULL"

progressBar=""

export GREEN='\033[0;32m'
export BLUE='\033[0;34m'
export RED='\033[0;31m'
export ORANGE='\033[0;33m'
export YELLOW='\033[1;33m'
export NC='\033[0m'

fncCreateDir "${resultDir}"
fncCreateDir "${tempConfigDir}"
echo "" > "$resultFile"

if [[ "$config" == "NULL"  ]]
then
  echo "updating config"
  configRealUrlResult=$(curl -I -L -s "$clientConfigFile" | grep "^HTTP" | grep 200 | awk '{ print $2 }')
  if [[ "$configRealUrlResult" == "200" ]]
  then
    curl -s "$clientConfigFile" -o "$scriptDir"/config.default
    echo "config.default updated with $clientConfigFile"
    echo ""
    config="$scriptDir/config.default"
    cat "$config"
  else
    echo ""
    echo "config file is not available $clientConfigFile"
    echo "use your own"
    echo "" 
    exit 1
  fi
else
  echo ""
  echo "using your own config $config"
  cat "$config"
  echo ""
fi

fileSize="$(( 2*speed*1024 ))"
if [[ "$downloadOrUpload" == "DOWN" || "$downloadOrUpload" == "BOTH" ]]
then
  echo "You are testing download"
fi
if [[ "$downloadOrUpload" == "UP" || "$downloadOrUpload" == "BOTH" ]]
then
  echo "You are testing upload"
  echo "making upload file by size $fileSize Bytes in $uploadFile"
  ddSize="$(( 2*speed ))"
  dd if=/dev/random of="$uploadFile" bs=1024 count="$ddSize" > /dev/null 2>&1
fi

fncValidateConfig "$config"

if [[ "$subnetOrIP" == "SUBNET" ]]
then
  fncMainCFFindSubnet "$threads" "$progressBar" "$resultFile" "$scriptDir" "$configId" "$configHost" "$configPort" "$configPath" "$fileSize" "$osVersion" "$subnetIPFile" "$tryCount" "$downThreshold" "$upThreshold" "$downloadOrUpload" "$vpnOrNot" "$quickOrNot"
elif [[ "$subnetOrIP" == "IP" ]]
then
  fncMainCFFindIP "$threads" "$progressBar" "$resultFile" "$scriptDir" "$configId" "$configHost" "$configPort" "$configPath" "$fileSize" "$osVersion" "$subnetIPFile" "$tryCount" "$downThreshold" "$upThreshold" "$downloadOrUpload" "$vpnOrNot" "$quickOrNot"
else
  echo "$subnetOrIP is not correct choose one SUBNET or IP"
  exit 1
fi
