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.

asked May 11, 2015 at 16:16
\$\endgroup\$
2
  • \$\begingroup\$ I have rolled back Rev 7 → 6. See What should I do when someone answers my question? \$\endgroup\$ Commented May 11, 2015 at 19:40
  • 1
    \$\begingroup\$ You could simply use inet_ntop(inet_pton($ip)); it'll give the short representation \$\endgroup\$ Commented Jun 11, 2020 at 8:17

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
answered May 11, 2015 at 16:32
\$\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\$ 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\$ Commented May 11, 2015 at 17:39
  • \$\begingroup\$ Implemented. :) \$\endgroup\$ 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
}
answered May 11, 2015 at 19:48
\$\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

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.