src/Modules/Scanner/Scanner.ps1

. "$([System.IO.Path]::Combine($PSScriptRoot, 'Private', 'ScannerApplication.ps1'))"

function scanner {
    param(
        [string[]]$targets,
        [switch]$quickScan,
        [int]$pMin,
        [int]$pMax,
        [string[]]$ports
    )

    function scannerTransport {
        if ($targets[0] -eq "") {
            Write-Host "You must specify at least one target with -targets.`nExiting now." @Pen
            return
        }

        $garudaDataDir = [System.IO.Path]::Combine([Environment]::GetFolderPath('LocalApplicationData'), 'Garuda')
        $PortListPath = [System.IO.Path]::Combine($garudaDataDir, 'ports.txt')

        . "$([System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, 'Private', 'PopulatePortsHash.ps1')))"
        . "$([System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, 'Private', 'UpdatePortDatabase.ps1')))"

        $portsHashTable = updatePortDatabase

        $result = [System.Collections.Concurrent.ConcurrentDictionary[object, object]]::new() 

        foreach ($target in $targets) {
            try {
                $resolvedIP = [System.Net.Dns]::GetHostAddresses($target)[0]
                $TargetIP = $resolvedIP.IPAddressToString
                $TargetFamily = $resolvedIP.AddressFamily
            } catch {
                Write-Warning "Failed to find IP for host: $target. Skipping this target..."
                continue
            }

            . "$([System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, 'Private', 'PortToScan.ps1')))"
            
            $portsToScan = portToScan -QuickScan:$quickScan -Ports $ports -PMin $pMin -PMax $pMax
            $totalPorts = $portsToScan.Count

            if ($totalPorts -gt 0) {
                0..($totalPorts - 1) | ForEach-Object -Parallel {
                    $index = $_
                    $portsToScan = $using:portsToScan
                    $port = $portsToScan[$index]
                    
                    $Target = $using:target
                    $TargetIP = $using:TargetIP
                    $TargetFamily = $using:TargetFamily
                    $portsHashTable = $using:portsHashTable
                    $portInt = [Int]$port
                    $localResult = $using:result
                    $totalPorts = $using:totalPorts

                    $completed = (($index + 1) / $totalPorts) * 100
                    Write-Progress -Activity "Scanning ${Target}:$port" -Status "$([math]::Round($completed, 2))% complete" -PercentComplete $completed

                    $obj = [System.Net.Sockets.Socket]::new(
                        $TargetFamily, 
                        [System.Net.Sockets.SocketType]::Stream, 
                        [System.Net.Sockets.ProtocolType]::Tcp
                    )

                    $obj.NoDelay = $true
                    $obj.SendTimeout = ($TargetIP -match '^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^127\.') ? 100 : 500
                    $obj.ReceiveTimeout = $obj.SendTimeout

                    $ip = [System.Net.IPAddress]::Parse($TargetIP)
                    $endpoint = [System.Net.IPEndPoint]::new($ip, $port)
                    
                    try {
                        $connect = $obj.BeginConnect($endpoint, $null, $null)
                        $Wait = $connect.AsyncWaitHandle.WaitOne($obj.SendTimeout, $false)

                        if (-not $Wait) {
                            Write-Verbose -Message "$Target 'port' $port 'Closed - Timeout'" -Verbose
                        }
                        else {
                            if ($obj.Connected) {
                                $obj.EndConnect($connect)

                                $value = "Open"
                                Write-Verbose -Message "$Target 'port' $port Open'" -Verbose

                                if ($portsHashTable.ContainsKey($portInt)) {
                                    $Service = $portsHashTable[$portInt].Split('|')
                                }
                                else {
                                    $Service = @("Unknown", "Unknown")
                                }

                                $r = [PSCustomObject]@{
                                    Host = $Target
                                    Port = $port
                                    State = $value
                                    L4_Service = $Service[0]
                                    "IANA Standard Description" = $Service[1]
                                }

                                $key = $Target + ":" + $port
                                $localResult[$key] = $r
                            }
                            else {
                                Write-Verbose -Message "$Target 'port' $port 'Closed - Refused'" -Verbose
                            }
                        }
                    } catch {
                        Write-Verbose -Message "$Target 'port' $port 'Error: $($_.Exception.Message)'" -Verbose
                    } finally {
                        $obj.Close()
                        $obj.Dispose()
                    }
                } @ThrottleCreat
            }
        }

        Write-Host "`n[+] Layer 4 Scan Results:" @App
        
        $result.Values | ForEach-Object {
            $dto = [HostResult]::new($_.Host)
            $dto.Port = $_.Port
            $dto.State = $_.State
            $dto.Service = $_.L4_Service
            $dto.IANADescription = $_."IANA Standard Description"
            $dto
        } | Sort-Object IPAddress, Port | Select-Object IPAddress, Port, State, Service, IANADescription | Format-Table -AutoSize

        $phase1Data = $result.Values | Sort-Object host, port 
        $openPorts = $phase1Data | Where-Object { $_.State -eq "Open" }

        if ($openPorts.Count -gt 0) {
            Write-Host ""
            $answer = Read-Host "There are $($openPorts.Count) open ports. Proceed with Layer 7 validation? (y/n)"
            if ($answer -match "^y") {
                scannerApplication -OpenPorts $openPorts
            } else {
                Write-Host "Scanning stopped at Layer 4."
            }
        } else {
            Write-Host "`nNo open ports were found for Layer 7 validation." @Cha
            Write-Host "[*] Possible reasons for empty results:" @Dim
            Write-Host " 1. Target Firewall/WAF/IPS packet drop." @Dim
            Write-Host " 2. Network latency exceeded timeout limit." @Dim
            Write-Host " 3. Local gateway NAT buffer overflow." @Dim
            Write-Host " 4. Outbound traffic rate-limited by ISP." @Dim
            Write-Host " 5. Local socket or thread exhaustion." @Dim
        }
    }

    scannerTransport
}