Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/3rdparty.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2022-09-20 17:45:16 +0300
committerGitHub <noreply@github.com>2022-09-20 17:45:16 +0300
commit3095d4062823f3f913d594f9ff313010ed55cd74 (patch)
treee0083da033961e884caf7779042446e23d4314fa
parentf143482ffb0b8dfdbc08cd848ce2e66f02a5d9b6 (diff)
parenteb02283d5b0d6dfe76d83e0df13030a153412fdf (diff)
Merge pull request #1173 from nextcloud/fix/bump-mlocati/ip-lib-to-1.18v25.0.1rc1v25.0.1v25.0.0rc5v25.0.0rc4v25.0.0rc3v25.0.0rc2v25.0.0rc1v25.0.0stable25
Add mlocati/ip-lib
-rw-r--r--.gitignore3
-rw-r--r--composer.json1
-rw-r--r--composer.lock73
-rw-r--r--composer/autoload_classmap.php16
-rw-r--r--composer/autoload_psr4.php1
-rw-r--r--composer/autoload_static.php21
-rw-r--r--composer/installed.json74
-rw-r--r--composer/installed.php13
-rw-r--r--mlocati/ip-lib/LICENSE.txt20
-rw-r--r--mlocati/ip-lib/ip-lib.php13
-rw-r--r--mlocati/ip-lib/src/Address/AddressInterface.php156
-rw-r--r--mlocati/ip-lib/src/Address/AssignedRange.php140
-rw-r--r--mlocati/ip-lib/src/Address/IPv4.php515
-rw-r--r--mlocati/ip-lib/src/Address/IPv6.php608
-rw-r--r--mlocati/ip-lib/src/Address/Type.php44
-rw-r--r--mlocati/ip-lib/src/Factory.php298
-rw-r--r--mlocati/ip-lib/src/ParseStringFlag.php79
-rw-r--r--mlocati/ip-lib/src/Range/AbstractRange.php125
-rw-r--r--mlocati/ip-lib/src/Range/Pattern.php322
-rw-r--r--mlocati/ip-lib/src/Range/RangeInterface.php160
-rw-r--r--mlocati/ip-lib/src/Range/Single.php244
-rw-r--r--mlocati/ip-lib/src/Range/Subnet.php354
-rw-r--r--mlocati/ip-lib/src/Range/Type.php152
-rw-r--r--mlocati/ip-lib/src/Service/BinaryMath.php120
-rw-r--r--mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php168
-rw-r--r--mlocati/ip-lib/src/Service/UnsignedIntegerMath.php171
26 files changed, 3888 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index d9610862..7cadd463 100644
--- a/.gitignore
+++ b/.gitignore
@@ -147,6 +147,9 @@ mexitek/phpcolors/README.md
mikey179/vfsstream
+mlocati/ip-lib/README.md
+mlocati/ip-lib/composer.json
+
mtdowling/jmespath.php/tests
nextcloud/lognormalizer/.gitignore
diff --git a/composer.json b/composer.json
index 1dbdd417..7e1ed00f 100644
--- a/composer.json
+++ b/composer.json
@@ -33,6 +33,7 @@
"laravel/serializable-closure": "^1.2",
"mexitek/phpcolors": "^1.0",
"microsoft/azure-storage-blob": "^1.5",
+ "mlocati/ip-lib": "^1.18",
"nextcloud/lognormalizer": "^1.0",
"nikic/php-parser": "^4.2",
"opis/closure": "^3.6",
diff --git a/composer.lock b/composer.lock
index 2be33eee..9ceed2f2 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": "682bfa95cb2df85d82d78f313aebd5fd",
+ "content-hash": "37d86728a17ad5fe98c1e8086a6c0c51",
"packages": [
{
"name": "aws/aws-sdk-php",
@@ -2239,6 +2239,77 @@
"time": "2020-12-28T07:59:51+00:00"
},
{
+ "name": "mlocati/ip-lib",
+ "version": "1.18.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mlocati/ip-lib.git",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "IPLib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michele Locati",
+ "email": "mlocati@gmail.com",
+ "homepage": "https://github.com/mlocati",
+ "role": "Author"
+ }
+ ],
+ "description": "Handle IPv4, IPv6 addresses and ranges",
+ "homepage": "https://github.com/mlocati/ip-lib",
+ "keywords": [
+ "IP",
+ "address",
+ "addresses",
+ "ipv4",
+ "ipv6",
+ "manage",
+ "managing",
+ "matching",
+ "network",
+ "networking",
+ "range",
+ "subnet"
+ ],
+ "support": {
+ "issues": "https://github.com/mlocati/ip-lib/issues",
+ "source": "https://github.com/mlocati/ip-lib/tree/1.18.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/mlocati",
+ "type": "github"
+ },
+ {
+ "url": "https://paypal.me/mlocati",
+ "type": "other"
+ }
+ ],
+ "time": "2022-01-13T18:05:33+00:00"
+ },
+ {
"name": "mtdowling/jmespath.php",
"version": "2.6.1",
"source": {
diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php
index ad99ca64..31663882 100644
--- a/composer/autoload_classmap.php
+++ b/composer/autoload_classmap.php
@@ -1463,6 +1463,22 @@ return array(
'ID3Parser\\getID3\\getid3_exception' => $vendorDir . '/christophwurst/id3parser/src/getID3/getid3_exception.php',
'ID3Parser\\getID3\\getid3_handler' => $vendorDir . '/christophwurst/id3parser/src/getID3/getid3_handler.php',
'ID3Parser\\getID3\\getid3_lib' => $vendorDir . '/christophwurst/id3parser/src/getID3/getid3_lib.php',
+ 'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php',
+ 'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php',
+ 'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php',
+ 'IPLib\\Address\\IPv6' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv6.php',
+ 'IPLib\\Address\\Type' => $vendorDir . '/mlocati/ip-lib/src/Address/Type.php',
+ 'IPLib\\Factory' => $vendorDir . '/mlocati/ip-lib/src/Factory.php',
+ 'IPLib\\ParseStringFlag' => $vendorDir . '/mlocati/ip-lib/src/ParseStringFlag.php',
+ 'IPLib\\Range\\AbstractRange' => $vendorDir . '/mlocati/ip-lib/src/Range/AbstractRange.php',
+ 'IPLib\\Range\\Pattern' => $vendorDir . '/mlocati/ip-lib/src/Range/Pattern.php',
+ 'IPLib\\Range\\RangeInterface' => $vendorDir . '/mlocati/ip-lib/src/Range/RangeInterface.php',
+ 'IPLib\\Range\\Single' => $vendorDir . '/mlocati/ip-lib/src/Range/Single.php',
+ 'IPLib\\Range\\Subnet' => $vendorDir . '/mlocati/ip-lib/src/Range/Subnet.php',
+ 'IPLib\\Range\\Type' => $vendorDir . '/mlocati/ip-lib/src/Range/Type.php',
+ 'IPLib\\Service\\BinaryMath' => $vendorDir . '/mlocati/ip-lib/src/Service/BinaryMath.php',
+ 'IPLib\\Service\\RangesFromBoundaryCalculator' => $vendorDir . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
+ 'IPLib\\Service\\UnsignedIntegerMath' => $vendorDir . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
'Icewind\\Streams\\CallbackWrapper' => $vendorDir . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\CountWrapper' => $vendorDir . '/icewind/streams/src/CountWrapper.php',
'Icewind\\Streams\\Directory' => $vendorDir . '/icewind/streams/src/Directory.php',
diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php
index 4b572bd1..59242d02 100644
--- a/composer/autoload_psr4.php
+++ b/composer/autoload_psr4.php
@@ -67,6 +67,7 @@ return array(
'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'),
'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
'Icewind\\Streams\\' => array($vendorDir . '/icewind/streams/src'),
+ 'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'),
'ID3Parser\\' => array($vendorDir . '/christophwurst/id3parser/src'),
'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'),
diff --git a/composer/autoload_static.php b/composer/autoload_static.php
index 7efc962f..353fc312 100644
--- a/composer/autoload_static.php
+++ b/composer/autoload_static.php
@@ -235,6 +235,7 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652
'I' =>
array (
'Icewind\\Streams\\' => 16,
+ 'IPLib\\' => 6,
'ID3Parser\\' => 10,
),
'H' =>
@@ -535,6 +536,10 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652
array (
0 => __DIR__ . '/..' . '/icewind/streams/src',
),
+ 'IPLib\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/mlocati/ip-lib/src',
+ ),
'ID3Parser\\' =>
array (
0 => __DIR__ . '/..' . '/christophwurst/id3parser/src',
@@ -2126,6 +2131,22 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652
'ID3Parser\\getID3\\getid3_exception' => __DIR__ . '/..' . '/christophwurst/id3parser/src/getID3/getid3_exception.php',
'ID3Parser\\getID3\\getid3_handler' => __DIR__ . '/..' . '/christophwurst/id3parser/src/getID3/getid3_handler.php',
'ID3Parser\\getID3\\getid3_lib' => __DIR__ . '/..' . '/christophwurst/id3parser/src/getID3/getid3_lib.php',
+ 'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php',
+ 'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php',
+ 'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php',
+ 'IPLib\\Address\\IPv6' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv6.php',
+ 'IPLib\\Address\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/Type.php',
+ 'IPLib\\Factory' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Factory.php',
+ 'IPLib\\ParseStringFlag' => __DIR__ . '/..' . '/mlocati/ip-lib/src/ParseStringFlag.php',
+ 'IPLib\\Range\\AbstractRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/AbstractRange.php',
+ 'IPLib\\Range\\Pattern' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Pattern.php',
+ 'IPLib\\Range\\RangeInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/RangeInterface.php',
+ 'IPLib\\Range\\Single' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Single.php',
+ 'IPLib\\Range\\Subnet' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Subnet.php',
+ 'IPLib\\Range\\Type' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Range/Type.php',
+ 'IPLib\\Service\\BinaryMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/BinaryMath.php',
+ 'IPLib\\Service\\RangesFromBoundaryCalculator' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php',
+ 'IPLib\\Service\\UnsignedIntegerMath' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php',
'Icewind\\Streams\\CallbackWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CallbackWrapper.php',
'Icewind\\Streams\\CountWrapper' => __DIR__ . '/..' . '/icewind/streams/src/CountWrapper.php',
'Icewind\\Streams\\Directory' => __DIR__ . '/..' . '/icewind/streams/src/Directory.php',
diff --git a/composer/installed.json b/composer/installed.json
index 1def2305..6dac20eb 100644
--- a/composer/installed.json
+++ b/composer/installed.json
@@ -2332,6 +2332,80 @@
"install-path": "../microsoft/azure-storage-common"
},
{
+ "name": "mlocati/ip-lib",
+ "version": "1.18.0",
+ "version_normalized": "1.18.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mlocati/ip-lib.git",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mlocati/ip-lib/zipball/c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "reference": "c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "ext-pdo_sqlite": "*",
+ "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.5 || ^9.5"
+ },
+ "time": "2022-01-13T18:05:33+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "IPLib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michele Locati",
+ "email": "mlocati@gmail.com",
+ "homepage": "https://github.com/mlocati",
+ "role": "Author"
+ }
+ ],
+ "description": "Handle IPv4, IPv6 addresses and ranges",
+ "homepage": "https://github.com/mlocati/ip-lib",
+ "keywords": [
+ "IP",
+ "address",
+ "addresses",
+ "ipv4",
+ "ipv6",
+ "manage",
+ "managing",
+ "matching",
+ "network",
+ "networking",
+ "range",
+ "subnet"
+ ],
+ "support": {
+ "issues": "https://github.com/mlocati/ip-lib/issues",
+ "source": "https://github.com/mlocati/ip-lib/tree/1.18.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/mlocati",
+ "type": "github"
+ },
+ {
+ "url": "https://paypal.me/mlocati",
+ "type": "other"
+ }
+ ],
+ "install-path": "../mlocati/ip-lib"
+ },
+ {
"name": "mtdowling/jmespath.php",
"version": "2.6.1",
"version_normalized": "2.6.1.0",
diff --git a/composer/installed.php b/composer/installed.php
index bd2d7725..677e6f99 100644
--- a/composer/installed.php
+++ b/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'nextcloud/3rdparty',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'd6a35b6d5759c08dd268618951f9e5b1c18aa939',
+ 'reference' => 'f143482ffb0b8dfdbc08cd848ce2e66f02a5d9b6',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
@@ -307,6 +307,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'mlocati/ip-lib' => array(
+ 'pretty_version' => '1.18.0',
+ 'version' => '1.18.0.0',
+ 'reference' => 'c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../mlocati/ip-lib',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'mtdowling/jmespath.php' => array(
'pretty_version' => '2.6.1',
'version' => '2.6.1.0',
@@ -319,7 +328,7 @@
'nextcloud/3rdparty' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'd6a35b6d5759c08dd268618951f9e5b1c18aa939',
+ 'reference' => 'f143482ffb0b8dfdbc08cd848ce2e66f02a5d9b6',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
diff --git a/mlocati/ip-lib/LICENSE.txt b/mlocati/ip-lib/LICENSE.txt
new file mode 100644
index 00000000..5c21e47c
--- /dev/null
+++ b/mlocati/ip-lib/LICENSE.txt
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+Copyright (c) 2016 Michele Locati
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. \ No newline at end of file
diff --git a/mlocati/ip-lib/ip-lib.php b/mlocati/ip-lib/ip-lib.php
new file mode 100644
index 00000000..c34d0b49
--- /dev/null
+++ b/mlocati/ip-lib/ip-lib.php
@@ -0,0 +1,13 @@
+<?php
+
+spl_autoload_register(
+ function ($class) {
+ if (strpos($class, 'IPLib\\') !== 0) {
+ return;
+ }
+ $file = __DIR__ . DIRECTORY_SEPARATOR . 'src' . str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('IPLib'))) . '.php';
+ if (is_file($file)) {
+ require_once $file;
+ }
+ }
+);
diff --git a/mlocati/ip-lib/src/Address/AddressInterface.php b/mlocati/ip-lib/src/Address/AddressInterface.php
new file mode 100644
index 00000000..f484c621
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/AddressInterface.php
@@ -0,0 +1,156 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\Range\RangeInterface;
+
+/**
+ * Interface of all the IP address types.
+ */
+interface AddressInterface
+{
+ /**
+ * Get the short string representation of this address.
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Get the number of bits representing this address type.
+ *
+ * @return int
+ *
+ * @since 1.14.0
+ *
+ * @example 32 for IPv4
+ * @example 128 for IPv6
+ */
+ public static function getNumberOfBits();
+
+ /**
+ * Get the string representation of this address.
+ *
+ * @param bool $long set to true to have a long/full representation, false otherwise
+ *
+ * @return string
+ *
+ * @example If $long is true, you'll get '0000:0000:0000:0000:0000:0000:0000:0001', '::1' otherwise.
+ */
+ public function toString($long = false);
+
+ /**
+ * Get the byte list of the IP address.
+ *
+ * @return int[]
+ *
+ * @example For localhost: for IPv4 you'll get array(127, 0, 0, 1), for IPv6 array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
+ */
+ public function getBytes();
+
+ /**
+ * Get the full bit list the IP address.
+ *
+ * @return string
+ *
+ * @since 1.14.0
+ *
+ * @example For localhost: For IPv4 you'll get '01111111000000000000000000000001' (32 digits), for IPv6 '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' (128 digits)
+ */
+ public function getBits();
+
+ /**
+ * Get the type of the IP address.
+ *
+ * @return int One of the \IPLib\Address\Type::T_... constants
+ */
+ public function getAddressType();
+
+ /**
+ * Get the default RFC reserved range type.
+ *
+ * @return int One of the \IPLib\Range\Type::T_... constants
+ *
+ * @since 1.5.0
+ */
+ public static function getDefaultReservedRangeType();
+
+ /**
+ * Get the RFC reserved ranges (except the ones of type getDefaultReservedRangeType).
+ *
+ * @return \IPLib\Address\AssignedRange[] ranges are sorted
+ *
+ * @since 1.5.0
+ */
+ public static function getReservedRanges();
+
+ /**
+ * Get the type of range of the IP address.
+ *
+ * @return int One of the \IPLib\Range\Type::T_... constants
+ */
+ public function getRangeType();
+
+ /**
+ * Get a string representation of this address than can be used when comparing addresses and ranges.
+ *
+ * @return string
+ */
+ public function getComparableString();
+
+ /**
+ * Check if this address is contained in an range.
+ *
+ * @param \IPLib\Range\RangeInterface $range
+ *
+ * @return bool
+ */
+ public function matches(RangeInterface $range);
+
+ /**
+ * Get the address at a certain distance from this address.
+ *
+ * @param int $n the distance of the address (can be negative)
+ *
+ * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the final address would be invalid
+ *
+ * @since 1.15.0
+ *
+ * @example passing 1 to the address 127.0.0.1 will result in 127.0.0.2
+ * @example passing -1 to the address 127.0.0.1 will result in 127.0.0.0
+ * @example passing -1 to the address 0.0.0.0 will result in NULL
+ */
+ public function getAddressAtOffset($n);
+
+ /**
+ * Get the address right after this IP address (if available).
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
+ * @since 1.4.0
+ */
+ public function getNextAddress();
+
+ /**
+ * Get the address right before this IP address (if available).
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
+ * @since 1.4.0
+ */
+ public function getPreviousAddress();
+
+ /**
+ * Get the Reverse DNS Lookup Address of this IP address.
+ *
+ * @return string
+ *
+ * @since 1.12.0
+ *
+ * @example for IPv4 it returns something like x.x.x.x.in-addr.arpa
+ * @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();
+}
diff --git a/mlocati/ip-lib/src/Address/AssignedRange.php b/mlocati/ip-lib/src/Address/AssignedRange.php
new file mode 100644
index 00000000..3d6fa532
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/AssignedRange.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\Range\RangeInterface;
+
+/**
+ * Represents an IP address range with an assigned range type.
+ *
+ * @since 1.5.0
+ */
+class AssignedRange
+{
+ /**
+ * The range definition.
+ *
+ * @var \IPLib\Range\RangeInterface
+ */
+ protected $range;
+
+ /**
+ * The range type.
+ *
+ * @var int one of the \IPLib\Range\Type::T_ constants
+ */
+ protected $type;
+
+ /**
+ * The list of exceptions for this range type.
+ *
+ * @var \IPLib\Address\AssignedRange[]
+ */
+ protected $exceptions;
+
+ /**
+ * Initialize the instance.
+ *
+ * @param \IPLib\Range\RangeInterface $range the range definition
+ * @param int $type The range type (one of the \IPLib\Range\Type::T_ constants)
+ * @param \IPLib\Address\AssignedRange[] $exceptions the list of exceptions for this range type
+ */
+ public function __construct(RangeInterface $range, $type, array $exceptions = array())
+ {
+ $this->range = $range;
+ $this->type = $type;
+ $this->exceptions = $exceptions;
+ }
+
+ /**
+ * Get the range definition.
+ *
+ * @return \IPLib\Range\RangeInterface
+ */
+ public function getRange()
+ {
+ return $this->range;
+ }
+
+ /**
+ * Get the range type.
+ *
+ * @return int one of the \IPLib\Range\Type::T_ constants
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Get the list of exceptions for this range type.
+ *
+ * @return \IPLib\Address\AssignedRange[]
+ */
+ public function getExceptions()
+ {
+ return $this->exceptions;
+ }
+
+ /**
+ * Get the assigned type for a specific address.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ *
+ * @return int|null return NULL of the address is outside this address; a \IPLib\Range\Type::T_ constant otherwise
+ */
+ public function getAddressType(AddressInterface $address)
+ {
+ $result = null;
+ if ($this->range->contains($address)) {
+ foreach ($this->exceptions as $exception) {
+ $result = $exception->getAddressType($address);
+ if ($result !== null) {
+ break;
+ }
+ }
+ if ($result === null) {
+ $result = $this->type;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the assigned type for a specific address range.
+ *
+ * @param \IPLib\Range\RangeInterface $range
+ *
+ * @return int|false|null return NULL of the range is fully outside this range; false if it's partly crosses this range (or it contains mixed types); a \IPLib\Range\Type::T_ constant otherwise
+ */
+ public function getRangeType(RangeInterface $range)
+ {
+ $myStart = $this->range->getComparableStartString();
+ $rangeEnd = $range->getComparableEndString();
+ if ($myStart > $rangeEnd) {
+ $result = null;
+ } else {
+ $myEnd = $this->range->getComparableEndString();
+ $rangeStart = $range->getComparableStartString();
+ if ($myEnd < $rangeStart) {
+ $result = null;
+ } elseif ($rangeStart < $myStart || $rangeEnd > $myEnd) {
+ $result = false;
+ } else {
+ $result = null;
+ foreach ($this->exceptions as $exception) {
+ $result = $exception->getRangeType($range);
+ if ($result !== null) {
+ break;
+ }
+ }
+ if ($result === null) {
+ $result = $this->getType();
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/mlocati/ip-lib/src/Address/IPv4.php b/mlocati/ip-lib/src/Address/IPv4.php
new file mode 100644
index 00000000..cd2c416f
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/IPv4.php
@@ -0,0 +1,515 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\ParseStringFlag;
+use IPLib\Range\RangeInterface;
+use IPLib\Range\Subnet;
+use IPLib\Range\Type as RangeType;
+
+/**
+ * An IPv4 address.
+ */
+class IPv4 implements AddressInterface
+{
+ /**
+ * The string representation of the address.
+ *
+ * @var string
+ *
+ * @example '127.0.0.1'
+ */
+ protected $address;
+
+ /**
+ * The byte list of the IP address.
+ *
+ * @var int[]|null
+ */
+ protected $bytes;
+
+ /**
+ * The type of the range of this IP address.
+ *
+ * @var int|null
+ */
+ protected $rangeType;
+
+ /**
+ * A string representation of this address than can be used when comparing addresses and ranges.
+ *
+ * @var string
+ */
+ protected $comparableString;
+
+ /**
+ * An array containing RFC designated address ranges.
+ *
+ * @var array|null
+ */
+ private static $reservedRanges;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param string $address
+ */
+ protected function __construct($address)
+ {
+ $this->address = $address;
+ $this->bytes = null;
+ $this->rangeType = null;
+ $this->comparableString = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getNumberOfBits()
+ */
+ public static function getNumberOfBits()
+ {
+ return 32;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $address the address to parse
+ * @param bool $mayIncludePort
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Address\IPv4::parseString()
+ * @since 1.1.0 added the $mayIncludePort argument
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($address, 0 | ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
+ *
+ * @param string|mixed $address the address to parse
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseString($address, $flags = 0)
+ {
+ if (!is_string($address)) {
+ return null;
+ }
+ $flags = (int) $flags;
+ $matches = null;
+ if ($flags & ParseStringFlag::ADDRESS_MAYBE_RDNS) {
+ if (preg_match('/^([12]?[0-9]{1,2}\.[12]?[0-9]{1,2}\.[12]?[0-9]{1,2}\.[12]?[0-9]{1,2})\.in-addr\.arpa\.?$/i', $address, $matches)) {
+ $address = implode('.', array_reverse(explode('.', $matches[1])));
+ $flags = $flags & ~(ParseStringFlag::IPV4_MAYBE_NON_DECIMAL | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED);
+ }
+ }
+ if ($flags & ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED) {
+ if (strpos($address, '.') === 0) {
+ return null;
+ }
+ $lengthNonHex = '{1,11}';
+ $lengthHex = '{1,8}';
+ $chunk234Optional = true;
+ } else {
+ if (!strpos($address, '.')) {
+ return null;
+ }
+ $lengthNonHex = '{1,3}';
+ $lengthHex = '{1,2}';
+ $chunk234Optional = false;
+ }
+ $rxChunk1 = "0?[0-9]{$lengthNonHex}";
+ if ($flags & ParseStringFlag::IPV4_MAYBE_NON_DECIMAL) {
+ $rxChunk1 = "(?:0[Xx]0*[0-9A-Fa-f]{$lengthHex})|(?:{$rxChunk1})";
+ $onlyDecimal = false;
+ } else {
+ $onlyDecimal = true;
+ }
+ $rxChunk1 = "0*?({$rxChunk1})";
+ $rxChunk234 = "\.{$rxChunk1}";
+ if ($chunk234Optional) {
+ $rxChunk234 = "(?:{$rxChunk234})?";
+ }
+ $rx = "{$rxChunk1}{$rxChunk234}{$rxChunk234}{$rxChunk234}";
+ if ($flags & ParseStringFlag::MAY_INCLUDE_PORT) {
+ $rx .= '(?::\d+)?';
+ }
+ if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
+ return null;
+ }
+ $math = new \IPLib\Service\UnsignedIntegerMath();
+ $nums = array();
+ $maxChunkIndex = count($matches) - 1;
+ for ($i = 1; $i <= $maxChunkIndex; $i++) {
+ $numBytes = $i === $maxChunkIndex ? 5 - $i : 1;
+ $chunkBytes = $math->getBytes($matches[$i], $numBytes, $onlyDecimal);
+ if ($chunkBytes === null) {
+ return null;
+ }
+ $nums = array_merge($nums, $chunkBytes);
+ }
+
+ return new static(implode('.', $nums));
+ }
+
+ /**
+ * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
+ *
+ * @param int[]|array $bytes
+ *
+ * @return static|null
+ */
+ public static function fromBytes(array $bytes)
+ {
+ $result = null;
+ if (count($bytes) === 4) {
+ $chunks = array_map(
+ function ($byte) {
+ return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
+ },
+ $bytes
+ );
+ if (in_array(false, $chunks, true) === false) {
+ $result = new static(implode('.', $chunks));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ if ($long) {
+ return $this->getComparableString();
+ }
+
+ return $this->address;
+ }
+
+ /**
+ * Get the octal representation of this IP address.
+ *
+ * @param bool $long
+ *
+ * @return string
+ *
+ * @since 1.10.0
+ *
+ * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
+ * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
+ */
+ public function toOctal($long = false)
+ {
+ $chunks = array();
+ foreach ($this->getBytes() as $byte) {
+ if ($long) {
+ $chunks[] = sprintf('%04o', $byte);
+ } else {
+ $chunks[] = '0' . decoct($byte);
+ }
+ }
+
+ return implode('.', $chunks);
+ }
+
+ /**
+ * Get the hexadecimal representation of this IP address.
+ *
+ * @param bool $long
+ *
+ * @return string
+ *
+ * @since 1.10.0
+ *
+ * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
+ * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
+ */
+ public function toHexadecimal($long = false)
+ {
+ $chunks = array();
+ foreach ($this->getBytes() as $byte) {
+ if ($long) {
+ $chunks[] = sprintf('0x%02x', $byte);
+ } else {
+ $chunks[] = '0x' . dechex($byte);
+ }
+ }
+
+ return implode('.', $chunks);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getBytes()
+ */
+ public function getBytes()
+ {
+ if ($this->bytes === null) {
+ $this->bytes = array_map(
+ function ($chunk) {
+ return (int) $chunk;
+ },
+ explode('.', $this->address)
+ );
+ }
+
+ return $this->bytes;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getBits()
+ */
+ public function getBits()
+ {
+ $parts = array();
+ foreach ($this->getBytes() as $byte) {
+ $parts[] = sprintf('%08b', $byte);
+ }
+
+ return implode('', $parts);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return Type::T_IPv4;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
+ */
+ public static function getDefaultReservedRangeType()
+ {
+ return RangeType::T_PUBLIC;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getReservedRanges()
+ */
+ public static function getReservedRanges()
+ {
+ if (self::$reservedRanges === null) {
+ $reservedRanges = array();
+ foreach (array(
+ // RFC 5735
+ '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
+ // RFC 5735
+ '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
+ // RFC 6598
+ '100.64.0.0/10' => array(RangeType::T_CGNAT),
+ // RFC 5735
+ '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
+ // RFC 5735
+ '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
+ // RFC 5735
+ '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
+ // RFC 5735
+ '192.0.0.0/24' => array(RangeType::T_RESERVED),
+ // RFC 5735
+ '192.0.2.0/24' => array(RangeType::T_RESERVED),
+ // RFC 5735
+ '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
+ // RFC 5735
+ '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
+ // RFC 5735
+ '198.18.0.0/15' => array(RangeType::T_RESERVED),
+ // RFC 5735
+ '198.51.100.0/24' => array(RangeType::T_RESERVED),
+ // RFC 5735
+ '203.0.113.0/24' => array(RangeType::T_RESERVED),
+ // RFC 5735
+ '224.0.0.0/4' => array(RangeType::T_MULTICAST),
+ // RFC 5735
+ '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
+ ) as $range => $data) {
+ $exceptions = array();
+ if (isset($data[1])) {
+ foreach ($data[1] as $exceptionRange => $exceptionType) {
+ $exceptions[] = new AssignedRange(Subnet::parseString($exceptionRange), $exceptionType);
+ }
+ }
+ $reservedRanges[] = new AssignedRange(Subnet::parseString($range), $data[0], $exceptions);
+ }
+ self::$reservedRanges = $reservedRanges;
+ }
+
+ return self::$reservedRanges;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getRangeType()
+ */
+ public function getRangeType()
+ {
+ if ($this->rangeType === null) {
+ $rangeType = null;
+ foreach (static::getReservedRanges() as $reservedRange) {
+ $rangeType = $reservedRange->getAddressType($this);
+ if ($rangeType !== null) {
+ break;
+ }
+ }
+ $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
+ }
+
+ return $this->rangeType;
+ }
+
+ /**
+ * Create an IPv6 representation of this address (in 6to4 notation).
+ *
+ * @return \IPLib\Address\IPv6
+ */
+ public function toIPv6()
+ {
+ $myBytes = $this->getBytes();
+
+ return IPv6::parseString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
+ }
+
+ /**
+ * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
+ *
+ * @return \IPLib\Address\IPv6
+ *
+ * @since 1.11.0
+ */
+ public function toIPv6IPv4Mapped()
+ {
+ return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getComparableString()
+ */
+ public function getComparableString()
+ {
+ if ($this->comparableString === null) {
+ $chunks = array();
+ foreach ($this->getBytes() as $byte) {
+ $chunks[] = sprintf('%03d', $byte);
+ }
+ $this->comparableString = implode('.', $chunks);
+ }
+
+ return $this->comparableString;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::matches()
+ */
+ public function matches(RangeInterface $range)
+ {
+ return $range->contains($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
+ */
+ public function getAddressAtOffset($n)
+ {
+ if (!is_int($n)) {
+ return null;
+ }
+
+ $boundary = 256;
+ $mod = $n;
+ $bytes = $this->getBytes();
+ for ($i = count($bytes) - 1; $i >= 0; $i--) {
+ $tmp = ($bytes[$i] + $mod) % $boundary;
+ $mod = (int) floor(($bytes[$i] + $mod) / $boundary);
+ if ($tmp < 0) {
+ $tmp += $boundary;
+ }
+
+ $bytes[$i] = $tmp;
+ }
+
+ if ($mod !== 0) {
+ return null;
+ }
+
+ return static::fromBytes($bytes);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getNextAddress()
+ */
+ public function getNextAddress()
+ {
+ return $this->getAddressAtOffset(1);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getPreviousAddress()
+ */
+ public function getPreviousAddress()
+ {
+ return $this->getAddressAtOffset(-1);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ return implode(
+ '.',
+ array_reverse($this->getBytes())
+ ) . '.in-addr.arpa';
+ }
+}
diff --git a/mlocati/ip-lib/src/Address/IPv6.php b/mlocati/ip-lib/src/Address/IPv6.php
new file mode 100644
index 00000000..e094881b
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/IPv6.php
@@ -0,0 +1,608 @@
+<?php
+
+namespace IPLib\Address;
+
+use IPLib\ParseStringFlag;
+use IPLib\Range\RangeInterface;
+use IPLib\Range\Subnet;
+use IPLib\Range\Type as RangeType;
+
+/**
+ * An IPv6 address.
+ */
+class IPv6 implements AddressInterface
+{
+ /**
+ * The long string representation of the address.
+ *
+ * @var string
+ *
+ * @example '0000:0000:0000:0000:0000:0000:0000:0001'
+ */
+ protected $longAddress;
+
+ /**
+ * The long string representation of the address.
+ *
+ * @var string|null
+ *
+ * @example '::1'
+ */
+ protected $shortAddress;
+
+ /**
+ * The byte list of the IP address.
+ *
+ * @var int[]|null
+ */
+ protected $bytes;
+
+ /**
+ * The word list of the IP address.
+ *
+ * @var int[]|null
+ */
+ protected $words;
+
+ /**
+ * The type of the range of this IP address.
+ *
+ * @var int|null
+ */
+ protected $rangeType;
+
+ /**
+ * An array containing RFC designated address ranges.
+ *
+ * @var array|null
+ */
+ private static $reservedRanges;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param string $longAddress
+ */
+ public function __construct($longAddress)
+ {
+ $this->longAddress = $longAddress;
+ $this->shortAddress = null;
+ $this->bytes = null;
+ $this->words = null;
+ $this->rangeType = null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getNumberOfBits()
+ */
+ public static function getNumberOfBits()
+ {
+ return 128;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
+ * - if $mayIncludeZoneID is true, use the ParseStringFlag::MAY_INCLUDE_ZONEID flag
+ *
+ * @param string|mixed $address
+ * @param bool $mayIncludePort
+ * @param bool $mayIncludeZoneID
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Address\IPv6::parseString()
+ * @since 1.1.0 added the $mayIncludePort argument
+ * @since 1.3.0 added the $mayIncludeZoneID argument
+ */
+ public static function fromString($address, $mayIncludePort = true, $mayIncludeZoneID = true)
+ {
+ return static::parseString($address, 0 | ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) | ($mayIncludeZoneID ? ParseStringFlag::MAY_INCLUDE_ZONEID : 0));
+ }
+
+ /**
+ * Parse a string and returns an IPv6 instance if the string is valid, or null otherwise.
+ *
+ * @param string|mixed $address the address to parse
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseString($address, $flags = 0)
+ {
+ if (!is_string($address)) {
+ return null;
+ }
+ $matches = null;
+ $flags = (int) $flags;
+ if ($flags & ParseStringFlag::ADDRESS_MAYBE_RDNS) {
+ if (preg_match('/^([0-9a-f](?:\.[0-9a-f]){31})\.ip6\.arpa\.?/i', $address, $matches)) {
+ $nibbles = array_reverse(explode('.', $matches[1]));
+ $quibbles = array();
+ foreach (array_chunk($nibbles, 4) as $n) {
+ $quibbles[] = implode('', $n);
+ }
+ $address = implode(':', $quibbles);
+ }
+ }
+ $result = null;
+ if (is_string($address) && strpos($address, ':') !== false && strpos($address, ':::') === false) {
+ if ($flags & ParseStringFlag::MAY_INCLUDE_PORT && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) {
+ $address = $matches[1];
+ }
+ if ($flags & ParseStringFlag::MAY_INCLUDE_ZONEID) {
+ $percentagePos = strpos($address, '%');
+ if ($percentagePos > 0) {
+ $address = substr($address, 0, $percentagePos);
+ }
+ }
+ if (preg_match('/^((?:[0-9a-f]*:+)+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $address, $matches)) {
+ $address6 = static::parseString($matches[1] . '0:0');
+ if ($address6 !== null) {
+ $address4 = IPv4::parseString($matches[2]);
+ if ($address4 !== null) {
+ $bytes4 = $address4->getBytes();
+ $address6->longAddress = substr($address6->longAddress, 0, -9) . sprintf('%02x%02x:%02x%02x', $bytes4[0], $bytes4[1], $bytes4[2], $bytes4[3]);
+ $result = $address6;
+ }
+ }
+ } else {
+ if (strpos($address, '::') === false) {
+ $chunks = explode(':', $address);
+ } else {
+ $chunks = array();
+ $parts = explode('::', $address);
+ if (count($parts) === 2) {
+ $before = ($parts[0] === '') ? array() : explode(':', $parts[0]);
+ $after = ($parts[1] === '') ? array() : explode(':', $parts[1]);
+ $missing = 8 - count($before) - count($after);
+ if ($missing >= 0) {
+ $chunks = $before;
+ if ($missing !== 0) {
+ $chunks = array_merge($chunks, array_fill(0, $missing, '0'));
+ }
+ $chunks = array_merge($chunks, $after);
+ }
+ }
+ }
+ if (count($chunks) === 8) {
+ $nums = array_map(
+ function ($chunk) {
+ return preg_match('/^[0-9A-Fa-f]{1,4}$/', $chunk) ? hexdec($chunk) : false;
+ },
+ $chunks
+ );
+ if (!in_array(false, $nums, true)) {
+ $longAddress = implode(
+ ':',
+ array_map(
+ function ($num) {
+ return sprintf('%04x', $num);
+ },
+ $nums
+ )
+ );
+ $result = new static($longAddress);
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parse an array of bytes and returns an IPv6 instance if the array is valid, or null otherwise.
+ *
+ * @param int[]|array $bytes
+ *
+ * @return static|null
+ */
+ public static function fromBytes(array $bytes)
+ {
+ $result = null;
+ if (count($bytes) === 16) {
+ $address = '';
+ for ($i = 0; $i < 16; $i++) {
+ if ($i !== 0 && $i % 2 === 0) {
+ $address .= ':';
+ }
+ $byte = $bytes[$i];
+ if (is_int($byte) && $byte >= 0 && $byte <= 255) {
+ $address .= sprintf('%02x', $byte);
+ } else {
+ $address = null;
+ break;
+ }
+ }
+ if ($address !== null) {
+ $result = new static($address);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Parse an array of words and returns an IPv6 instance if the array is valid, or null otherwise.
+ *
+ * @param int[]|array $words
+ *
+ * @return static|null
+ */
+ public static function fromWords(array $words)
+ {
+ $result = null;
+ if (count($words) === 8) {
+ $chunks = array();
+ for ($i = 0; $i < 8; $i++) {
+ $word = $words[$i];
+ if (is_int($word) && $word >= 0 && $word <= 0xffff) {
+ $chunks[] = sprintf('%04x', $word);
+ } else {
+ $chunks = null;
+ break;
+ }
+ }
+ if ($chunks !== null) {
+ $result = new static(implode(':', $chunks));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ if ($long) {
+ $result = $this->longAddress;
+ } else {
+ if ($this->shortAddress === null) {
+ if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
+ $lastBytes = array_slice($this->getBytes(), -4);
+ $this->shortAddress = '::ffff:' . implode('.', $lastBytes);
+ } else {
+ $chunks = array_map(
+ function ($word) {
+ return dechex($word);
+ },
+ $this->getWords()
+ );
+ $shortAddress = implode(':', $chunks);
+ $matches = null;
+ for ($i = 8; $i > 1; $i--) {
+ $search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . '(?:$|:)';
+ if (preg_match('/^(.*?)' . $search . '(.*)$/', $shortAddress, $matches)) {
+ $shortAddress = $matches[1] . '::' . $matches[2];
+ break;
+ }
+ }
+ $this->shortAddress = $shortAddress;
+ }
+ }
+ $result = $this->shortAddress;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getBytes()
+ */
+ public function getBytes()
+ {
+ if ($this->bytes === null) {
+ $bytes = array();
+ foreach ($this->getWords() as $word) {
+ $bytes[] = $word >> 8;
+ $bytes[] = $word & 0xff;
+ }
+ $this->bytes = $bytes;
+ }
+
+ return $this->bytes;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getBits()
+ */
+ public function getBits()
+ {
+ $parts = array();
+ foreach ($this->getBytes() as $byte) {
+ $parts[] = sprintf('%08b', $byte);
+ }
+
+ return implode('', $parts);
+ }
+
+ /**
+ * Get the word list of the IP address.
+ *
+ * @return int[]
+ */
+ public function getWords()
+ {
+ if ($this->words === null) {
+ $this->words = array_map(
+ function ($chunk) {
+ return hexdec($chunk);
+ },
+ explode(':', $this->longAddress)
+ );
+ }
+
+ return $this->words;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return Type::T_IPv6;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
+ */
+ public static function getDefaultReservedRangeType()
+ {
+ return RangeType::T_RESERVED;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getReservedRanges()
+ */
+ public static function getReservedRanges()
+ {
+ if (self::$reservedRanges === null) {
+ $reservedRanges = array();
+ foreach (array(
+ // RFC 4291
+ '::/128' => array(RangeType::T_UNSPECIFIED),
+ // RFC 4291
+ '::1/128' => array(RangeType::T_LOOPBACK),
+ // RFC 4291
+ '100::/8' => array(RangeType::T_DISCARD, array('100::/64' => RangeType::T_DISCARDONLY)),
+ //'2002::/16' => array(RangeType::),
+ // RFC 4291
+ '2000::/3' => array(RangeType::T_PUBLIC),
+ // RFC 4193
+ 'fc00::/7' => array(RangeType::T_PRIVATENETWORK),
+ // RFC 4291
+ 'fe80::/10' => array(RangeType::T_LINKLOCAL_UNICAST),
+ // RFC 4291
+ 'ff00::/8' => array(RangeType::T_MULTICAST),
+ // RFC 4291
+ //'::/8' => array(RangeType::T_RESERVED),
+ // RFC 4048
+ //'200::/7' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'400::/6' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'800::/5' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'1000::/4' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'4000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'6000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'8000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'a000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'c000::/3' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'e000::/4' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'f000::/5' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'f800::/6' => array(RangeType::T_RESERVED),
+ // RFC 4291
+ //'fe00::/9' => array(RangeType::T_RESERVED),
+ // RFC 3879
+ //'fec0::/10' => array(RangeType::T_RESERVED),
+ ) as $range => $data) {
+ $exceptions = array();
+ if (isset($data[1])) {
+ foreach ($data[1] as $exceptionRange => $exceptionType) {
+ $exceptions[] = new AssignedRange(Subnet::parseString($exceptionRange), $exceptionType);
+ }
+ }
+ $reservedRanges[] = new AssignedRange(Subnet::parseString($range), $data[0], $exceptions);
+ }
+ self::$reservedRanges = $reservedRanges;
+ }
+
+ return self::$reservedRanges;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getRangeType()
+ */
+ public function getRangeType()
+ {
+ if ($this->rangeType === null) {
+ $ipv4 = $this->toIPv4();
+ if ($ipv4 !== null) {
+ $this->rangeType = $ipv4->getRangeType();
+ } else {
+ $rangeType = null;
+ foreach (static::getReservedRanges() as $reservedRange) {
+ $rangeType = $reservedRange->getAddressType($this);
+ if ($rangeType !== null) {
+ break;
+ }
+ }
+ $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
+ }
+ }
+
+ return $this->rangeType;
+ }
+
+ /**
+ * Create an IPv4 representation of this address (if possible, otherwise returns null).
+ *
+ * @return \IPLib\Address\IPv4|null
+ */
+ public function toIPv4()
+ {
+ if (strpos($this->longAddress, '2002:') === 0) {
+ // 6to4
+ return IPv4::fromBytes(array_slice($this->getBytes(), 2, 4));
+ }
+ if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
+ // IPv4-mapped IPv6 addresses
+ return IPv4::fromBytes(array_slice($this->getBytes(), -4));
+ }
+
+ return null;
+ }
+
+ /**
+ * Render this IPv6 address in the "mixed" IPv6 (first 12 bytes) + IPv4 (last 4 bytes) mixed syntax.
+ *
+ * @param bool $ipV6Long render the IPv6 part in "long" format?
+ * @param bool $ipV4Long render the IPv4 part in "long" format?
+ *
+ * @return string
+ *
+ * @example '::13.1.68.3'
+ * @example '0000:0000:0000:0000:0000:0000:13.1.68.3' when $ipV6Long is true
+ * @example '::013.001.068.003' when $ipV4Long is true
+ * @example '0000:0000:0000:0000:0000:0000:013.001.068.003' when $ipV6Long and $ipV4Long are true
+ *
+ * @see https://tools.ietf.org/html/rfc4291#section-2.2 point 3.
+ * @since 1.9.0
+ */
+ public function toMixedIPv6IPv4String($ipV6Long = false, $ipV4Long = false)
+ {
+ $myBytes = $this->getBytes();
+ $ipv6Bytes = array_merge(array_slice($myBytes, 0, 12), array(0xff, 0xff, 0xff, 0xff));
+ $ipv6String = static::fromBytes($ipv6Bytes)->toString($ipV6Long);
+ $ipv4Bytes = array_slice($myBytes, 12, 4);
+ $ipv4String = IPv4::fromBytes($ipv4Bytes)->toString($ipV4Long);
+
+ return preg_replace('/((ffff:ffff)|(\d+(\.\d+){3}))$/i', $ipv4String, $ipv6String);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getComparableString()
+ */
+ public function getComparableString()
+ {
+ return $this->longAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::matches()
+ */
+ public function matches(RangeInterface $range)
+ {
+ return $range->contains($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
+ */
+ public function getAddressAtOffset($n)
+ {
+ if (!is_int($n)) {
+ return null;
+ }
+
+ $boundary = 0x10000;
+ $mod = $n;
+ $words = $this->getWords();
+ for ($i = count($words) - 1; $i >= 0; $i--) {
+ $tmp = ($words[$i] + $mod) % $boundary;
+ $mod = (int) floor(($words[$i] + $mod) / $boundary);
+ if ($tmp < 0) {
+ $tmp += $boundary;
+ }
+
+ $words[$i] = $tmp;
+ }
+
+ if ($mod !== 0) {
+ return null;
+ }
+
+ return static::fromWords($words);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getNextAddress()
+ */
+ public function getNextAddress()
+ {
+ return $this->getAddressAtOffset(1);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getPreviousAddress()
+ */
+ public function getPreviousAddress()
+ {
+ return $this->getAddressAtOffset(-1);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ return implode(
+ '.',
+ array_reverse(str_split(str_replace(':', '', $this->toString(true)), 1))
+ ) . '.ip6.arpa';
+ }
+}
diff --git a/mlocati/ip-lib/src/Address/Type.php b/mlocati/ip-lib/src/Address/Type.php
new file mode 100644
index 00000000..17488f38
--- /dev/null
+++ b/mlocati/ip-lib/src/Address/Type.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace IPLib\Address;
+
+/**
+ * Types of IP addresses.
+ */
+class Type
+{
+ /**
+ * IPv4 address.
+ *
+ * @var int
+ */
+ const T_IPv4 = 4;
+
+ /**
+ * IPv6 address.
+ *
+ * @var int
+ */
+ const T_IPv6 = 6;
+
+ /**
+ * Get the name of a type.
+ *
+ * @param int $type
+ *
+ * @return string
+ *
+ * @since 1.1.0
+ */
+ public static function getName($type)
+ {
+ switch ($type) {
+ case static::T_IPv4:
+ return 'IP v4';
+ case static::T_IPv6:
+ return 'IP v6';
+ default:
+ return sprintf('Unknown type (%s)', $type);
+ }
+ }
+}
diff --git a/mlocati/ip-lib/src/Factory.php b/mlocati/ip-lib/src/Factory.php
new file mode 100644
index 00000000..31b21d41
--- /dev/null
+++ b/mlocati/ip-lib/src/Factory.php
@@ -0,0 +1,298 @@
+<?php
+
+namespace IPLib;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Range\Subnet;
+use IPLib\Service\RangesFromBoundaryCalculator;
+
+/**
+ * Factory methods to build class instances.
+ */
+class Factory
+{
+ /**
+ * @deprecated since 1.17.0: use the parseAddressString() method instead.
+ * For upgrading:
+ * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag
+ * - if $mayIncludeZoneID is true, use the ParseStringFlag::MAY_INCLUDE_ZONEID flag
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $address
+ * @param bool $mayIncludePort
+ * @param bool $mayIncludeZoneID
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Factory::parseAddressString()
+ * @since 1.1.0 added the $mayIncludePort argument
+ * @since 1.3.0 added the $mayIncludeZoneID argument
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function addressFromString($address, $mayIncludePort = true, $mayIncludeZoneID = true, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseAddressString($address, 0 + ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) + ($mayIncludeZoneID ? ParseStringFlag::MAY_INCLUDE_ZONEID : 0) + ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Parse an IP address string.
+ *
+ * @param string|mixed $address the address to parse
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseAddressString($address, $flags = 0)
+ {
+ $result = null;
+ if ($result === null) {
+ $result = Address\IPv4::parseString($address, $flags);
+ }
+ if ($result === null) {
+ $result = Address\IPv6::parseString($address, $flags);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert a byte array to an address instance.
+ *
+ * @param int[]|array $bytes
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ */
+ public static function addressFromBytes(array $bytes)
+ {
+ $result = null;
+ if ($result === null) {
+ $result = Address\IPv4::fromBytes($bytes);
+ }
+ if ($result === null) {
+ $result = Address\IPv6::fromBytes($bytes);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseRangeString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $address
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Factory::parseRangeString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function rangeFromString($address, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseRangeString($address, $supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0);
+ }
+
+ /**
+ * Parse an IP range string.
+ *
+ * @param string $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Range\RangeInterface|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseRangeString($range, $flags = 0)
+ {
+ $result = null;
+ if ($result === null) {
+ $result = Range\Subnet::parseString($range, $flags);
+ }
+ if ($result === null) {
+ $result = Range\Pattern::parseString($range, $flags);
+ }
+ if ($result === null) {
+ $result = Range\Single::parseString($range, $flags);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the getRangeFromBoundaries() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|\IPLib\Address\AddressInterface|mixed $from
+ * @param string|\IPLib\Address\AddressInterface|mixed $to
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Address\AddressInterface|null
+ *
+ * @see \IPLib\Factory::getRangeFromBoundaries()
+ * @since 1.2.0
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function rangeFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
+ {
+ return static::getRangeFromBoundaries($from, $to, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Create the smallest address range that comprises two addresses.
+ *
+ * @param string|\IPLib\Address\AddressInterface|mixed $from
+ * @param string|\IPLib\Address\AddressInterface|mixed $to
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Range\RangeInterface|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function getRangeFromBoundaries($from, $to, $flags = 0)
+ {
+ list($from, $to) = self::parseBoundaries($from, $to, $flags);
+
+ return $from === false || $to === false ? null : static::rangeFromBoundaryAddresses($from, $to);
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the getRangesFromBoundaries() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|\IPLib\Address\AddressInterface|mixed $from
+ * @param string|\IPLib\Address\AddressInterface|mixed $to
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return \IPLib\Range\Subnet[]|null
+ *
+ * @see \IPLib\Factory::getRangesFromBoundaries()
+ * @since 1.14.0
+ */
+ public static function rangesFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
+ {
+ return static::getRangesFromBoundaries($from, $to, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Create a list of Range instances that exactly describes all the addresses between the two provided addresses.
+ *
+ * @param string|\IPLib\Address\AddressInterface $from
+ * @param string|\IPLib\Address\AddressInterface $to
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return \IPLib\Range\Subnet[]|null return NULL if $from and/or $to are invalid addresses, or if both are NULL or empty strings, or if they are addresses of different types
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function getRangesFromBoundaries($from, $to, $flags = 0)
+ {
+ list($from, $to) = self::parseBoundaries($from, $to, $flags);
+ if ($from === false || $to === false || ($from === null && $to === null)) {
+ return null;
+ }
+ if ($from === null || $to === null) {
+ $address = $from ? $from : $to;
+
+ return array(new Subnet($address, $address, $address->getNumberOfBits()));
+ }
+ $numberOfBits = $from->getNumberOfBits();
+ if ($to->getNumberOfBits() !== $numberOfBits) {
+ return null;
+ }
+ $calculator = new RangesFromBoundaryCalculator($numberOfBits);
+
+ return $calculator->getRanges($from, $to);
+ }
+
+ /**
+ * @param \IPLib\Address\AddressInterface $from
+ * @param \IPLib\Address\AddressInterface $to
+ *
+ * @return \IPLib\Range\RangeInterface|null
+ *
+ * @since 1.2.0
+ */
+ protected static function rangeFromBoundaryAddresses(AddressInterface $from = null, AddressInterface $to = null)
+ {
+ if ($from === null && $to === null) {
+ $result = null;
+ } elseif ($to === null) {
+ $result = Range\Single::fromAddress($from);
+ } elseif ($from === null) {
+ $result = Range\Single::fromAddress($to);
+ } else {
+ $result = null;
+ $addressType = $from->getAddressType();
+ if ($addressType === $to->getAddressType()) {
+ $cmp = strcmp($from->getComparableString(), $to->getComparableString());
+ if ($cmp === 0) {
+ $result = Range\Single::fromAddress($from);
+ } else {
+ if ($cmp > 0) {
+ list($from, $to) = array($to, $from);
+ }
+ $fromBytes = $from->getBytes();
+ $toBytes = $to->getBytes();
+ $numBytes = count($fromBytes);
+ $sameBits = 0;
+ for ($byteIndex = 0; $byteIndex < $numBytes; $byteIndex++) {
+ $fromByte = $fromBytes[$byteIndex];
+ $toByte = $toBytes[$byteIndex];
+ if ($fromByte === $toByte) {
+ $sameBits += 8;
+ } else {
+ $differentBitsInByte = decbin($fromByte ^ $toByte);
+ $sameBits += 8 - strlen($differentBitsInByte);
+ break;
+ }
+ }
+ $result = static::parseRangeString($from->toString() . '/' . (string) $sameBits);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string|\IPLib\Address\AddressInterface $from
+ * @param string|\IPLib\Address\AddressInterface $to
+ * @param int $flags
+ *
+ * @return \IPLib\Address\AddressInterface[]|null[]|false[]
+ */
+ private static function parseBoundaries($from, $to, $flags = 0)
+ {
+ $result = array();
+ foreach (array('from', 'to') as $param) {
+ $value = $$param;
+ if (!($value instanceof AddressInterface)) {
+ $value = (string) $value;
+ if ($value === '') {
+ $value = null;
+ } else {
+ $value = static::parseAddressString($value, $flags);
+ if ($value === null) {
+ $value = false;
+ }
+ }
+ }
+ $result[] = $value;
+ }
+ if ($result[0] && $result[1] && strcmp($result[0]->getComparableString(), $result[1]->getComparableString()) > 0) {
+ $result = array($result[1], $result[0]);
+ }
+
+ return $result;
+ }
+}
diff --git a/mlocati/ip-lib/src/ParseStringFlag.php b/mlocati/ip-lib/src/ParseStringFlag.php
new file mode 100644
index 00000000..860256e7
--- /dev/null
+++ b/mlocati/ip-lib/src/ParseStringFlag.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace IPLib;
+
+/**
+ * Flags for the parseString() methods.
+ *
+ * @since 1.17.0
+ */
+class ParseStringFlag
+{
+ /**
+ * Use this flag if the input string may include the port.
+ *
+ * @var int
+ */
+ const MAY_INCLUDE_PORT = 1;
+
+ /**
+ * Use this flag if the input string may include a zone ID.
+ *
+ * @var int
+ */
+ const MAY_INCLUDE_ZONEID = 2;
+
+ /**
+ * Use this flag if IPv4 addresses may be in decimal/octal/hexadecimal format.
+ * This notation is accepted by the implementation of inet_aton and inet_addr of the libc implementation of GNU, Windows and Mac (but not Musl), but not by inet_pton and ip2long.
+ *
+ * @var int
+ *
+ * @example 1.08.0x10.0 => 5.0.0.1
+ * @example 5.256 => 5.0.1.0
+ * @example 5.0.256 => 5.0.1.0
+ * @example 123456789 => 7.91.205.21
+ */
+ const IPV4_MAYBE_NON_DECIMAL = 4;
+
+ /**
+ * Use this flag if IPv4 subnet ranges may be in compact form.
+ *
+ * @example 127/24 => 127.0.0.0/24
+ * @example 10/8 => 10.0.0.0/8
+ * @example 10/24 => 10.0.0.0/24
+ * @example 10.10.10/24 => 10.10.10.0/24
+ *
+ * @var int
+ */
+ const IPV4SUBNET_MAYBE_COMPACT = 8;
+
+ /**
+ * Use this flag if IPv4 addresses may be in non quad-dotted notation.
+ * This notation is accepted by the implementation of inet_aton and inet_addr of the libc implementation of GNU, Windows and Mac (but not Musl), but not by inet_pton and ip2long.
+ *
+ * @var int
+ *
+ * @example 5.1 => 5.0.0.1
+ * @example 5.256 => 5.0.1.0
+ * @example 5.0.256 => 5.0.1.0
+ * @example 123456789 => 7.91.205.21
+ *
+ * @see https://man7.org/linux/man-pages/man3/inet_addr.3.html#DESCRIPTION
+ * @see https://www.freebsd.org/cgi/man.cgi?query=inet_net&sektion=3&apropos=0&manpath=FreeBSD+12.2-RELEASE+and+Ports#end
+ * @see http://git.musl-libc.org/cgit/musl/tree/src/network/inet_aton.c?h=v1.2.2
+ */
+ const IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED = 16;
+
+ /**
+ * Use this flag if you want to accept parsing IPv4/IPv6 addresses in Reverse DNS Lookup Address format.
+ *
+ * @var int
+ *
+ * @since 1.18.0
+ *
+ * @example 140.13.12.10.in-addr.arpa => 10.12.13.140
+ * @example b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.ip6.arpa => 4321:0:1:2:3:4:567:89ab
+ */
+ const ADDRESS_MAYBE_RDNS = 32;
+}
diff --git a/mlocati/ip-lib/src/Range/AbstractRange.php b/mlocati/ip-lib/src/Range/AbstractRange.php
new file mode 100644
index 00000000..12204350
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/AbstractRange.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\IPv6;
+use IPLib\Address\Type as AddressType;
+use IPLib\Factory;
+
+/**
+ * Base class for range classes.
+ */
+abstract class AbstractRange implements RangeInterface
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getRangeType()
+ */
+ public function getRangeType()
+ {
+ if ($this->rangeType === null) {
+ $addressType = $this->getAddressType();
+ if ($addressType === AddressType::T_IPv6 && Subnet::get6to4()->containsRange($this)) {
+ $this->rangeType = Factory::getRangeFromBoundaries($this->fromAddress->toIPv4(), $this->toAddress->toIPv4())->getRangeType();
+ } else {
+ switch ($addressType) {
+ case AddressType::T_IPv4:
+ $defaultType = IPv4::getDefaultReservedRangeType();
+ $reservedRanges = IPv4::getReservedRanges();
+ break;
+ case AddressType::T_IPv6:
+ $defaultType = IPv6::getDefaultReservedRangeType();
+ $reservedRanges = IPv6::getReservedRanges();
+ break;
+ default:
+ throw new \Exception('@todo'); // @codeCoverageIgnore
+ }
+ $rangeType = null;
+ foreach ($reservedRanges as $reservedRange) {
+ $rangeType = $reservedRange->getRangeType($this);
+ if ($rangeType !== null) {
+ break;
+ }
+ }
+ $this->rangeType = $rangeType === null ? $defaultType : $rangeType;
+ }
+ }
+
+ return $this->rangeType === false ? null : $this->rangeType;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressAtOffset()
+ */
+ public function getAddressAtOffset($n)
+ {
+ if (!is_int($n)) {
+ return null;
+ }
+
+ $address = null;
+ if ($n >= 0) {
+ $start = Factory::parseAddressString($this->getComparableStartString());
+ $address = $start->getAddressAtOffset($n);
+ } else {
+ $end = Factory::parseAddressString($this->getComparableEndString());
+ $address = $end->getAddressAtOffset($n + 1);
+ }
+
+ if ($address === null) {
+ return null;
+ }
+
+ return $this->contains($address) ? $address : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::contains()
+ */
+ public function contains(AddressInterface $address)
+ {
+ $result = false;
+ if ($address->getAddressType() === $this->getAddressType()) {
+ $cmp = $address->getComparableString();
+ $from = $this->getComparableStartString();
+ if ($cmp >= $from) {
+ $to = $this->getComparableEndString();
+ if ($cmp <= $to) {
+ $result = true;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::containsRange()
+ */
+ public function containsRange(RangeInterface $range)
+ {
+ $result = false;
+ if ($range->getAddressType() === $this->getAddressType()) {
+ $myStart = $this->getComparableStartString();
+ $itsStart = $range->getComparableStartString();
+ if ($itsStart >= $myStart) {
+ $myEnd = $this->getComparableEndString();
+ $itsEnd = $range->getComparableEndString();
+ if ($itsEnd <= $myEnd) {
+ $result = true;
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/Pattern.php b/mlocati/ip-lib/src/Range/Pattern.php
new file mode 100644
index 00000000..1e180ead
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Pattern.php
@@ -0,0 +1,322 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\IPv6;
+use IPLib\Address\Type as AddressType;
+use IPLib\ParseStringFlag;
+
+/**
+ * Represents an address range in pattern format (only ending asterisks are supported).
+ *
+ * @example 127.0.*.*
+ * @example ::/8
+ */
+class Pattern extends AbstractRange
+{
+ /**
+ * Starting address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $fromAddress;
+
+ /**
+ * Final address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $toAddress;
+
+ /**
+ * Number of ending asterisks.
+ *
+ * @var int
+ */
+ protected $asterisksCount;
+
+ /**
+ * The type of the range of this IP range.
+ *
+ * @var int|false|null false if this range crosses multiple range types, null if yet to be determined
+ *
+ * @since 1.5.0
+ */
+ protected $rangeType;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param \IPLib\Address\AddressInterface $fromAddress
+ * @param \IPLib\Address\AddressInterface $toAddress
+ * @param int $asterisksCount
+ */
+ public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $asterisksCount)
+ {
+ $this->fromAddress = $fromAddress;
+ $this->toAddress = $toAddress;
+ $this->asterisksCount = $asterisksCount;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $range
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Range\Pattern::parseString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($range, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Try get the range instance starting from its string representation.
+ *
+ * @param string|mixed $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @since 1.17.0
+ * @see \IPLib\ParseStringFlag
+ */
+ public static function parseString($range, $flags = 0)
+ {
+ if (!is_string($range) || strpos($range, '*') === false) {
+ return null;
+ }
+ if ($range === '*.*.*.*') {
+ return new static(IPv4::parseString('0.0.0.0'), IPv4::parseString('255.255.255.255'), 4);
+ }
+ if ($range === '*:*:*:*:*:*:*:*') {
+ return new static(IPv6::parseString('::'), IPv6::parseString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8);
+ }
+ $matches = null;
+ if (strpos($range, '.') !== false && preg_match('/^[^*]+((?:\.\*)+)$/', $range, $matches)) {
+ $asterisksCount = strlen($matches[1]) >> 1;
+ if ($asterisksCount > 0) {
+ $missingDots = 3 - substr_count($range, '.');
+ if ($missingDots > 0) {
+ $range .= str_repeat('.*', $missingDots);
+ $asterisksCount += $missingDots;
+ }
+ }
+ $fromAddress = IPv4::parseString(str_replace('*', '0', $range), $flags);
+ if ($fromAddress === null) {
+ return null;
+ }
+ $fixedBytes = array_slice($fromAddress->getBytes(), 0, -$asterisksCount);
+ $otherBytes = array_fill(0, $asterisksCount, 255);
+ $toAddress = IPv4::fromBytes(array_merge($fixedBytes, $otherBytes));
+
+ return new static($fromAddress, $toAddress, $asterisksCount);
+ }
+ if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) {
+ $asterisksCount = strlen($matches[1]) >> 1;
+ $fromAddress = IPv6::parseString(str_replace('*', '0', $range));
+ if ($fromAddress === null) {
+ return null;
+ }
+ $fixedWords = array_slice($fromAddress->getWords(), 0, -$asterisksCount);
+ $otherWords = array_fill(0, $asterisksCount, 0xffff);
+ $toAddress = IPv6::fromWords(array_merge($fixedWords, $otherWords));
+
+ return new static($fromAddress, $toAddress, $asterisksCount);
+ }
+
+ return null;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ if ($this->asterisksCount === 0) {
+ return $this->fromAddress->toString($long);
+ }
+ switch (true) {
+ case $this->fromAddress instanceof \IPLib\Address\IPv4:
+ $chunks = explode('.', $this->fromAddress->toString());
+ $chunks = array_slice($chunks, 0, -$this->asterisksCount);
+ $chunks = array_pad($chunks, 4, '*');
+ $result = implode('.', $chunks);
+ break;
+ case $this->fromAddress instanceof \IPLib\Address\IPv6:
+ if ($long) {
+ $chunks = explode(':', $this->fromAddress->toString(true));
+ $chunks = array_slice($chunks, 0, -$this->asterisksCount);
+ $chunks = array_pad($chunks, 8, '*');
+ $result = implode(':', $chunks);
+ } elseif ($this->asterisksCount === 8) {
+ $result = '*:*:*:*:*:*:*:*';
+ } else {
+ $bytes = $this->toAddress->getBytes();
+ $bytes = array_slice($bytes, 0, -$this->asterisksCount * 2);
+ $bytes = array_pad($bytes, 16, 1);
+ $address = IPv6::fromBytes($bytes);
+ $before = substr($address->toString(false), 0, -strlen(':101') * $this->asterisksCount);
+ $result = $before . str_repeat(':*', $this->asterisksCount);
+ }
+ break;
+ default:
+ throw new \Exception('@todo'); // @codeCoverageIgnore
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return $this->fromAddress->getAddressType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getStartAddress()
+ */
+ public function getStartAddress()
+ {
+ return $this->fromAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getEndAddress()
+ */
+ public function getEndAddress()
+ {
+ return $this->toAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableStartString()
+ */
+ public function getComparableStartString()
+ {
+ return $this->fromAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableEndString()
+ */
+ public function getComparableEndString()
+ {
+ return $this->toAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asSubnet()
+ * @since 1.8.0
+ */
+ public function asSubnet()
+ {
+ return new Subnet($this->getStartAddress(), $this->getEndAddress(), $this->getNetworkPrefix());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asPattern()
+ */
+ public function asPattern()
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSubnetMask()
+ */
+ public function getSubnetMask()
+ {
+ if ($this->getAddressType() !== AddressType::T_IPv4) {
+ return null;
+ }
+ switch ($this->asterisksCount) {
+ case 0:
+ $bytes = array(255, 255, 255, 255);
+ break;
+ case 4:
+ $bytes = array(0, 0, 0, 0);
+ break;
+ default:
+ $bytes = array_pad(array_fill(0, 4 - $this->asterisksCount, 255), 4, 0);
+ break;
+ }
+
+ return IPv4::fromBytes($bytes);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ return $this->asterisksCount === 0 ? array($this->getStartAddress()->getReverseDNSLookupName()) : $this->asSubnet()->getReverseDNSLookupName();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSize()
+ */
+ public function getSize()
+ {
+ $fromAddress = $this->fromAddress;
+ $maxPrefix = $fromAddress::getNumberOfBits();
+ $prefix = $this->getNetworkPrefix();
+
+ return pow(2, ($maxPrefix - $prefix));
+ }
+
+ /**
+ * @return float|int
+ */
+ private function getNetworkPrefix()
+ {
+ switch ($this->getAddressType()) {
+ case AddressType::T_IPv4:
+ return 8 * (4 - $this->asterisksCount);
+ case AddressType::T_IPv6:
+ return 16 * (8 - $this->asterisksCount);
+ }
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/RangeInterface.php b/mlocati/ip-lib/src/Range/RangeInterface.php
new file mode 100644
index 00000000..4d4d8347
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/RangeInterface.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+
+/**
+ * Interface of all the range types.
+ */
+interface RangeInterface
+{
+ /**
+ * Get the short string representation of this address.
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Get the string representation of this address.
+ *
+ * @param bool $long set to true to have a long/full representation, false otherwise
+ *
+ * @return string
+ *
+ * @example If $long is true, you'll get '0000:0000:0000:0000:0000:0000:0000:0001/128', '::1/128' otherwise.
+ */
+ public function toString($long = false);
+
+ /**
+ * Get the type of the IP addresses contained in this range.
+ *
+ * @return int One of the \IPLib\Address\Type::T_... constants
+ */
+ public function getAddressType();
+
+ /**
+ * Get the type of range of the IP address.
+ *
+ * @return int One of the \IPLib\Range\Type::T_... constants
+ *
+ * @since 1.5.0
+ */
+ public function getRangeType();
+
+ /**
+ * Get the address at a certain offset of this range.
+ *
+ * @param int $n the offset of the address (support negative offset)
+ *
+ * @return \IPLib\Address\AddressInterface|null return NULL if $n is not an integer or if the offset out of range
+ *
+ * @since 1.15.0
+ *
+ * @example passing 256 to the range 127.0.0.0/16 will result in 127.0.1.0
+ * @example passing -1 to the range 127.0.1.0/16 will result in 127.0.255.255
+ * @example passing 256 to the range 127.0.0.0/24 will result in NULL
+ */
+ public function getAddressAtOffset($n);
+
+ /**
+ * Check if this range contains an IP address.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ *
+ * @return bool
+ */
+ public function contains(AddressInterface $address);
+
+ /**
+ * Check if this range contains another range.
+ *
+ * @param \IPLib\Range\RangeInterface $range
+ *
+ * @return bool
+ *
+ * @since 1.5.0
+ */
+ public function containsRange(RangeInterface $range);
+
+ /**
+ * Get the initial address contained in this range.
+ *
+ * @return \IPLib\Address\AddressInterface
+ *
+ * @since 1.4.0
+ */
+ public function getStartAddress();
+
+ /**
+ * Get the final address contained in this range.
+ *
+ * @return \IPLib\Address\AddressInterface
+ *
+ * @since 1.4.0
+ */
+ public function getEndAddress();
+
+ /**
+ * Get a string representation of the starting address of this range than can be used when comparing addresses and ranges.
+ *
+ * @return string
+ */
+ public function getComparableStartString();
+
+ /**
+ * Get a string representation of the final address of this range than can be used when comparing addresses and ranges.
+ *
+ * @return string
+ */
+ public function getComparableEndString();
+
+ /**
+ * Get the subnet mask representing this range (only for IPv4 ranges).
+ *
+ * @return \IPLib\Address\IPv4|null return NULL if the range is an IPv6 range, the subnet mask otherwise
+ *
+ * @since 1.8.0
+ */
+ public function getSubnetMask();
+
+ /**
+ * Get the subnet/CIDR representation of this range.
+ *
+ * @return \IPLib\Range\Subnet
+ *
+ * @since 1.13.0
+ */
+ public function asSubnet();
+
+ /**
+ * Get the pattern/asterisk representation (if applicable) of this range.
+ *
+ * @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
+ *
+ * @since 1.13.0
+ */
+ public function asPattern();
+
+ /**
+ * Get the Reverse DNS Lookup Addresses of this IP range.
+ *
+ * @return string[]
+ *
+ * @since 1.13.0
+ *
+ * @example for IPv4 it returns something like array('x.x.x.x.in-addr.arpa', 'x.x.x.x.in-addr.arpa') (where the number of 'x.' ranges from 1 to 4)
+ * @example for IPv6 it returns something like array('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', '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') (where the number of 'x.' ranges from 1 to 32)
+ */
+ public function getReverseDNSLookupName();
+
+ /**
+ * Get the count of addresses this IP range contains.
+ *
+ * @return int|float Return float as for huge IPv6 networks, int is not enough
+ *
+ * @since 1.16.0
+ */
+ public function getSize();
+}
diff --git a/mlocati/ip-lib/src/Range/Single.php b/mlocati/ip-lib/src/Range/Single.php
new file mode 100644
index 00000000..ec3531f6
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Single.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\Type as AddressType;
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+/**
+ * Represents a single address (eg a range that contains just one address).
+ *
+ * @example 127.0.0.1
+ * @example ::1
+ */
+class Single extends AbstractRange
+{
+ /**
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $address;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ */
+ protected function __construct(AddressInterface $address)
+ {
+ $this->address = $address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->address->__toString();
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $range
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Range\Single::parseString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($range, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Try get the range instance starting from its string representation.
+ *
+ * @param string|mixed $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseString($range, $flags = 0)
+ {
+ $result = null;
+ $flags = (int) $flags;
+ $address = Factory::parseAddressString($range, $flags);
+ if ($address !== null) {
+ $result = new static($address);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create the range instance starting from an address instance.
+ *
+ * @param \IPLib\Address\AddressInterface $address
+ *
+ * @return static
+ *
+ * @since 1.2.0
+ */
+ public static function fromAddress(AddressInterface $address)
+ {
+ return new static($address);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ return $this->address->toString($long);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return $this->address->getAddressType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getRangeType()
+ */
+ public function getRangeType()
+ {
+ return $this->address->getRangeType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::contains()
+ */
+ public function contains(AddressInterface $address)
+ {
+ $result = false;
+ if ($address->getAddressType() === $this->getAddressType()) {
+ if ($address->toString(false) === $this->address->toString(false)) {
+ $result = true;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getStartAddress()
+ */
+ public function getStartAddress()
+ {
+ return $this->address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getEndAddress()
+ */
+ public function getEndAddress()
+ {
+ return $this->address;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableStartString()
+ */
+ public function getComparableStartString()
+ {
+ return $this->address->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableEndString()
+ */
+ public function getComparableEndString()
+ {
+ return $this->address->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asSubnet()
+ */
+ public function asSubnet()
+ {
+ $networkPrefixes = array(
+ AddressType::T_IPv4 => 32,
+ AddressType::T_IPv6 => 128,
+ );
+
+ return new Subnet($this->address, $this->address, $networkPrefixes[$this->address->getAddressType()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asPattern()
+ */
+ public function asPattern()
+ {
+ return new Pattern($this->address, $this->address, 0);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSubnetMask()
+ */
+ public function getSubnetMask()
+ {
+ if ($this->getAddressType() !== AddressType::T_IPv4) {
+ return null;
+ }
+
+ return IPv4::fromBytes(array(255, 255, 255, 255));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ return array($this->getStartAddress()->getReverseDNSLookupName());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSize()
+ */
+ public function getSize()
+ {
+ return 1;
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/Subnet.php b/mlocati/ip-lib/src/Range/Subnet.php
new file mode 100644
index 00000000..ea397622
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Subnet.php
@@ -0,0 +1,354 @@
+<?php
+
+namespace IPLib\Range;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Address\IPv4;
+use IPLib\Address\Type as AddressType;
+use IPLib\Factory;
+use IPLib\ParseStringFlag;
+
+/**
+ * Represents an address range in subnet format (eg CIDR).
+ *
+ * @example 127.0.0.1/32
+ * @example ::/8
+ */
+class Subnet extends AbstractRange
+{
+ /**
+ * Starting address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $fromAddress;
+
+ /**
+ * Final address of the range.
+ *
+ * @var \IPLib\Address\AddressInterface
+ */
+ protected $toAddress;
+
+ /**
+ * Number of the same bits of the range.
+ *
+ * @var int
+ */
+ protected $networkPrefix;
+
+ /**
+ * The type of the range of this IP range.
+ *
+ * @var int|null
+ *
+ * @since 1.5.0
+ */
+ protected $rangeType;
+
+ /**
+ * The 6to4 address IPv6 address range.
+ *
+ * @var self|null
+ */
+ private static $sixToFour;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param \IPLib\Address\AddressInterface $fromAddress
+ * @param \IPLib\Address\AddressInterface $toAddress
+ * @param int $networkPrefix
+ *
+ * @internal
+ */
+ public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
+ {
+ $this->fromAddress = $fromAddress;
+ $this->toAddress = $toAddress;
+ $this->networkPrefix = $networkPrefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::__toString()
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * @deprecated since 1.17.0: use the parseString() method instead.
+ * For upgrading:
+ * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
+ *
+ * @param string|mixed $range
+ * @param bool $supportNonDecimalIPv4
+ *
+ * @return static|null
+ *
+ * @see \IPLib\Range\Subnet::parseString()
+ * @since 1.10.0 added the $supportNonDecimalIPv4 argument
+ */
+ public static function fromString($range, $supportNonDecimalIPv4 = false)
+ {
+ return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
+ }
+
+ /**
+ * Try get the range instance starting from its string representation.
+ *
+ * @param string|mixed $range
+ * @param int $flags A combination or zero or more flags
+ *
+ * @return static|null
+ *
+ * @see \IPLib\ParseStringFlag
+ * @since 1.17.0
+ */
+ public static function parseString($range, $flags = 0)
+ {
+ if (!is_string($range)) {
+ return null;
+ }
+ $parts = explode('/', $range);
+ if (count($parts) !== 2) {
+ return null;
+ }
+ $flags = (int) $flags;
+ if (strpos($parts[0], ':') === false && $flags & ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT) {
+ $missingDots = 3 - substr_count($parts[0], '.');
+ if ($missingDots > 0) {
+ $parts[0] .= str_repeat('.0', $missingDots);
+ }
+ }
+ $address = Factory::parseAddressString($parts[0], $flags);
+ if ($address === null) {
+ return null;
+ }
+ if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
+ return null;
+ }
+ $networkPrefix = (int) $parts[1];
+ $addressBytes = $address->getBytes();
+ $totalBytes = count($addressBytes);
+ $numDifferentBits = $totalBytes * 8 - $networkPrefix;
+ if ($numDifferentBits < 0) {
+ return null;
+ }
+ $numSameBytes = $networkPrefix >> 3;
+ $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
+ $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
+ $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
+ $startSameBits = $networkPrefix % 8;
+ if ($startSameBits !== 0) {
+ $varyingByte = $addressBytes[$numSameBytes];
+ $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
+ $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
+ }
+
+ return new static(
+ Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
+ Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
+ $networkPrefix
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::toString()
+ */
+ public function toString($long = false)
+ {
+ return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getAddressType()
+ */
+ public function getAddressType()
+ {
+ return $this->fromAddress->getAddressType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getStartAddress()
+ */
+ public function getStartAddress()
+ {
+ return $this->fromAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getEndAddress()
+ */
+ public function getEndAddress()
+ {
+ return $this->toAddress;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableStartString()
+ */
+ public function getComparableStartString()
+ {
+ return $this->fromAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getComparableEndString()
+ */
+ public function getComparableEndString()
+ {
+ return $this->toAddress->getComparableString();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asSubnet()
+ */
+ public function asSubnet()
+ {
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::asPattern()
+ * @since 1.8.0
+ */
+ public function asPattern()
+ {
+ $address = $this->getStartAddress();
+ $networkPrefix = $this->getNetworkPrefix();
+ switch ($address->getAddressType()) {
+ case AddressType::T_IPv4:
+ return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
+ case AddressType::T_IPv6:
+ return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
+ }
+ }
+
+ /**
+ * Get the 6to4 address IPv6 address range.
+ *
+ * @return self
+ *
+ * @since 1.5.0
+ */
+ public static function get6to4()
+ {
+ if (self::$sixToFour === null) {
+ self::$sixToFour = self::parseString('2002::/16');
+ }
+
+ return self::$sixToFour;
+ }
+
+ /**
+ * Get subnet prefix.
+ *
+ * @return int
+ *
+ * @since 1.7.0
+ */
+ public function getNetworkPrefix()
+ {
+ return $this->networkPrefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSubnetMask()
+ */
+ public function getSubnetMask()
+ {
+ if ($this->getAddressType() !== AddressType::T_IPv4) {
+ return null;
+ }
+ $bytes = array();
+ $prefix = $this->getNetworkPrefix();
+ while ($prefix >= 8) {
+ $bytes[] = 255;
+ $prefix -= 8;
+ }
+ if ($prefix !== 0) {
+ $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
+ }
+ $bytes = array_pad($bytes, 4, 0);
+
+ return IPv4::fromBytes($bytes);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
+ */
+ public function getReverseDNSLookupName()
+ {
+ switch ($this->getAddressType()) {
+ case AddressType::T_IPv4:
+ $unitSize = 8; // bytes
+ $maxUnits = 4;
+ $isHex = false;
+ $rxUnit = '\d+';
+ break;
+ case AddressType::T_IPv6:
+ $unitSize = 4; // nibbles
+ $maxUnits = 32;
+ $isHex = true;
+ $rxUnit = '[0-9A-Fa-f]';
+ break;
+ }
+ $totBits = $unitSize * $maxUnits;
+ $prefixUnits = (int) ($this->networkPrefix / $unitSize);
+ $extraBits = ($totBits - $this->networkPrefix) % $unitSize;
+ if ($extraBits !== 0) {
+ $prefixUnits += 1;
+ }
+ $numVariants = 1 << $extraBits;
+ $result = array();
+ $unitsToRemove = $maxUnits - $prefixUnits;
+ $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName());
+ $chunks = explode('.', $initialPointer, 2);
+ for ($index = 0; $index < $numVariants; $index++) {
+ if ($index !== 0) {
+ $chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
+ }
+ $result[] = implode('.', $chunks);
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see \IPLib\Range\RangeInterface::getSize()
+ */
+ public function getSize()
+ {
+ $fromAddress = $this->fromAddress;
+ $maxPrefix = $fromAddress::getNumberOfBits();
+ $prefix = $this->getNetworkPrefix();
+
+ return pow(2, ($maxPrefix - $prefix));
+ }
+}
diff --git a/mlocati/ip-lib/src/Range/Type.php b/mlocati/ip-lib/src/Range/Type.php
new file mode 100644
index 00000000..b2ba8eb7
--- /dev/null
+++ b/mlocati/ip-lib/src/Range/Type.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace IPLib\Range;
+
+/**
+ * Types of IP address classes.
+ */
+class Type
+{
+ /**
+ * Unspecified/unknown address.
+ *
+ * @var int
+ */
+ const T_UNSPECIFIED = 1;
+
+ /**
+ * Reserved/internal use only.
+ *
+ * @var int
+ */
+ const T_RESERVED = 2;
+
+ /**
+ * Refer to source hosts on "this" network.
+ *
+ * @var int
+ */
+ const T_THISNETWORK = 3;
+
+ /**
+ * Internet host loopback address.
+ *
+ * @var int
+ */
+ const T_LOOPBACK = 4;
+
+ /**
+ * Relay anycast address.
+ *
+ * @var int
+ */
+ const T_ANYCASTRELAY = 5;
+
+ /**
+ * "Limited broadcast" destination address.
+ *
+ * @var int
+ */
+ const T_LIMITEDBROADCAST = 6;
+
+ /**
+ * Multicast address assignments - Indentify a group of interfaces.
+ *
+ * @var int
+ */
+ const T_MULTICAST = 7;
+
+ /**
+ * "Link local" address, allocated for communication between hosts on a single link.
+ *
+ * @var int
+ */
+ const T_LINKLOCAL = 8;
+
+ /**
+ * Link local unicast / Linked-scoped unicast.
+ *
+ * @var int
+ */
+ const T_LINKLOCAL_UNICAST = 9;
+
+ /**
+ * Discard-Only address.
+ *
+ * @var int
+ */
+ const T_DISCARDONLY = 10;
+
+ /**
+ * Discard address.
+ *
+ * @var int
+ */
+ const T_DISCARD = 11;
+
+ /**
+ * For use in private networks.
+ *
+ * @var int
+ */
+ const T_PRIVATENETWORK = 12;
+
+ /**
+ * Public address.
+ *
+ * @var int
+ */
+ const T_PUBLIC = 13;
+
+ /**
+ * Carrier-grade NAT address.
+ *
+ * @var int
+ *
+ * @since 1.10.0
+ */
+ const T_CGNAT = 14;
+
+ /**
+ * Get the name of a type.
+ *
+ * @param int $type
+ *
+ * @return string
+ */
+ public static function getName($type)
+ {
+ switch ($type) {
+ case static::T_UNSPECIFIED:
+ return 'Unspecified/unknown address';
+ case static::T_RESERVED:
+ return 'Reserved/internal use only';
+ case static::T_THISNETWORK:
+ return 'Refer to source hosts on "this" network';
+ case static::T_LOOPBACK:
+ return 'Internet host loopback address';
+ case static::T_ANYCASTRELAY:
+ return 'Relay anycast address';
+ case static::T_LIMITEDBROADCAST:
+ return '"Limited broadcast" destination address';
+ case static::T_MULTICAST:
+ return 'Multicast address assignments - Indentify a group of interfaces';
+ case static::T_LINKLOCAL:
+ return '"Link local" address, allocated for communication between hosts on a single link';
+ case static::T_LINKLOCAL_UNICAST:
+ return 'Link local unicast / Linked-scoped unicast';
+ case static::T_DISCARDONLY:
+ return 'Discard only';
+ case static::T_DISCARD:
+ return 'Discard';
+ case static::T_PRIVATENETWORK:
+ return 'For use in private networks';
+ case static::T_PUBLIC:
+ return 'Public address';
+ case static::T_CGNAT:
+ return 'Carrier-grade NAT';
+ default:
+ return $type === null ? 'Unknown type' : sprintf('Unknown type (%s)', $type);
+ }
+ }
+}
diff --git a/mlocati/ip-lib/src/Service/BinaryMath.php b/mlocati/ip-lib/src/Service/BinaryMath.php
new file mode 100644
index 00000000..727fecf9
--- /dev/null
+++ b/mlocati/ip-lib/src/Service/BinaryMath.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace IPLib\Service;
+
+/**
+ * Helper class to work with unsigned binary integers.
+ *
+ * @internal
+ */
+class BinaryMath
+{
+ /**
+ * Trim the leading zeroes from a non-negative integer represented in binary form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function reduce($value)
+ {
+ $value = ltrim($value, '0');
+
+ return $value === '' ? '0' : $value;
+ }
+
+ /**
+ * Compare two non-negative integers represented in binary form.
+ *
+ * @param string $a
+ * @param string $b
+ *
+ * @return int 1 if $a is greater than $b, -1 if $b is greater than $b, 0 if they are the same
+ */
+ public function compare($a, $b)
+ {
+ list($a, $b) = $this->toSameLength($a, $b);
+
+ return $a < $b ? -1 : ($a > $b ? 1 : 0);
+ }
+
+ /**
+ * Add 1 to a non-negative integer represented in binary form.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public function increment($value)
+ {
+ $lastZeroIndex = strrpos($value, '0');
+ if ($lastZeroIndex === false) {
+ return '1' . str_repeat('0', strlen($value));
+ }
+
+ return ltrim(substr($value, 0, $lastZeroIndex), '0') . '1' . str_repeat('0', strlen($value) - $lastZeroIndex - 1);
+ }
+
+ /**
+ * Calculate the bitwise AND of two non-negative integers represented in binary form.
+ *
+ * @param string $operand1
+ * @param string $operand2
+ *
+ * @return string
+ */
+ public function andX($operand1, $operand2)
+ {
+ $operand1 = $this->reduce($operand1);
+ $operand2 = $this->reduce($operand2);
+ $numBits = min(strlen($operand1), strlen($operand2));
+ $operand1 = substr(str_pad($operand1, $numBits, '0', STR_PAD_LEFT), -$numBits);
+ $operand2 = substr(str_pad($operand2, $numBits, '0', STR_PAD_LEFT), -$numBits);
+ $result = '';
+ for ($index = 0; $index < $numBits; $index++) {
+ $result .= $operand1[$index] === '1' && $operand2[$index] === '1' ? '1' : '0';
+ }
+
+ return $this->reduce($result);
+ }
+
+ /**
+ * Calculate the bitwise OR of two non-negative integers represented in binary form.
+ *
+ * @param string $operand1
+ * @param string $operand2
+ *
+ * @return string
+ */
+ public function orX($operand1, $operand2)
+ {
+ list($operand1, $operand2, $numBits) = $this->toSameLength($operand1, $operand2);
+ $result = '';
+ for ($index = 0; $index < $numBits; $index++) {
+ $result .= $operand1[$index] === '1' || $operand2[$index] === '1' ? '1' : '0';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Zero-padding of two non-negative integers represented in binary form, so that they have the same length.
+ *
+ * @param string $num1
+ * @param string $num2
+ *
+ * @return string[],int[] The first array element is $num1 (padded), the first array element is $num2 (padded), the third array element is the number of bits
+ */
+ private function toSameLength($num1, $num2)
+ {
+ $num1 = $this->reduce($num1);
+ $num2 = $this->reduce($num2);
+ $numBits = max(strlen($num1), strlen($num2));
+
+ return array(
+ str_pad($num1, $numBits, '0', STR_PAD_LEFT),
+ str_pad($num2, $numBits, '0', STR_PAD_LEFT),
+ $numBits,
+ );
+ }
+}
diff --git a/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php b/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php
new file mode 100644
index 00000000..7a127e79
--- /dev/null
+++ b/mlocati/ip-lib/src/Service/RangesFromBoundaryCalculator.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace IPLib\Service;
+
+use IPLib\Address\AddressInterface;
+use IPLib\Factory;
+use IPLib\Range\Subnet;
+
+/**
+ * Helper class to calculate the subnets describing all (and only all) the addresses between two boundaries.
+ *
+ * @internal
+ */
+class RangesFromBoundaryCalculator
+{
+ /**
+ * The BinaryMath instance to be used to perform bitwise operations.
+ *
+ * @var \IPLib\Service\BinaryMath
+ */
+ private $math;
+
+ /**
+ * The number of bits used to represent addresses.
+ *
+ * @var int
+ *
+ * @example 32 for IPv4, 128 for IPv6
+ */
+ private $numBits;
+
+ /**
+ * The bit masks for every bit index.
+ *
+ * @var string[]
+ */
+ private $masks;
+
+ /**
+ * The bit unmasks for every bit index.
+ *
+ * @var string[]
+ */
+ private $unmasks;
+
+ /**
+ * Initializes the instance.
+ *
+ * @param int $numBits the number of bits used to represent addresses (32 for IPv4, 128 for IPv6)
+ */
+ public function __construct($numBits)
+ {
+ $this->math = new BinaryMath();
+ $this->setNumBits($numBits);
+ }
+
+ /**
+ * Calculate the subnets describing all (and only all) the addresses between two boundaries.
+ *
+ * @param \IPLib\Address\AddressInterface $from
+ * @param \IPLib\Address\AddressInterface $to
+ *
+ * @return \IPLib\Range\Subnet[]|null return NULL if the two addresses have an invalid number of bits (that is, different from the one passed to the constructor of this class)
+ */
+ public function getRanges(AddressInterface $from, AddressInterface $to)
+ {
+ if ($from->getNumberOfBits() !== $this->numBits || $to->getNumberOfBits() !== $this->numBits) {
+ return null;
+ }
+ if ($from->getComparableString() > $to->getComparableString()) {
+ list($from, $to) = array($to, $from);
+ }
+ $result = array();
+ $this->calculate($this->math->reduce($from->getBits()), $this->math->reduce($to->getBits()), $this->numBits, $result);
+
+ return $result;
+ }
+
+ /**
+ * Set the number of bits used to represent addresses (32 for IPv4, 128 for IPv6).
+ *
+ * @param int $numBits
+ */
+ private function setNumBits($numBits)
+ {
+ $numBits = (int) $numBits;
+ $masks = array();
+ $unmasks = array();
+ for ($bit = 0; $bit < $numBits; $bit++) {
+ $masks[$bit] = str_repeat('1', $numBits - $bit) . str_repeat('0', $bit);
+ $unmasks[$bit] = $bit === 0 ? '0' : str_repeat('1', $bit);
+ }
+ $this->numBits = $numBits;
+ $this->masks = $masks;
+ $this->unmasks = $unmasks;
+ }
+
+ /**
+ * Calculate the subnets.
+ *
+ * @param string $start the start address (represented in reduced bit form)
+ * @param string $end the end address (represented in reduced bit form)
+ * @param int $position the number of bits in the mask we are comparing at this cycle
+ * @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable
+ */
+ private function calculate($start, $end, $position, array &$result)
+ {
+ if ($start === $end) {
+ $result[] = $this->subnetFromBits($start, $this->numBits);
+
+ return;
+ }
+ for ($index = $position - 1; $index >= 0; $index--) {
+ $startMasked = $this->math->andX($start, $this->masks[$index]);
+ $endMasked = $this->math->andX($end, $this->masks[$index]);
+ if ($startMasked !== $endMasked) {
+ $position = $index;
+ break;
+ }
+ }
+ if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') {
+ $result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position);
+
+ return;
+ }
+ $middleAddress = $this->math->orX($start, $this->unmasks[$position]);
+ $this->calculate($start, $middleAddress, $position, $result);
+ $this->calculate($this->math->increment($middleAddress), $end, $position, $result);
+ }
+
+ /**
+ * Create an address instance starting from its bits.
+ *
+ * @param string $bits the bits of the address (represented in reduced bit form)
+ *
+ * @return \IPLib\Address\AddressInterface
+ */
+ private function addressFromBits($bits)
+ {
+ $bits = str_pad($bits, $this->numBits, '0', STR_PAD_LEFT);
+ $bytes = array();
+ foreach (explode("\n", trim(chunk_split($bits, 8, "\n"))) as $byteBits) {
+ $bytes[] = bindec($byteBits);
+ }
+
+ return Factory::addressFromBytes($bytes);
+ }
+
+ /**
+ * Create an range instance starting from the bits if the address and the length of the network prefix.
+ *
+ * @param string $bits the bits of the address (represented in reduced bit form)
+ * @param int $networkPrefix the length of the network prefix
+ *
+ * @return \IPLib\Range\Subnet
+ */
+ private function subnetFromBits($bits, $networkPrefix)
+ {
+ $startAddress = $this->addressFromBits($bits);
+ $numOnes = $this->numBits - $networkPrefix;
+ if ($numOnes === 0) {
+ return new Subnet($startAddress, $startAddress, $networkPrefix);
+ }
+ $endAddress = $this->addressFromBits(substr($bits, 0, -$numOnes) . str_repeat('1', $numOnes));
+
+ return new Subnet($startAddress, $endAddress, $networkPrefix);
+ }
+}
diff --git a/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php b/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php
new file mode 100644
index 00000000..3179f746
--- /dev/null
+++ b/mlocati/ip-lib/src/Service/UnsignedIntegerMath.php
@@ -0,0 +1,171 @@
+<?php
+
+namespace IPLib\Service;
+
+/**
+ * Helper class to work with unsigned integers.
+ *
+ * @internal
+ */
+class UnsignedIntegerMath
+{
+ /**
+ * Convert a string containing a decimal, octal or hexadecimal number into its bytes.
+ *
+ * @param string $value
+ * @param int $numBytes the wanted number of bytes
+ * @param bool $onlyDecimal Only parse decimal numbers
+ *
+ * @return int[]|null
+ */
+ public function getBytes($value, $numBytes, $onlyDecimal = false)
+ {
+ $m = null;
+ if ($onlyDecimal) {
+ if (preg_match('/^0*(\d+)$/', $value, $m)) {
+ return $this->getBytesFromDecimal($m[1], $numBytes);
+ }
+ } else {
+ if (preg_match('/^0[Xx]0*([0-9A-Fa-f]+)$/', $value, $m)) {
+ return $this->getBytesFromHexadecimal($m[1], $numBytes);
+ }
+ if (preg_match('/^0+([0-7]*)$/', $value, $m)) {
+ return $this->getBytesFromOctal($m[1], $numBytes);
+ }
+ if (preg_match('/^[1-9][0-9]*$/', $value)) {
+ return $this->getBytesFromDecimal($value, $numBytes);
+ }
+ }
+
+ // Not a valid number
+ return null;
+ }
+
+ /**
+ * @return int
+ */
+ protected function getMaxSignedInt()
+ {
+ return PHP_INT_MAX;
+ }
+
+ /**
+ * @param string $value never zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromBits($value, $numBytes)
+ {
+ $valueLength = strlen($value);
+ if ($valueLength > $numBytes << 3) {
+ // overflow
+ return null;
+ }
+ $remainderBits = $valueLength % 8;
+ if ($remainderBits !== 0) {
+ $value = str_pad($value, $valueLength + 8 - $remainderBits, '0', STR_PAD_LEFT);
+ }
+ $bytes = array_map('bindec', str_split($value, 8));
+
+ return array_pad($bytes, -$numBytes, 0);
+ }
+
+ /**
+ * @param string $value may be zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromOctal($value, $numBytes)
+ {
+ if ($value === '') {
+ return array_fill(0, $numBytes, 0);
+ }
+ $bits = implode(
+ '',
+ array_map(
+ function ($octalDigit) {
+ return str_pad(decbin(octdec($octalDigit)), 3, '0', STR_PAD_LEFT);
+ },
+ str_split($value, 1)
+ )
+ );
+ $bits = ltrim($bits, '0');
+
+ return $bits === '' ? array_fill(0, $numBytes, 0) : static::getBytesFromBits($bits, $numBytes);
+ }
+
+ /**
+ * @param string $value never zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromDecimal($value, $numBytes)
+ {
+ $valueLength = strlen($value);
+ $maxSignedIntLength = strlen((string) $this->getMaxSignedInt());
+ if ($valueLength < $maxSignedIntLength) {
+ return $this->getBytesFromBits(decbin((int) $value), $numBytes);
+ }
+ // Divide by two, so that we have 1 less bit
+ $carry = 0;
+ $halfValue = ltrim(
+ implode(
+ '',
+ array_map(
+ function ($digit) use (&$carry) {
+ $number = $carry + (int) $digit;
+ $carry = ($number % 2) * 10;
+
+ return (string) $number >> 1;
+ },
+ str_split($value, 1)
+ )
+ ),
+ '0'
+ );
+ $halfValueBytes = $this->getBytesFromDecimal($halfValue, $numBytes);
+ if ($halfValueBytes === null) {
+ return null;
+ }
+ $carry = $carry === 0 ? 0 : 1;
+ $result = array_fill(0, $numBytes, 0);
+ for ($index = $numBytes - 1; $index >= 0; $index--) {
+ $byte = $carry + ($halfValueBytes[$index] << 1);
+ if ($byte <= 0xFF) {
+ $carry = 0;
+ } else {
+ $carry = ($byte & ~0xFF) >> 8;
+ $byte -= 0x100;
+ }
+ $result[$index] = $byte;
+ }
+ if ($carry !== 0) {
+ // Overflow
+ return null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param string $value never zero-length, never extra leading zeroes
+ * @param int $numBytes
+ *
+ * @return int[]|null
+ */
+ private function getBytesFromHexadecimal($value, $numBytes)
+ {
+ $valueLength = strlen($value);
+ if ($valueLength > $numBytes << 1) {
+ // overflow
+ return null;
+ }
+ $value = str_pad($value, $valueLength + $valueLength % 2, '0', STR_PAD_LEFT);
+ $bytes = array_map('hexdec', str_split($value, 2));
+
+ return array_pad($bytes, -$numBytes, 0);
+ }
+}