<?php

/* * ********************************************************************
 * Proxmox Addon  Product developed. (2013-11-18)
 * *
 *
 *  CREATED BY MODULESGARDEN       ->       http://modulesgarden.com
 *  CONTACT                        ->       contact@modulesgarden.com
 *
 *
 * This software is furnished under a license and may be used and copied
 * only  in  accordance  with  the  terms  of such  license and with the
 * inclusion of the above copyright notice.  This software  or any other
 * copies thereof may not be provided or otherwise made available to any
 * other person.  No title to and  ownership of the  software is  hereby
 * transferred.
 *
 *
 * ******************************************************************** */

namespace ModulesGarden\ProxmoxAddon\App\Libs;

if (!function_exists('ipv6_range'))
{

    function ipv6_range($prefix)
    {
        // Split in address and prefix length
        list($firstaddrstr, $prefixlen) = explode('/', $prefix);

        // Parse the address into a binary string
        $firstaddrbin = inet_pton($firstaddrstr);

        // Convert the binary string to a string with hexadecimal characters
        # unpack() can be replaced with bin2hex()
        # unpack() is used for symmetry with pack() below
        $unpacked     = unpack('H*', $firstaddrbin);
        $firstaddrhex = reset($unpacked);

        // Overwriting first address string to make sure notation is optimal
        $firstaddrstr = inet_ntop($firstaddrbin);

        // Calculate the number of 'flexible' bits
        $flexbits = 128 - $prefixlen;

        // Build the hexadecimal string of the last address
        $lastaddrhex = $firstaddrhex;

        // We start at the end of the string (which is always 32 characters long)
        $pos = 31;
        while ($flexbits > 0)
        {
            // Get the character at this position
            $orig = substr($lastaddrhex, $pos, 1);

            // Convert it to an integer
            $origval = hexdec($orig);

            // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
            $newval = $origval | (pow(2, min(4, $flexbits)) - 1);

            // Convert it back to a hexadecimal character
            $new = dechex($newval);

            // And put that character back in the string
            $lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);

            // We processed one nibble, move to previous position
            $flexbits -= 4;
            $pos      -= 1;
        }

        // Convert the hexadecimal string to a binary string
        # Using pack() here
        # Newer PHP version can use hex2bin()
        $lastaddrbin = pack('H*', $lastaddrhex);

        // And create an IPv6 address from the binary string
        $lastaddrstr = inet_ntop($lastaddrbin);

        return [$prefix, $firstaddrstr, $lastaddrstr];
    }
}

/**
 * Converts human readable representation to a 128bit int
 * which can be stored in MySQL using DECIMAL(39,0).
 *
 * Requires PHP to be compiled with IPv6 support.
 * This could be made to work without IPv6 support but
 * I don't think there would be much use for it if PHP
 * doesn't support IPv6.
 *
 * @param string $ip IPv4 or IPv6 address to convert
 * @return string 128bit string that can be used with DECIMNAL(39,0) or false
 */
if (!function_exists('inet_ptoi'))
{

    function inet_ptoi($ip)
    {
        // make sure it is an ip
        if (filter_var($ip, FILTER_VALIDATE_IP) === false)
        {
            return false;
        }

        $parts = unpack('N*', inet_pton($ip));

        // fix IPv4
        if (strpos($ip, '.') !== false)
        {
            $parts = [1 => 0, 2 => 0, 3 => 0, 4 => $parts[1]];
        }

        foreach ($parts as &$part)
        {
            // convert any unsigned ints to signed from unpack.
            // this should be OK as it will be a PHP float not an int
            if ($part < 0)
            {
                $part += 4294967296;
            }
        }

        // use BCMath if the extension exists
        if (function_exists('bcadd'))
        {
            $decimal = $parts[4];
            $decimal = bcadd($decimal, bcmul($parts[3], '4294967296'));
            $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616'));
            $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336'));
        }
        // otherwise use the pure PHP BigInteger
        else
        {
            $decimal = new BigInteger($parts[4]);
            $part3   = new BigInteger($parts[3]);
            $part2   = new BigInteger($parts[2]);
            $part1   = new BigInteger($parts[1]);

            $decimal = $decimal->add($part3->multiply(new BigInteger('4294967296')));
            $decimal = $decimal->add($part2->multiply(new BigInteger('18446744073709551616')));
            $decimal = $decimal->add($part1->multiply(new BigInteger('79228162514264337593543950336')));

            $decimal = $decimal->toString();
        }

        return $decimal;
    }
}

/**
 * Converts a 128bit int to a human readable representation.
 *
 * Requires PHP to be compiled with IPv6 support.
 * This could be made to work without IPv6 support but
 * I don't think there would be much use for it if PHP
 * doesn't support IPv6.
 *
 * @param string $decimal 128bit int
 * @return string IPv4 or IPv6
 */
if (!function_exists('inet_itop'))
{

    function inet_itop($decimal)
    {
        $parts = [];

        // use BCMath if the extension exists
        if (function_exists('bcadd'))
        {
            $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0);
            $decimal  = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
            $parts[2] = bcdiv($decimal, '18446744073709551616', 0);
            $decimal  = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
            $parts[3] = bcdiv($decimal, '4294967296', 0);
            $decimal  = bcsub($decimal, bcmul($parts[3], '4294967296'));
            $parts[4] = $decimal;
        }
        // otherwise use the pure PHP BigInteger
        else
        {
            $decimal = new BigInteger($decimal);
            list($parts[1],) = $decimal->divide(new BigInteger('79228162514264337593543950336'));
            $decimal = $decimal->subtract($parts[1]->multiply(new BigInteger('79228162514264337593543950336')));
            list($parts[2],) = $decimal->divide(new BigInteger('18446744073709551616'));
            $decimal = $decimal->subtract($parts[2]->multiply(new BigInteger('18446744073709551616')));
            list($parts[3],) = $decimal->divide(new BigInteger('4294967296'));
            $decimal  = $decimal->subtract($parts[3]->multiply(new BigInteger('4294967296')));
            $parts[4] = $decimal;

            $parts[1] = $parts[1]->toString();
            $parts[2] = $parts[2]->toString();
            $parts[3] = $parts[3]->toString();
            $parts[4] = $parts[4]->toString();
        }

        foreach ($parts as &$part)
        {
            // convert any signed ints to unsigned for pack
            // this should be fine as it will be treated as a float
            if ($part > 2147483647)
            {
                $part -= 4294967296;
            }
        }

        $ip = inet_ntop(pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]));

        // fix IPv4 by removing :: from the beginning
        if (strpos($ip, '.') !== false)
        {
            return substr($ip, 2);
        }

        return $ip;
    }
}