8
\$\begingroup\$

I wrote the following function in PHP to represent IPv6 addresses as short as possible:

function ipv6_compress($ip){
    // Shorten first group of zeros
    if(substr($ip, 0, 4) == 0000) $ip = substr_replace($ip, '0', 0, 4);
    // Shorten full groups of zeros
    $ip = str_replace('0000:', '0:', $ip);
    // Remove leading zeros
    $ip = preg_replace('/:0{1,3}(?=\w)/', ':', $ip);
    // Remove longest extra group of zeros per [RFC 5952](https://www.rfc-editor.org/rfc/rfc5952)
    if(strpos('::') !== false) return $ip; // But don't if a :: is already present as entered
    $pos = strpos($ip, '0:0:0:0:0:0:0:');
    if($pos !== false) return $ip = substr_replace($ip, '::', $pos, 14);
    $pos = strpos($ip, '0:0:0:0:0:0:');
    if($pos !== false) return $ip = substr_replace($ip, '::', $pos, 12);
    $pos = strpos($ip, '0:0:0:0:0:');
    if($pos !== false) return $ip = substr_replace($ip, '::', $pos, 10);
    $pos = strpos($ip, '0:0:0:0:');
    if($pos !== false) return $ip = substr_replace($ip, '::', $pos, 8);
    $pos = strpos($ip, '0:0:0:');
    if($pos !== false) return substr_replace($ip, '::', $pos, 6);
    $pos = strpos($ip, '0:0:');
    if($pos !== false) return substr_replace($ip, '::', $pos, 4);
    return $ip;
}

I attempt to adhere to RFC 5952.

  1. Do I violate the standard in any way?
  2. Can I do anything more efficiently?

As you can probably tell, I am not concerned with validating the addresses.

\$\endgroup\$
2

2 Answers 2

4
\$\begingroup\$

You seem to adhere to the RFC from what I can tell. What I feel is missing in your code is just a little more bracing and a way to drop these repeating if-statements.

What do you think of the following for the :: replacing (pseudocode)

const $zero_chain = '0:0:0:0:0:0:0:';

// zero shortening already happened by now    
$zero_chain_copy = $zero_chain;
while (strpos($ip, '::') === false && strlen($zero_chain) >= 4) {
    $pos = strpos($ip, $zerochain);
    if ($pos !== false) {
        // early return, because no further shortenings can be applied anyways
        return $ip = substr_replace($ip, ::, $pos, strlen($zero_chain));
    }
    // cut away one '0:' to shorten the chain
    $zero_chain = substr($zerochain, 0, strlen($zero_chain) - 2);
}
return $ip; // no zero-chains at all
\$\endgroup\$
3
  • \$\begingroup\$ I like the idea of this, but wouldn't it be less efficient to keep changing the chain than it is to run through the 6 if statements? \$\endgroup\$
    – Mooseman
    Commented May 11, 2015 at 17:04
  • \$\begingroup\$ I think the clarity and simplicity you gain is a net benefit. Overall string manipulations like substring are cheap by comparison. I'd use it as proposed until it's proven to be a performance issue. \$\endgroup\$
    – Vogel612
    Commented May 11, 2015 at 17:39
  • \$\begingroup\$ Implemented. :) \$\endgroup\$
    – Mooseman
    Commented May 11, 2015 at 19:33
1
\$\begingroup\$

I implemented the ideas in this answer and squashed other bugs as follows:

  • Some addresses such as 2001:0db8:0000:0000:0000:ff00:0042:8329 could contain ::: rather than ::.
  • The loopback address (0000:0000:0000:0000:0000:0000:0000:0001) was abbreviated to :1 rather than ::1.

Here's the code I'm using now.

function ipv6_compress($ip){
    // Shorten first group of zeros
    if(substr($ip, 0, 4) === '0000') $ip = substr_replace($ip, ':0', 0, 4);
    // Shorten full groups of zeros
    $ip = str_replace(':0000', ':0', $ip);
    // Remove leading zeros
    $ip = preg_replace('/:0{1,3}(?=\w)/', ':', $ip); //return $ip;
    // Remove longest extra group of zeros per [RFC 5952](http://tools.ietf.org/html/rfc5952)
    $z = ':0:0:0:0:0:0:0:'; // Set chain
    while(strpos($ip, '::') === false && strlen($z) >= 5){ // While no :: and chain still possible
        $pos = strpos($ip, $z);
        if($pos !== false){ $ip = substr_replace($ip, '::', $pos, strlen($z)); break; } // Replace chain and break
        $z = substr($z, 0, strlen($z) - 2); // cut away one '0:' to shorten the chain
    }
    if(substr($ip, 1, 1) !== ':') return ltrim($ip, ':'); // Remove initial : if not a ::
    return $ip; // return
}
\$\endgroup\$
1
  • \$\begingroup\$ You could simply use inet_ntop(inet_pton($ip)); it'll give the short representation \$\endgroup\$ Commented Jun 11, 2020 at 8:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.