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.
- Do I violate the standard in any way?
- Can I do anything more efficiently?
As you can probably tell, I am not concerned with validating the addresses.
-
\$\begingroup\$ I have rolled back Rev 7 → 6. See What should I do when someone answers my question? \$\endgroup\$200_success– 200_success2015年05月11日 19:40:16 +00:00Commented May 11, 2015 at 19:40
-
1\$\begingroup\$ You could simply use inet_ntop(inet_pton($ip)); it'll give the short representation \$\endgroup\$Giso Stallenberg– Giso Stallenberg2020年06月11日 08:17:28 +00:00Commented Jun 11, 2020 at 8:17
2 Answers 2
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
-
\$\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– Mooseman2015年05月11日 17:04:43 +00:00Commented 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– Vogel6122015年05月11日 17:39:17 +00:00Commented May 11, 2015 at 17:39
-
\$\begingroup\$ Implemented. :) \$\endgroup\$Mooseman– Mooseman2015年05月11日 19:33:54 +00:00Commented May 11, 2015 at 19:33
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
}
-
\$\begingroup\$ You could simply use inet_ntop(inet_pton($ip)); it'll give the short representation \$\endgroup\$Giso Stallenberg– Giso Stallenberg2020年06月11日 08:18:23 +00:00Commented Jun 11, 2020 at 8:18