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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ require_once 'path/to/iplib/ip-lib.php';

### Installation with Composer

Simply run
Simply run

```sh
composer require mlocati/ip-lib
Expand Down Expand Up @@ -101,6 +101,17 @@ echo (string) $address->shift(-1);
echo (string) $address->shift(-16);
```

### Adding two IP addresses

You can calculate the sum of 2 IP addresses using the `add` method:

```php
$a = \IPLib\Factory::parseAddressString('1.2.3.4');
$b = \IPLib\Factory::parseAddressString('10.0.0.0');
// This will print 11.2.3.4
echo (string) $a->add($b);
```

### Get the addresses at a specified offset

For addresses:
Expand Down
12 changes: 12 additions & 0 deletions src/Address/AddressInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,16 @@ public function getReverseDNSLookupName();
* @example shifting by -1 127.0.0.1 you'll have 254.0.0.2
*/
public function shift($bits);

/**
* Create a new IP address by adding to this address another address.
*
* @return self|null returns NULL if $other is not compatible with this address, or if it generates an invalid address
*
* @since 1.20.0
*
* @example adding 0.0.0.10 to 127.0.0.1 generates the IP 127.0.0.11
* @example adding 255.0.0.10 to 127.0.0.1 generates NULL
*/
public function add(AddressInterface $other);
}
31 changes: 31 additions & 0 deletions src/Address/IPv4.php
Original file line number Diff line number Diff line change
Expand Up @@ -539,4 +539,35 @@ public function shift($bits)

return new static(implode('.', $bytes));
}

/**
* {@inheritdoc}
*
* @see \IPLib\Address\AddressInterface::add()
*/
public function add(AddressInterface $other)
{
if (!$other instanceof self) {
return null;
}
$myBytes = $this->getBytes();
$otherBytes = $other->getBytes();
$sum = array_fill(0, 4, 0);
$carry = 0;
for ($index = 3; $index >= 0; $index--) {
$byte = $myBytes[$index] + $otherBytes[$index] + $carry;
if ($byte > 0xFF) {
$carry = $byte >> 8;
$byte &= 0xFF;
} else {
$carry = 0;
}
$sum[$index] = $byte;
}
if ($carry !== 0) {
return null;
}

return new static(implode('.', $sum));
}
}
31 changes: 31 additions & 0 deletions src/Address/IPv6.php
Original file line number Diff line number Diff line change
Expand Up @@ -632,4 +632,35 @@ public function shift($bits)

return static::fromWords($bytes);
}

/**
* {@inheritdoc}
*
* @see \IPLib\Address\AddressInterface::add()
*/
public function add(AddressInterface $other)
{
if (!$other instanceof self) {
return null;
}
$myWords = $this->getWords();
$otherWords = $other->getWords();
$sum = array_fill(0, 7, 0);
$carry = 0;
for ($index = 7; $index >= 0; $index--) {
$word = $myWords[$index] + $otherWords[$index] + $carry;
if ($word > 0xFFFF) {
$carry = $word >> 16;
$word &= 0xFFFF;
} else {
$carry = 0;
}
$sum[$index] = $word;
}
if ($carry !== 0) {
return null;
}

return static::fromWords($sum);
}
}
64 changes: 64 additions & 0 deletions test/tests/Addresses/AddTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace IPLib\Test\Addresses;

use IPLib\Factory;
use IPLib\Test\TestCase;

class AddTest extends TestCase
{
public function provideCases()
{
return array(
array('0.0.0.0', '::', null),
array('0.0.0.0', '0.0.0.0', '0.0.0.0'),
array('0.0.0.1', '0.0.0.1', '0.0.0.2'),
array('0.0.1.0', '0.0.1.0', '0.0.2.0'),
array('0.0.0.255', '0.0.0.1', '0.0.1.0'),
array('0.0.0.255', '0.0.0.255', '0.0.1.254'),
array('1.2.3.4', '10.0.0.0', '11.2.3.4'),
array('127.2.3.4', '127.255.0.0', '255.1.3.4'),
array('127.3.3.4', '127.255.0.0', '255.2.3.4'),
array('127.127.3.4', '128.255.0.0', null),
array('255.255.255.254', '0.0.0.1', '255.255.255.255'),
array('255.255.255.255', '0.0.0.1', null),
array('255.255.255.255', '255.255.255.255', null),

array('::', '127.0.0.1', null),
array('::', '::', '::'),
array('::1', '::', '::1'),
array('::1', '::1', '::2'),
array('::1', '::1:0', '::1:1'),
array('::1', '::10', '::11'),
array('::a', '::a', '::14'),
array('::fffe', '::1', '::ffff'),
array('::ffff', '::1', '::1:0'),
array('::ffff', '::ffff', '::1:fffe'),
array('ffff::', '::1', 'ffff::1'),
array('fffe::', '1::', 'ffff::'),
array('ffff::', '1::', null),
);
}

/**
* @dataProvider provideCases
*
* @param string $addressA
* @param string $addressB
* @param string|null $expectedSum
*/
public function testAdd($addressA, $addressB, $expectedSum)
{
$ipA = Factory::parseAddressString($addressA);
$this->assertNotNull($ipA, "'{$addressA}' has been detected as an invalid IP, but it should be valid");
$ipB = Factory::parseAddressString($addressB);
$this->assertNotNull($ipB, "'{$addressB}' has been detected as an invalid IP, but it should be valid");
if ($expectedSum === null) {
$this->assertNull($ipA->add($ipB));
$this->assertNull($ipB->add($ipA));
} else {
$this->assertSame($expectedSum, (string) $ipA->add($ipB));
$this->assertSame($expectedSum, (string) $ipB->add($ipA));
}
}
}