Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,36 @@ echo \IPLib\Factory::parseRangeString('192.168.0.12/30')->getSize();
echo \IPLib\Factory::parseRangeString('192.168.0.1')->getSize();
```

Please note that if the number of IP addresses contained in the range is greater than the maximum integer supported by the operating system (2,147,483,647 for 32-bit systems, 9,223,372,036,854,775,807 for 64-bit systems), the `getSize()` method will return a `float` (which may be not precise).

If instead you want the exact number of IP addresses, you can use the `getExactSize()` method, which will return a string containing the number of IP addresses in decimal format in case of such big numbers.

```php
// This will print:
// int(1)
var_dump(\IPLib\Factory::parseRangeString('0.0.0.0/32')->getExactSize());

// On 32-bit systems, this will print
// string(10) "2147483648"
// On 64-bit systems, this will print
// int(2147483648)
var_dump(\IPLib\Factory::parseRangeString('0.0.0.0/1')->getExactSize());

// This will print:
// int(1073741824)
var_dump(\IPLib\Factory::parseRangeString('::/98')->getExactSize());

// On 32-bit systems, this will print
// string(10) "2147483648"
// On 64-bit systems, this will print
// int(2147483648)
var_dump(\IPLib\Factory::parseRangeString('::/97')->getExactSize());

// On 32-bit and 64-bit systems, this will print
// string(39) "170141183460469231731687303715884105728"
var_dump(\IPLib\Factory::parseRangeString('::/1')->getExactSize());
```

### Getting the reverse DNS lookup address

To perform reverse DNS queries, you need to use a special format of the IP addresses.
Expand Down
17 changes: 16 additions & 1 deletion src/Range/Pattern.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use IPLib\Address\IPv6;
use IPLib\Address\Type as AddressType;
use IPLib\ParseStringFlag;
use IPLib\Service\BinaryMath;

/**
* Represents an address range in pattern format (only ending asterisks are supported).
Expand Down Expand Up @@ -304,7 +305,21 @@ public function getSize()
$maxPrefix = $fromAddress::getNumberOfBits();
$prefix = $this->getNetworkPrefix();

return pow(2, ($maxPrefix - $prefix));
return pow(2, $maxPrefix - $prefix);
}

/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getExactSize()
*/
public function getExactSize()
{
$fromAddress = $this->fromAddress;
$maxPrefix = $fromAddress::getNumberOfBits();
$prefix = $this->getNetworkPrefix();

return BinaryMath::getInstance()->pow2string($maxPrefix - $prefix);
}

/**
Expand Down
13 changes: 11 additions & 2 deletions src/Range/RangeInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,23 @@ public function asPattern();
public function getReverseDNSLookupName();

/**
* Get the count of addresses this IP range contains.
* Get the count of addresses contained in this IP range (possibly approximated).
*
* @return int|float Return float as for huge IPv6 networks, int is not enough
* @return int|float If the number of addresses exceeds PHP_INT_MAX a float containing an approximation will be returned
*
* @since 1.16.0
*/
public function getSize();

/**
* Get the exact count of addresses contained in this IP range.
*
* @return int|numeric-string If the number of addresses exceeds PHP_INT_MAX a string containing the exact number of addresses will be returned
*
* @since 1.21.0
*/
public function getExactSize();

/**
* Get the "network prefix", that is how many bits of the address are dedicated to the network portion.
*
Expand Down
10 changes: 10 additions & 0 deletions src/Range/Single.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ public function getSize()
return 1;
}

/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getExactSize()
*/
public function getExactSize()
{
return 1;
}

/**
* {@inheritdoc}
*
Expand Down
17 changes: 16 additions & 1 deletion src/Range/Subnet.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use IPLib\Address\Type as AddressType;
use IPLib\Factory;
use IPLib\ParseStringFlag;
use IPLib\Service\BinaryMath;

/**
* Represents an address range in subnet format (eg CIDR).
Expand Down Expand Up @@ -348,6 +349,20 @@ public function getSize()
$maxPrefix = $fromAddress::getNumberOfBits();
$prefix = $this->getNetworkPrefix();

return pow(2, ($maxPrefix - $prefix));
return pow(2, $maxPrefix - $prefix);
}

/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getExactSize()
*/
public function getExactSize()
{
$fromAddress = $this->fromAddress;
$maxPrefix = $fromAddress::getNumberOfBits();
$prefix = $this->getNetworkPrefix();

return BinaryMath::getInstance()->pow2string($maxPrefix - $prefix);
}
}
45 changes: 45 additions & 0 deletions src/Service/BinaryMath.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@
*/
class BinaryMath
{
private static $instance;

/**
* @return \IPLib\Service\BinaryMath
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}

return self::$instance;
}

/**
* Trim the leading zeroes from a non-negative integer represented in binary form.
*
Expand Down Expand Up @@ -97,6 +111,37 @@ public function orX($operand1, $operand2)
return $result;
}

/**
* Compute 2 raised to the given exponent.
*
* If the result fits into a native PHP integer, an int is returned.
* If the result exceeds PHP_INT_MAX, a string containing the exact decimal representation is returned.
*
* @param int $exponent The non-negative exponent
*
* @return int|string
*/
public function pow2string($exponent)
{
if ($exponent < PHP_INT_SIZE * 8 - 1) {
return 1 << $exponent;
}
$digits = array(1);
for ($i = 0; $i < $exponent; $i++) {
$carry = 0;
foreach ($digits as $index => $digit) {
$product = $digit * 2 + $carry;
$digits[$index] = $product % 10;
$carry = (int) ($product / 10);
}
if ($carry !== 0) {
$digits[] = $carry;
}
}

return implode('', array_reverse($digits));
}

/**
* Zero-padding of two non-negative integers represented in binary form, so that they have the same length.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Service/RangesFromBoundaryCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class RangesFromBoundaryCalculator
*/
public function __construct($numBits)
{
$this->math = new BinaryMath();
$this->math = BinaryMath::getInstance();
$this->setNumBits($numBits);
}

Expand Down
37 changes: 25 additions & 12 deletions test/tests/Ranges/RangeSizeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,43 @@ public function rangesProvider()
array('8.8.8.8', 1),
array('2001:0db8:85a3:0000:0000:8a2e:0370:7334', 1),

array('8.8.8.8/32', 1),
array('8.8.8.8/31', 2),
array('0.0.0.0/0', 4294967296),
array('100::/64', 1.8446744073709552E+19),
array('::/0', 3.402823669209385E+38),
array('0.0.0.0/1', PHP_INT_SIZE > 4 ? 0x80000000 : 2147483648.0, PHP_INT_SIZE > 4 ? null : '2147483648'),
array('0.0.0.0/0', PHP_INT_SIZE > 4 ? 0x100000000 : 4294967296.0, PHP_INT_SIZE > 4 ? null : '4294967296'),

array('172.16.0.*', 256),
array('172.*.*.*', 16777216),
array('*.*.*.*', 4294967296),
array('2001:0db8:85a3:0000:0000:8a2e:0370:*', 65536),
array('2001:0db8:85a3:0000:0000:*:*:*', 281474976710656),
array('*:*:*:*:*:*:*:*', 3.402823669209385E+38),
array('100::/128', 1),
array('100::/127', 2),
array('100::/98', 1073741824),
array('100::/97', PHP_INT_SIZE > 4 ? 2147483648 : 2147483648.0, PHP_INT_SIZE > 4 ? null : '2147483648'),
array('0::/1', 170141183460469231731687303715884105728.0, '170141183460469231731687303715884105728'),
array('::/0', 340282366920938463463374607431768211456.0, '340282366920938463463374607431768211456'),

array('172.16.0.*', 0x100),
array('172.*.*.*', 0x1000000),
array('*.*.*.*', PHP_INT_SIZE > 4 ? 0x100000000 : 4294967296.0, PHP_INT_SIZE > 4 ? null : '4294967296'),
array('2001:0db8:85a3:0000:0000:8a2e:0370:*', 0x10000),
array('2001:0db8:85a3:0000:0000:*:*:*', PHP_INT_SIZE > 4 ? 0x1000000000000 : 281474976710656.0, PHP_INT_SIZE > 4 ? null : '281474976710656'),
array('*:*:*:*:*:*:*:*', 340282366920938463463374607431768211456.0, '340282366920938463463374607431768211456'),
);
}

/**
* @dataProvider rangesProvider
*
* @param string $addressRange
* @param int|float $size
* @param int|float $expectedSize
* @param int|string|null $expectedExactSize
*/
public function testSize($addressRange, $size)
public function testSize($addressRange, $expectedSize, $expectedExactSize = null)
{
$range = Factory::rangeFromString($addressRange);
$actualSize = $range->getSize();
$this->assertSame($size, $actualSize);
$this->assertSame($expectedSize, $actualSize, 'getSize()');
if ($expectedExactSize === null) {
$expectedExactSize = $expectedSize;
}
$actualExactSize = $range->getExactSize();
$this->assertSame($expectedExactSize, $actualExactSize, 'getExactSize()');
}
}
54 changes: 53 additions & 1 deletion test/tests/Services/BinaryMathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use IPLib\Service\BinaryMath;
use IPLib\Test\TestCase;
use ReflectionProperty;

class BinaryMathTest extends TestCase
{
Expand All @@ -19,7 +20,20 @@ class BinaryMathTest extends TestCase
*/
protected static function doSetUpBeforeClass()
{
self::$math = new BinaryMath();
self::$math = BinaryMath::getInstance();
}

public function testSingleton()
{
$this->assertSame(self::$math, BinaryMath::getInstance());
$instanceProperty = new ReflectionProperty('IPLib\\Service\\BinaryMath', 'instance');
if (PHP_VERSION_ID < 80100) {
$instanceProperty->setAccessible(true);
}
$instanceProperty->setValue(null, null);
$newMath = BinaryMath::getInstance();
$this->assertEquals(self::$math, $newMath);
$this->assertNotSame(self::$math, $newMath);
}

/**
Expand Down Expand Up @@ -177,4 +191,42 @@ public function provideOrCases()

return $cases;
}

/**
* @dataProvider providePow2stringCases
*
* @param int $exponent
* @param int|string $expectedResult
*/
public function testPow2string($exponent, $expectedResult)
{
$actualResult = self::$math->pow2string($exponent);

$this->assertSame($expectedResult, $actualResult);
}

/**
* @return array
*/
public function providePow2stringCases()
{
return array(
array(0, 1),
array(1, 2),
array(2, 4),
array(3, 8),
array(30, 0x40000000),
array(31, PHP_INT_SIZE > 4 ? 0x80000000 : '2147483648'),
array(32, PHP_INT_SIZE > 4 ? 0x100000000 : '4294967296'),
array(33, PHP_INT_SIZE > 4 ? 0x200000000 : '8589934592'),
array(62, PHP_INT_SIZE > 4 ? 0x4000000000000000 : '4611686018427387904'),
array(63, PHP_INT_SIZE > 8 ? 0x8000000000000000 : '9223372036854775808'),
array(64, PHP_INT_SIZE > 8 ? 0x10000000000000000 : '18446744073709551616'),
array(65, PHP_INT_SIZE > 8 ? 0x20000000000000000 : '36893488147419103232'),
array(126, PHP_INT_SIZE > 8 ? 0x20000000000000000000000000000000 : '85070591730234615865843651857942052864'),
array(127, PHP_INT_SIZE > 9 ? 0x40000000000000000000000000000000 : '170141183460469231731687303715884105728'),
array(128, PHP_INT_SIZE > 9 ? 0x80000000000000000000000000000000 : '340282366920938463463374607431768211456'),
array(129, PHP_INT_SIZE > 9 ? 0x100000000000000000000000000000000 : '680564733841876926926749214863536422912'),
);
}
}
Loading