<?php

namespace App\Helpers\Utility;

use Symfony\Component\Process\Process;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use Symfony\Component\HttpFoundation\IpUtils;
use Illuminate\Support\Str;

class RouteList {
  
  /**
   * Return a list of cidr's split into ip and width
   */
    static function get_routes_url($_address, $_crush = true) {
        try {
            $exclude = static::get_excluded_blocks();

            // Get routelist from website.
            $raw = Http::get($_address);

            // Get CIDRs out of the list.
            $prefixes = [];

            preg_match_all('/((?:(?:(?:[A-Fa-f0-9]{1,4}:{1,2}){0,7}(?:[A-Fa-f0-9]{1,4}))|(?:(?:\d{1,3}\.?)){4})\/\d{1,3})/', $raw, $prefixes);

            $prefixes = $prefixes[0];

            // Run crusher if specified.
            if($_crush) {
                $crusher_process = new Process(['python3', './app/Bin/SubnetCrusher.py']);
                $crusher_process->setInput(implode(' ', $prefixes));
                $crusher_process->run();
                            
                if($crusher_process->isSuccessful()) {
                    $prefixes = explode("\n", rtrim($crusher_process->getOutput()));
                }
                else {
                  $prefixes = null;
                }
            }

            if($prefixes != null) {
                $return = [];

                foreach($prefixes as $prefix) {
                    if($prefix != '') {
                        if(!static::ip_in_blocks($prefix, $exclude)) {
                            [$subnet, $mask] = explode('/', $prefix);

                            $subnet = inet_ntop(inet_pton($subnet));
                            
                            $return[] = ['ip' => $subnet, 'width' => $mask, 'full' => $subnet . '/' . $mask];
                        }
                    }

                }
    
                return $return;
            }
        }
        catch(Exception $ex){}

        return [];
    }

    static function ip_in_blocks( $ip, $blocks ) {
        [$ip_subnet, $ip_mask] = explode('/', $ip);
        
        if(Str::contains($ip, ':')) {
        // Ganked and modified from: https://stackoverflow.com/questions/49365018/how-to-check-if-an-ipv6-address-is-in-a-specific-network-for-acl-implementation
            $ip_subnet = inet_pton($ip_subnet);
            $ip_length = strlen($ip_subnet);
    
            if (strlen($ip_subnet) < 4) {
                return false;
            }
            foreach ($blocks as $block) {
                $ar = explode('/',$block);

                if($ip_mask <= $ar[1]) {
                    continue;
                }
                
                if($ip === $block) {
                    return true;
                }
    
                $ip1 = $ar[0];
                $ip1b = inet_pton($ip1);
                $ip1len = strlen($ip1b);
    
                if ($ip1len != $ip_length) {
                    continue;
                }
                if (count($ar)>1) {
                    $bits=(int)($ar[1]);
                } else {
                    $bits = $ip_length * 8;
                }
                for ($c=0; $bits>0; ++$c) {
                    $bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff;
                    if (((ord($ip_subnet[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0)
                        continue 2;
                    $bits-=8;
                }
                return true;
            }
        }
        else {
            foreach($blocks as $block) {
                [$block_subnet, $block_mask] = explode('/', $block);

                if($ip_mask <= $block_mask) {
                    continue;
                }

                if ($ip === $block || ((ip2long($ip_subnet) & ~((1 << (32 - $block_mask)) - 1) ) === ip2long($block_subnet)))
                {
                    return true;
                }
            }
        }

        return false;
    }

    static function ip_in_block( $ip, $block ) {
        if($ip === $block) {
            return true;
        }

        [$ip_subnet, $ip_mask] = explode('/', $ip);

        if(Str::contains($ip, ':')) {
            // Ganked and modified from: https://stackoverflow.com/questions/49365018/how-to-check-if-an-ipv6-address-is-in-a-specific-network-for-acl-implementation
            $ar = explode('/',$block);

            if($ip_mask <= $ar[1]) {
                return false;
            }
            
            $ip_subnet = inet_pton(explode('/', $ip)[0]);
            $ip_length = strlen($ip_subnet);
    
            if (strlen($ip_subnet) < 4) {
                return false;
            }
    
            $ip1 = $ar[0];
            $ip1b = inet_pton($ip1);
            $ip1len = strlen($ip1b);
    
            if ($ip1len != $ip_length) {
                return false;
            }
    
            if (count($ar)>1) {
                $bits=(int)($ar[1]);
            } else {
                $bits = $ip_length * 8;
            }
            for ($c=0; $bits>0; ++$c) {
                $bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff;
                if (((ord($ip_subnet[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0)
                    return false;
                $bits-=8;
            }
    
            return true;
        }
        else {
            [$block_subnet, $block_mask] = explode('/', $block);

            if($ip_mask <= $block_mask) {
                return false;
            }
    
            return (ip2long($ip_subnet) & ~((1 << (32 - $block_mask)) - 1) ) === ip2long($block_subnet);
        }

        return false;
    }

    static function get_excluded_blocks() {
        return [
            // Predefined
            //"0.0.0.0/8",
            "10.0.0.0/8",
            "100.64.0.0/10",
            "127.0.0.0/8",
            "169.254.0.0/16",
            "172.16.0.0/12",
            "192.0.0.0/24",
            "192.0.2.0/24",
            "192.88.99.0/24",
            "192.168.0.0/16",
            "198.18.0.0/15",
            "198.51.100.0/24",
            "203.0.113.0/24",
            "224.0.0.0/4",
            "233.252.0.0/24",
            "240.0.0.0/4",
            "255.255.255.255/32",
            // GSL
            '31.217.251.0/24',
            '103.137.13.0/24',
            '223.165.7.0/24',
        ];
    }

    static function valid_subnet($supernet_cidr, $subnet_cidr) {
        $subnet_mask = explode('/', $subnet_cidr)[1];

        // Calculate the number of IP addresses in the subnet
        $subnet_size = pow(2, 32 - $subnet_mask);
    
        list($supernet_ip, $supernet_mask) = explode('/', $supernet_cidr);
        
        // Convert the supernet to an array of bytes
        $supernet_bytes = explode('.', $supernet_ip);
        
        // Calculate the number of subnets that fit inside the supernet
        $class_cidr = 24;
    
        if($supernet_mask <= 8) {
            $class_cidr = 8;
        }
        elseif($supernet_mask <= 16) {
            $class_cidr = 16;
        }
    
        $num_subnets = pow(2, $subnet_mask - $class_cidr);
        
        // Calculate the number of IP addresses in each subnet
        $subnet_step = pow(2, 32 - $subnet_mask);
        
        // Initialize the first subnet to the first IP address in the supernet
        $subnet = ip2long($supernet_ip);
        
        // Generate the subnets
        $subnets = array();
        for ($i = 0; $i < $num_subnets; $i++) {
            // Generate the subnet IP address from the subnet base
            $subnet_ip = long2ip($subnet);
            
            // Add the subnet IP address to the subnets array
            $subnets[] = $subnet_ip.'/'.$subnet_mask;

            if("$subnet_ip/$subnet_mask" == $subnet_cidr) {
                return true;
            }
            
            // Increment the subnet base by the subnet size
            $subnet += $subnet_step;
        }
        
        return false;
      }
}

?>