diff --git a/composer.json b/composer.json index 4e626d95d..8bfbf8df7 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "laravel/serializable-closure": "^2.0.4", "mexitek/phpcolors": "^1.0", "microsoft/azure-storage-blob": "^1.5.4", - "mlocati/ip-lib": "^1.18", + "mlocati/ip-lib": "^1.20", "nextcloud/lognormalizer": "^1.0", "pear/archive_tar": "^1.4.9", "pear/pear-core-minimal": "^1.10", diff --git a/composer.lock b/composer.lock index 328f2836c..7172bdf5a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2ccffdb701e2c3593e25b479d16c1926", + "content-hash": "d05140cb26808d8851e7745edaf0be8b", "packages": [ { "name": "aws/aws-crt-php", @@ -2024,16 +2024,16 @@ }, { "name": "mlocati/ip-lib", - "version": "1.18.1", + "version": "1.20.0", "source": { "type": "git", "url": "https://github.com/mlocati/ip-lib.git", - "reference": "08bb43b4949069c543ebdf099a6b2c322d0172ab" + "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/08bb43b4949069c543ebdf099a6b2c322d0172ab", - "reference": "08bb43b4949069c543ebdf099a6b2c322d0172ab", + "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/fd45fc3bf08ed6c7e665e2e70562082ac954afd4", + "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4", "shasum": "" }, "require": { @@ -2079,7 +2079,7 @@ ], "support": { "issues": "https://github.com/mlocati/ip-lib/issues", - "source": "https://github.com/mlocati/ip-lib/tree/1.18.1" + "source": "https://github.com/mlocati/ip-lib/tree/1.20.0" }, "funding": [ { @@ -2091,7 +2091,7 @@ "type": "other" } ], - "time": "2024-10-29T15:44:19+00:00" + "time": "2025-02-04T17:30:58+00:00" }, { "name": "mtdowling/jmespath.php", diff --git a/composer/installed.json b/composer/installed.json index a113ddc74..9d128052f 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -2105,17 +2105,17 @@ }, { "name": "mlocati/ip-lib", - "version": "1.18.1", - "version_normalized": "1.18.1.0", + "version": "1.20.0", + "version_normalized": "1.20.0.0", "source": { "type": "git", "url": "https://github.com/mlocati/ip-lib.git", - "reference": "08bb43b4949069c543ebdf099a6b2c322d0172ab" + "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/08bb43b4949069c543ebdf099a6b2c322d0172ab", - "reference": "08bb43b4949069c543ebdf099a6b2c322d0172ab", + "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/fd45fc3bf08ed6c7e665e2e70562082ac954afd4", + "reference": "fd45fc3bf08ed6c7e665e2e70562082ac954afd4", "shasum": "" }, "require": { @@ -2125,7 +2125,7 @@ "ext-pdo_sqlite": "*", "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5" }, - "time": "2024-10-29T15:44:19+00:00", + "time": "2025-02-04T17:30:58+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -2163,7 +2163,7 @@ ], "support": { "issues": "https://github.com/mlocati/ip-lib/issues", - "source": "https://github.com/mlocati/ip-lib/tree/1.18.1" + "source": "https://github.com/mlocati/ip-lib/tree/1.20.0" }, "funding": [ { diff --git a/composer/installed.php b/composer/installed.php index ebaf1b8fc..6fccc86e5 100644 --- a/composer/installed.php +++ b/composer/installed.php @@ -272,9 +272,9 @@ 'dev_requirement' => false, ), 'mlocati/ip-lib' => array( - 'pretty_version' => '1.18.1', - 'version' => '1.18.1.0', - 'reference' => '08bb43b4949069c543ebdf099a6b2c322d0172ab', + 'pretty_version' => '1.20.0', + 'version' => '1.20.0.0', + 'reference' => 'fd45fc3bf08ed6c7e665e2e70562082ac954afd4', 'type' => 'library', 'install_path' => __DIR__ . '/../mlocati/ip-lib', 'aliases' => array(), diff --git a/mlocati/ip-lib/src/Address/AddressInterface.php b/mlocati/ip-lib/src/Address/AddressInterface.php index f484c621c..c367a4d53 100644 --- a/mlocati/ip-lib/src/Address/AddressInterface.php +++ b/mlocati/ip-lib/src/Address/AddressInterface.php @@ -153,4 +153,30 @@ public function getPreviousAddress(); * @example for IPv6 it returns something like x.x.x.x..x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa */ public function getReverseDNSLookupName(); + + /** + * Shift the bits of the address, padding with zeroes. + * + * @param int $bits If negative the bits will be shifted left, if positive the bits will be shifted right + * + * @return self + * + * @since 1.20.0 + * + * @example shifting by 1 127.0.0.1 you'll have 63.128.0.0 + * @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/mlocati/ip-lib/src/Address/IPv4.php b/mlocati/ip-lib/src/Address/IPv4.php index cd2c416f5..1fa109deb 100644 --- a/mlocati/ip-lib/src/Address/IPv4.php +++ b/mlocati/ip-lib/src/Address/IPv4.php @@ -512,4 +512,62 @@ public function getReverseDNSLookupName() array_reverse($this->getBytes()) ) . '.in-addr.arpa'; } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::shift() + */ + public function shift($bits) + { + $bits = (int) $bits; + if ($bits === 0) { + return $this; + } + $absBits = abs($bits); + if ($absBits >= 32) { + return new self('0.0.0.0'); + } + $pad = str_repeat('0', $absBits); + $paddedBits = $this->getBits(); + if ($bits > 0) { + $paddedBits = $pad . substr($paddedBits, 0, -$bits); + } else { + $paddedBits = substr($paddedBits, $absBits) . $pad; + } + $bytes = array_map('bindec', str_split($paddedBits, 8)); + + 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/mlocati/ip-lib/src/Address/IPv6.php b/mlocati/ip-lib/src/Address/IPv6.php index e094881b6..ad7e2a7ff 100644 --- a/mlocati/ip-lib/src/Address/IPv6.php +++ b/mlocati/ip-lib/src/Address/IPv6.php @@ -605,4 +605,62 @@ public function getReverseDNSLookupName() array_reverse(str_split(str_replace(':', '', $this->toString(true)), 1)) ) . '.ip6.arpa'; } + + /** + * {@inheritdoc} + * + * @see \IPLib\Address\AddressInterface::shift() + */ + public function shift($bits) + { + $bits = (int) $bits; + if ($bits === 0) { + return $this; + } + $absBits = abs($bits); + if ($absBits >= 128) { + return new self('0000:0000:0000:0000:0000:0000:0000:0000'); + } + $pad = str_repeat('0', $absBits); + $paddedBits = $this->getBits(); + if ($bits > 0) { + $paddedBits = $pad . substr($paddedBits, 0, -$bits); + } else { + $paddedBits = substr($paddedBits, $absBits) . $pad; + } + $bytes = array_map('bindec', str_split($paddedBits, 16)); + + 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/mlocati/ip-lib/src/Range/AbstractRange.php b/mlocati/ip-lib/src/Range/AbstractRange.php index 122043506..24783ca5f 100644 --- a/mlocati/ip-lib/src/Range/AbstractRange.php +++ b/mlocati/ip-lib/src/Range/AbstractRange.php @@ -7,6 +7,7 @@ use IPLib\Address\IPv6; use IPLib\Address\Type as AddressType; use IPLib\Factory; +use OutOfBoundsException; /** * Base class for range classes. @@ -122,4 +123,48 @@ public function containsRange(RangeInterface $range) return $result; } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::split() + */ + public function split($networkPrefix, $forceSubnet = false) + { + $networkPrefix = (int) $networkPrefix; + $myNetworkPrefix = $this->getNetworkPrefix(); + if ($networkPrefix === $myNetworkPrefix) { + return array( + $forceSubnet ? $this->asSubnet() : $this, + ); + } + if ($networkPrefix < $myNetworkPrefix) { + throw new OutOfBoundsException("The value of the \$networkPrefix parameter can't be smaller than the network prefix of the range ({$myNetworkPrefix})"); + } + $startIp = $this->getStartAddress(); + $maxPrefix = $startIp::getNumberOfBits(); + if ($networkPrefix > $maxPrefix) { + throw new OutOfBoundsException("The value of the \$networkPrefix parameter can't be larger than the maximum network prefix of the range ({$maxPrefix})"); + } + if ($startIp instanceof IPv4) { + $one = IPv4::fromBytes(array(0, 0, 0, 1)); + } elseif ($startIp instanceof IPv6) { + $one = IPv6::fromBytes(array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)); + } + $delta = $one->shift($networkPrefix - $maxPrefix); + $result = array(); + while (true) { + $range = Subnet::parseString("{$startIp}/{$networkPrefix}"); + if (!$forceSubnet && $this instanceof Pattern) { + $range = $range->asPattern() ?: $range; + } + $result[] = $range; + $startIp = $startIp->add($delta); + if ($startIp === null || !$this->contains($startIp)) { + break; + } + } + + return $result; + } } diff --git a/mlocati/ip-lib/src/Range/Pattern.php b/mlocati/ip-lib/src/Range/Pattern.php index 1e180eadb..f7afeebb8 100644 --- a/mlocati/ip-lib/src/Range/Pattern.php +++ b/mlocati/ip-lib/src/Range/Pattern.php @@ -308,9 +308,11 @@ public function getSize() } /** - * @return float|int + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getNetworkPrefix() */ - private function getNetworkPrefix() + public function getNetworkPrefix() { switch ($this->getAddressType()) { case AddressType::T_IPv4: diff --git a/mlocati/ip-lib/src/Range/RangeInterface.php b/mlocati/ip-lib/src/Range/RangeInterface.php index 4d4d83471..4c7c02d60 100644 --- a/mlocati/ip-lib/src/Range/RangeInterface.php +++ b/mlocati/ip-lib/src/Range/RangeInterface.php @@ -157,4 +157,30 @@ public function getReverseDNSLookupName(); * @since 1.16.0 */ public function getSize(); + + /** + * Get the "network prefix", that is how many bits of the address are dedicated to the network portion. + * + * @return int + * + * @since 1.19.0 + * + * @example for 10.0.0.0/24 it's 24 + * @example for 10.0.0.* it's 24 + */ + public function getNetworkPrefix(); + + /** + * Split the range into smaller ranges. + * + * @param int $networkPrefix + * @param bool $forceSubnet set to true to always have ranges in "subnet format" (ie 1.2.3.4/5), to false to try to keep the original format if possible (that is, pattern to pattern, single to single) + * + * @throws \OutOfBoundsException if $networkPrefix is not valid + * + * @return \IPLib\Range\RangeInterface[] + * + * @since 1.19.0 + */ + public function split($networkPrefix, $forceSubnet = false); } diff --git a/mlocati/ip-lib/src/Range/Single.php b/mlocati/ip-lib/src/Range/Single.php index ec3531f65..09af4d8df 100644 --- a/mlocati/ip-lib/src/Range/Single.php +++ b/mlocati/ip-lib/src/Range/Single.php @@ -190,12 +190,7 @@ public function getComparableEndString() */ public function asSubnet() { - $networkPrefixes = array( - AddressType::T_IPv4 => 32, - AddressType::T_IPv6 => 128, - ); - - return new Subnet($this->address, $this->address, $networkPrefixes[$this->address->getAddressType()]); + return new Subnet($this->address, $this->address, $this->getNetworkPrefix()); } /** @@ -241,4 +236,19 @@ public function getSize() { return 1; } + + /** + * {@inheritdoc} + * + * @see \IPLib\Range\RangeInterface::getNetworkPrefix() + */ + public function getNetworkPrefix() + { + switch ($this->getAddressType()) { + case AddressType::T_IPv4: + return 32; + case AddressType::T_IPv6: + return 128; + } + } } diff --git a/mlocati/ip-lib/src/Range/Subnet.php b/mlocati/ip-lib/src/Range/Subnet.php index ea3976229..9c00a1017 100644 --- a/mlocati/ip-lib/src/Range/Subnet.php +++ b/mlocati/ip-lib/src/Range/Subnet.php @@ -261,10 +261,9 @@ public static function get6to4() } /** - * Get subnet prefix. - * - * @return int + * {@inheritdoc} * + * @see \IPLib\Range\RangeInterface::getNetworkPrefix() * @since 1.7.0 */ public function getNetworkPrefix()