<?php

// I use some dirty constructs in the building of the benchtable, so ignore notices :P
error_reporting(E_ALL E_NOTICE);

// This seems to be overall the fastest way to do IPv4 checking in PHP
function validateIPv4($IP)
{
    return 
$IP == long2ip(ip2long($IP));
}

// This is an alternative to the one above using a regular expression
function validateIPv4_alt($IP)
{
    return 
preg_match('/^(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/'$IP);
}

// Breaking the IP apart, only checking the hexparts with a regexp. Rather slow...
function validateIPv6_1($IP)
{
    if (
strlen($IP) < 3)
        return 
$IP == '::';

    
$ipParts explode(':'$IP);
    
$hexCount 0;
    
$emptyCount 0;
    
$checkIPv4 false;

    for (
$i 0$l count($ipParts); $i $l$i++)
    {
        if (
$checkIPv4 !== false || $emptyCount 1)
            return 
false;

        
$ipPart $ipParts[$i];

        if (
$ipPart === '')
        {
            if (
$i == 0)
            {
                if (
$ipParts[$i 1] !== '')
                    return 
false;

                
$hexCount++;
                
$i++;
            }
            elseif (
$i == $l 1)
            {
                if (
$ipParts[$i 1] !== '')
                    return 
false;

                
$hexCount++;
                break;
            }

            
$emptyCount++;
        }
        elseif (
preg_match('/^[0-9a-f]{1,4}$/i'$ipPart))
        {
            
$hexCount++;
        }
        else
        {
            
$checkIPv4 $ipPart;
        }
    }

    if (
$emptyCount 1)
        return 
false;

    if (
$checkIPv4 !== false)
    {
        if (!
validateIPv4($checkIPv4))
            return 
false;

        
$hexCount += 2;
    }

    return 
$emptyCount $hexCount $hexCount == 8;
}

// This is a single regexp-approach based on rfc3986 that actually passes all of my testcases, it is reasonably performant
function validateIPv6_2($IP)
{
    return 
preg_match('/^
            (?:
                (?:
                        (?:[a-f0-9]{1,4}:){6}
                    |
                        ::(?:[a-f0-9]{1,4}:){0,4}
                    |
                        (?:[a-f0-9]{1,4}:){1,1}:(?:[a-f0-9]{1,4}:){4}
                    |
                        (?:[a-f0-9]{1,4}:){1,2}:(?:[a-f0-9]{1,4}:){3}
                    |
                        (?:[a-f0-9]{1,4}:){1,3}:(?:[a-f0-9]{1,4}:){2}
                    |
                        (?:[a-f0-9]{1,4}:){1,4}:(?:[a-f0-9]{1,4}:){1}
                    |
                        (?:[a-f0-9]{1,4}:){1,5}:
                )
                (?:
                        [a-f0-9]{1,4}:[a-f0-9]{1,4}
                    |
                        (?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
                )
                |
                    (?::|(?:[a-f0-9]{1,4}:){1,6}):(?:[a-f0-9]{1,4})?
            )
            $/ix'
,
            
$IP
    
);
}

// This is a bit of an hybrid approach; first it checks if the last part is in IPv4 format
// and validates that part first, then checks if the address is in compressed format or not
// and uses far more simple expressions to validate the address (also taking into account
// the number of colons to validate the compressed format)
// There is a nifty trick involved which replaces the IPv4 format-part with '0:0'. There's
// a number of ways to do that, some alternatives have been commented here as well
// I personally like the first one because it re-uses the validation function for IPv4 addresses
// reducing duplication of functionality
function validateIPv6_3($IP)
{
    if (
strlen($IP) < 3)
        return 
$IP == '::';

    if (
strpos($IP'.'))
    {
        
$lastcolon strrpos($IP':');
        if (!(
$lastcolon && validateIPv4(substr($IP$lastcolon 1))))
            return 
false;

        
$IP substr($IP0$lastcolon) . ':0:0';
/*
        $ipv4 = 0;
        $IP = preg_replace(
            '/:(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/',
            ':0:0',
            $IP,
            1,
            $ipv4
        );

        if (!$ipv4)
            return false;
*/
/*
        if (!preg_match('/:(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/', $IP))
            return false;

        $IP = substr($IP, 0, strrpos($IP, ':')) . ':0:0';
*/
    
}

    if (
strpos($IP'::') === false)
    {
        return 
preg_match('/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i'$IP);
    }

    if (
substr_count($IP':') < 8)
    {
        return 
preg_match('/^(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?$/i'$IP);
    }

    return 
false;
}

// This is actually an alternative approach to validateIPv6_3 which branches the checking
// regexp based on whether it has an IPv4 part or not
// It is slightly faster, but I don't like how large parts of the expressions are duplicated
function validateIPv6_4($IP)
{
    if (
strlen($IP) < 3)
        return 
$IP == '::';

    if (
strpos($IP'.'))
    {
        if (
strpos($IP'::') === false)
        {
            return 
preg_match('/^(?:[a-f0-9]{1,4}:){6}(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/i'$IP);
        }

        if (
substr_count($IP':') < 7)
        {
            return 
preg_match('/^(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$/i'$IP);
        }
    }
    else
    {
        if (
strpos($IP'::') === false)
        {
            return 
preg_match('/^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i'$IP);
        }

        if (
substr_count($IP':') < 8)
        {
            return 
preg_match('/^(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?$/i'$IP);
        }
    }

    return 
false;
}

// This is the expression that is being used(?) by CakePHP (https://trac.cakephp.org/ticket/5872)
// - it has the most failures in my testcases :P
function validateIPv6_5($IP)
{
    return 
preg_match('/^
            (?:
                    (?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}
                |
                    [a-f0-9]{0,4}::
                |
                    :(?::[a-f0-9]{1,4}){1,6}
                |
                    (?:[a-f0-9]{1,4}:){1,6}:
                |
                    (?:[a-f0-9]{1,4}:)(?::[a-f0-9]{1,4}){1,6}
            )
            |
                (?:[a-f0-9]{1,4}:){2}(?::[a-f0-9]{1,4}){1,5}
            |
                (?:[a-f0-9]{1,4}:){3}(?::[a-f0-9]{1,4}){1,4}
            |
                (?:[a-f0-9]{1,4}:){4}(?::[a-f0-9]{1,4}){1,3}
            |
                (?:[a-f0-9]{1,4}:){5}(?::[a-f0-9]{1,4}){1,2}
            |
                (?:[a-f0-9]{1,4}:){6}(?::[a-f0-9]{1,4})
            |
                (?:0:){5}ffff:(?:\d{1,3}\.){3}\d{1,3}
            |
                (?:0:){6}(?:\d{1,3}\.){3}\d{1,3}
            |
                ::(?:ffff:)?(?:\d{1,3}\.){3}\d{1,3}
        $/ix'
$IP);
}

// This is a regexp based on rfc2732. It is reasonably fast but incorrect on many points
// (also why rfc2732 has been superceeded by rfc3986)
function validateIPv6_6($IP)
{
    return 
preg_match('/^
            (?:
                    [a-f0-9]{1,4}(?::[a-f0-9]{1,4})*
                |
                    [a-f0-9]{1,4}(?::[a-f0-9]{1,4})*::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4})*)?
                |
                    ::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4})*)?
            )
            (?::\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?
        $/ix'
$IP);
}

// This is probably the fastest way to do IPv6 validation in PHP, however it is not
// 100% rfc-compliant so it fails on a couple of testcases
function validateIPv6_7($IP)
{
    return 
filter_var($IPFILTER_VALIDATE_IPFILTER_FLAG_IPV6);
}

// testcases; a value of true means it is a valid IPv6 address, false means it is invalid
$unitTests = array(
    
'2001:0db8:0000:0000:0000:0000:1428:57ab'    => true,
    
'2001:0DB8:0000:0000:0000:0000:1428:57AB'    => true,
    
'2001:00db8:0000:0000:0000:0000:1428:57ab'    => false,
    
'2001:0db8:xxxx:0000:0000:0000:1428:57ab'    => false,

    
'2001:db8::1428:57ab'    => true,
    
'2001:db8::1428::57ab'    => false,
    
'2001:dx0::1234'    => false,
    
'2001:db0::12345'    => false,

    
''            => false,
    
':'            => false,
    
'::'            => true,
    
':::'            => false,
    
'::::'            => false,
    
'::1'            => true,
    
':::1'            => false,

    
'::1.2.3.4'        => true,
    
'::256.0.0.1'        => false,
    
'::01.02.03.04'        => false,
    
'a:b:c::1.2.3.4'    => true,
    
'a:b:c:d::1.2.3.4'    => true,
    
'a:b:c:d:e::1.2.3.4'    => true,
    
'a:b:c:d:e:f:1.2.3.4'    => true,
    
'a:b:c:d:e:f:1.256.3.4'    => false,
    
'a:b:c:d:e:f::1.2.3.4'    => false,

    
'a:b:c:d:e:f:0:1:2'    => false,
    
'a:b:c:d:e:f:0:1'    => true,
    
'a::b:c:d:e:f:0:1'    => false,
    
'a::c:d:e:f:0:1'    => true,
    
'a::d:e:f:0:1'        => true,
    
'a::e:f:0:1'        => true,
    
'a::f:0:1'        => true,
    
'a::0:1'        => true,
    
'a::1'            => true,
    
'a::'            => true,

    
'::0:1:a:b:c:d:e:f'    => false,
    
'::0:a:b:c:d:e:f'    => false,
    
'::a:b:c:d:e:f'        => true,
    
'::b:c:d:e:f'        => true,
    
'::c:d:e:f'        => true,
    
'::d:e:f'        => true,
    
'::e:f'            => true,
    
'::f'            => true,

    
'0:1:a:b:c:d:e:f::'    => false,
    
'0:a:b:c:d:e:f::'    => false,
    
'a:b:c:d:e:f::'        => true,
    
'b:c:d:e:f::'        => true,
    
'c:d:e:f::'        => true,
    
'd:e:f::'        => true,
    
'e:f::'            => true,
    
'f::'            => true,

    
'a:b:::e:f'        => false,
    
'::a:'            => false,
    
'::a::'            => false,
    
':a::b'            => false,
    
'a::b:'            => false,
    
'::a:b::c'        => false,
    
'abcde::f'        => false,

    
'10.0.0.1'        => false,
    
':10.0.0.1'        => false,
    
'0:0:0:255.255.255.255'    => false,
    
'1fff::a88:85a3::172.31.128.1' => false,

    
'a:b:c:d:e:f:0::1'    => false,
    
'a:b:c:d:e:f:0::'    => false,
    
'a:b:c:d:e:f::0'    => true,
    
'a:b:c:d:e:f::'        => true,

    
'total gibberish'    => false
);

$benchmarks = array();
$iterations 100;
// only test the first 4 functions which pass all the unittests
$numfunctions 4;

foreach (
$unitTests as $ip => $assertion)
{
    
$benchmarks[$ip] = array();
    for (
$i 1$i <= $numfunctions$i++)
    {
        
$j $iterations;
        
$f 'validateIPv6_' $i;
        
$s microtime(true);
        while (
$j--)
        {
            
$f($ip);
        }
        
$e microtime(true);
        
$benchmarks[$ip][$i] = $e $s;
    }
}

// print table
echo '<table border=1><tr>';
for (
$i 0$i <= $numfunctions$i++)
{
    echo 
'<td>' . ($i 'validateIPv6_' $i '&nbsp;') . '</td>';
}
echo 
'</tr>';

$totals = array(
    
'valid' => array(),
    
'invalid' => array(),
    
'grand' => array()
);

foreach (
$unitTests as $ip => $assertion)
{
    echo 
'<tr><td style="color:' . ($assertion 'green' 'red') . '">' . ($ip $ip '&nbsp') . '</td>';
    for (
$i 1$i <= $numfunctions$i++)
    {
        
$f 'validateIPv6_' $i;
        
$result $f($ip);
        
$min min($benchmarks[$ip]);
        
$correct = (bool)$result == $assertion;

        
$totals['grand'][$i] += $benchmarks[$ip][$i];
        if (
$assertion)
            
$totals['valid'][$i] += $benchmarks[$ip][$i];
        else
            
$totals['invalid'][$i] += $benchmarks[$ip][$i];

        echo 
'<td style="text-align:right' . ($correct '' ';color:red') . ($benchmarks[$ip][$i] == $min ';background:lime' '') . '">' number_format($benchmarks[$ip][$i], 3'.''') . '</td>';
    }
    echo 
'</tr>';
}

foreach (
$totals as $label => $results)
{
    echo 
'<tr><td><b>' $label '</td>';
    for (
$i 1$i <= $numfunctions$i++)
    {
        
$min min($results);
        echo 
'<td style="text-align:right' . ($results[$i] == $min ';background:lime' '') . '">' number_format($results[$i], 3'.''') . '</td>';
    }
    echo 
'</tr>';
}

echo 
'</table>';

?>