From b3709922b52a37edfef4f4fd0030441fcd8cd235 Mon Sep 17 00:00:00 2001 From: Michele Locati Date: Tue, 4 Feb 2025 17:23:07 +0100 Subject: [PATCH] Add AddressInterface::add() --- README.md | 13 ++++++- src/Address/AddressInterface.php | 12 ++++++ src/Address/IPv4.php | 31 ++++++++++++++++ src/Address/IPv6.php | 31 ++++++++++++++++ test/tests/Addresses/AddTest.php | 64 ++++++++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 test/tests/Addresses/AddTest.php diff --git a/README.md b/README.md index a475113..316ac5e 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: diff --git a/src/Address/AddressInterface.php b/src/Address/AddressInterface.php index 218f776..c367a4d 100644 --- a/src/Address/AddressInterface.php +++ b/src/Address/AddressInterface.php @@ -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); } diff --git a/src/Address/IPv4.php b/src/Address/IPv4.php index 0409857..1fa109d 100644 --- a/src/Address/IPv4.php +++ b/src/Address/IPv4.php @@ -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)); + } } diff --git a/src/Address/IPv6.php b/src/Address/IPv6.php index a4388ce..ad7e2a7 100644 --- a/src/Address/IPv6.php +++ b/src/Address/IPv6.php @@ -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); + } } diff --git a/test/tests/Addresses/AddTest.php b/test/tests/Addresses/AddTest.php new file mode 100644 index 0000000..a17b219 --- /dev/null +++ b/test/tests/Addresses/AddTest.php @@ -0,0 +1,64 @@ +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)); + } + } +}