Page 1 of 3

[Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 7:42 am
by VladSun
Write a validator for IP version 4 subnet masks in a decimal form. A valid subnet mask value is a one that has leftmost bits set to '1' and the rest rightmost bits set to '0'.
E.g.:
11111111 11111111 11111111 00000000 (i.e. 255.255.255.0) - valid
11111111 11111111 00111111 00000000 - invalid

In this challenge, these subnet masks are also considered invalid:
255.255.255.255 (11111111 11111111 11111111 11111111)
0.0.0.0 (00000000 00000000 00000000 00000000)

The function expected is:

Code: Select all

/**
 * @param String $mask - decimal, dot separated representation of a IPv4 subnet mask - e.g. "255.255.255.0"
 * @return bool
 */
function isValidIPv4Mask($mask)

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 9:34 am
by Weiry
Challenge Complete!!

I created a class rather than a single function.
I thought that it might be useful for some people to work in binary? So i added a flag to turn on binary comparison.
Code includes examples.

Code: Select all

<?php
#php subnet validator
class isValidIPv4Mask{

	public function __Construct(){}
	
	public function checkSubnet($mask,$isBin=false){	
		if(!$this->checkChallengeMask($mask,$isBin)){
			return false;
		}
		$segments = explode('.',$mask);
		if(!$isBin){
			return $this->checkValidity($this->convertToBin($segments));
		}
		return $this->checkValidity($segments);
	}
	
	private function convertToBin($segments){
		foreach($segments as $decimal){
			$binArr[] = decbin($decimal);
		}
		return $binArr;
	}
	
	private function checkChallengeMask($mask,$isBin=false){
		if(!$isBin){
			if(preg_match('/(0|255)\.(0|255)\.(0|255)\.(0|255)/',$mask)){	// check if mask is either 0.0.0.0 or 255.255.255.255
				return false;
			}
		}else{
			if(preg_match('/(0{8}|1{8})\.(0{8}|1{8})\.(0{8}|1{8})\.(0{8}|1{8})/',$mask)){// check if mask (in binary) is either 0.0.0.0 or 255.255.255.255
				return false;
			}
		}
		return true;
	}

	private function checkValidity($checkArr){
		$binStr = implode($checkArr);
		$regexFalse = array('/^0/','/(0+)(1+)(0+)/','/(1+)(0+)(1+)/');
		foreach($regexFalse as $regex){
			if(preg_match($regex,$binStr)){
				return false;
			}
		}
		if(preg_match('/^(1+)(0+)/',$binStr)){
			return true;
		}
		return false;
	}	
};
$subnetValidator = new isValidIPv4Mask();

$subArr = array("255.255.255.254","255.128.255.128","255.63.0.255","255.128.0.256","0.0.0.0","255.255.255.255");
$binArr = array("11111111.11111111.11111111.11111111","00000000.00000000.00000000.00000000","01111111.11111111.11111111.11111111","11111111.10000000.10000000.11111110","01111111.11111111.11111111.11111110","11111111.11111111.11111111.10000000","11111111.11111111.11111111.11111111");	

	foreach($subArr as $sub){
		if($subnetValidator->checkSubnet($sub)){
			print "Subnet: {$sub} - True<br/>";
		}else{
			print "Subnet: {$sub} - False<br/>";
		}
	}
	print "-----------------------------------------------------------------<br/>";
	foreach($binArr as $sub){
		if($subnetValidator->checkSubnet($sub,true)){
			print "Bin Subnet: {$sub} - True<br/>";
		}else{
			print "Bin Subnet: {$sub} - False<br/>";
		}
	}
?>
Output:

Code: Select all

Subnet: 255.255.255.254 - True
Subnet: 255.128.255.128 - False
Subnet: 255.63.0.255 - False
Subnet: 255.128.0.256 - False
Subnet: 0.0.0.0 - False
Subnet: 255.255.255.255 - False
-----------------------------------------------------------------
Bin Subnet: 11111111.11111111.11111111.11111111 - False
Bin Subnet: 00000000.00000000.00000000.00000000 - False
Bin Subnet: 01111111.11111111.11111111.11111111 - False
Bin Subnet: 11111111.10000000.10000000.11111110 - False
Bin Subnet: 01111111.11111111.11111111.11111110 - False
Bin Subnet: 11111111.11111111.11111111.10000000 - True
Bin Subnet: 11111111.11111111.11111111.11111111 - False
EDIT::
Updated to correctly reflect valid subnets XD

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 9:41 am
by VladSun
I must disappoint you.
Bin Subnet: 11110000.11110000.11110000.11110000 - True
Bin Subnet: 11111111.10000000.10000000.11111110 - True
These should be False ...

In a valid subnet mask you start with series of 1s only (most significant bits) and end with 0s only (least significant bits). You can't have one or more 0s between 1s. Like these:

11111111 11111111 11111111 11111000
11111111 11111111 11111111 10000000
11111111 11111111 11111000 00000000
11111111 11111000 00000000 00000000
etc.

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 9:52 am
by Weiry
Ok.. so:
11111111 11111111 11111111 00000000 - Valid
11111111 11111111 10000000 11111111 - Invalid
?

My assumption was that as long as the subnet address does not exceed a IPv4 subnet address (0.0.0.0 - 255.255.255.255), the subnet must be valid.

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 10:00 am
by VladSun
Weiry wrote:Ok.. so:
11111111 11111111 11111111 00000000 - Valid
11111111 11111111 10000000 11111111 - Invalid
?
Yep.
Weiry wrote:My assumption was that as long as the subnet address does not exceed a IPv4 subnet address (0.0.0.0 - 255.255.255.255), the subnet must be valid.
That would be too easy ;)

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 10:36 am
by Weiry
VladSun wrote:That would be too easy ;)
Actually in a way, it made it much easier XD
Updated:: correct code. (and less of it as a result)
Definitely a good challenge, learnt a couple of things doing it too :D

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 11:55 am
by Weirdan

Code: Select all

<?php

/**
 * @param String $mask - decimal, dot separated representation of a IPv4 subnet mask - e.g. "255.255.255.0"
 * @return bool
 */
function isValidIPv4Mask($mask) {
    if (in_array($mask, array('255.255.255.255', '0.0.0.0'))) return false;
    return false === strpos(sprintf("%'032b", ip2long($mask)), '01');
}

$tests = array(
    '255.255.255.0' => true,
    '255.255.0.255' => false,
    '0.0.0.0' => false,
    '255.255.255.255' => false,
    '0.255.255.255' => false,
);

foreach ($tests as $mask => $expected) {
    assert(var_export($expected, true) . ' === isValidIPv4Mask(' . var_export($mask, true) . ')');
}

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 4:17 pm
by AbraCadaver

Code: Select all

function isValidIPv4Mask($mask)
{
	$bin = decbin(ip2long($mask));
	return strlen($bin) == 32 && preg_match('/0/', $bin) && !preg_match('/01/', $bin);
}

Code: Select all

$tests = array('255.255.255.0','255.255.0.255','0.0.0.0','255.255.255.255','0.255.255.255','255');

foreach($tests as $mask) {
   echo str_pad($mask, 18) . var_export(isValidIPv4Mask($mask), true) . "\n";
}
[text]255.255.255.0 true
255.255.0.255 false
0.0.0.0 false
255.255.255.255 false
0.255.255.255 false
255 false[/text]

Re: [Challenge] IPv4 subnet mask validator

Posted: Wed Mar 31, 2010 4:51 pm
by VladSun
Very nice solutions :)

Weiry's one is a bit more complicated than I wanted it to be, but it's more functional. But it fails on 255.255.255.0 - it's a valid subnet mask. It also fails on invalid subnet masks like "255.0".

Weirdan's one is very short and simple (WOW :) ), but as AbraCadaver noticed it fails on invalid subnet masks like "255.0"

AbraCadaver's solution passes all tests and is still short and simple.

They are all sharing the same idea - string matching :), so I've made some speed tests (10000 iterations x 9 tests):

Weiry : ~3.1 sec
Weirdan : ~1.17 sec
AbraCadaver : ~1.21 sec


My solution: ~0.6 sec :P

So the challenge now is to increase your script speed :)

Re: [Challenge] IPv4 subnet mask validator

Posted: Thu Apr 01, 2010 7:18 am
by timWebUK
Am I missing something but...

255.255.255. (0, 128, 192, 224, 240, 248, 252, 254)

Are all valid subnet masks... maybe I'm misunderstanding the challenge.

Re: [Challenge] IPv4 subnet mask validator

Posted: Thu Apr 01, 2010 7:35 am
by Weirdan
timWebUK wrote:Am I missing something but...
255.255.255. (0, 128, 192, 224, 240, 248, 252, 254)
Are all valid subnet masks...
They are. What made you think they are not considered as such?

Re: [Challenge] IPv4 subnet mask validator

Posted: Thu Apr 01, 2010 10:50 am
by AbraCadaver
Should be faster, but you'll have to test the same as the others:

Code: Select all

function isValidIPv4Mask($mask)
{
    return ($bin = decbin(ip2long($mask))) && strpos($bin, '0') && !strpos($bin, '01');
}

Re: [Challenge] IPv4 subnet mask validator

Posted: Thu Apr 01, 2010 1:50 pm
by AbraCadaver
Last one, I think... :wink:

Code: Select all

function isValidIPv4Mask($mask)
{
    return ($result = log((ip2long($mask)^-1)+1,2)) != 0 && $result-(int)$result == 0;
}

Re: [Challenge] IPv4 subnet mask validator

Posted: Thu Apr 01, 2010 2:55 pm
by AbraCadaver
Why do you people have to start these things? I have real work to do :(

Code: Select all

function isValidIPv4Mask($mask)
{
    return (bool)preg_match('/^[1]+0[^1]+$/',sprintf('%032b',ip2long($mask)));
}

Re: [Challenge] IPv4 subnet mask validator

Posted: Fri Apr 02, 2010 3:33 am
by VladSun
Very creative, AbraCadaver :)

The first one fails on 0.255.255.0 mask.

The second one is OK and its time is ~0.8 sec :) It also shares the same idea as the one used in my solution :)

The third one fails on 255.255.255.254 mask :(