Page MenuHomeDevCentral

No OneTemporary

diff --git a/src/Network/IPRange.php b/src/Network/IPRange.php
new file mode 100644
index 0000000..58f2c99
--- /dev/null
+++ b/src/Network/IPRange.php
@@ -0,0 +1,67 @@
+<?php
+declare(strict_types=1);
+
+namespace Keruald\OmniTools\Network;
+
+use Countable;
+use InvalidArgumentException;
+
+abstract class IPRange implements Countable {
+
+ ///
+ /// Constructors
+ ///
+
+ public static function from (string $format) : self {
+ $data = explode("/", $format, 2);
+
+ if (IP::isIPv4($data[0])) {
+ return new IPv4Range($data[0], (int)$data[1]);
+ }
+
+ if (IP::isIPv6($data[0])) {
+ return new IPv6Range($data[0], (int)$data[1]);
+ }
+
+ throw new InvalidArgumentException();
+ }
+
+ ///
+ /// Getters and setters
+ ///
+
+ public abstract function getBase () : string;
+ public abstract function setBase (string $base) : void;
+
+ public abstract function getNetworkBits () : int;
+ public abstract function setNetworkBits (int $networkBits) : void;
+
+ ///
+ /// Helper methods
+ ///
+
+ public abstract function contains (string $ip) : bool;
+ public abstract function getFirst () : string;
+ public abstract function getLast () : string;
+
+ ///
+ /// Countable methods
+ ///
+
+ public abstract function count () : int;
+
+ ///
+ /// Data sources
+ ///
+
+ /**
+ * @return IPRange[]
+ */
+ public static function getLoopbackRanges () : array {
+ return [
+ "IPv4" => self::from("127.0.0.0/8"),
+ "IPv6" => self::from("::1/128"),
+ ];
+ }
+
+}
diff --git a/src/Network/IPv4Range.php b/src/Network/IPv4Range.php
new file mode 100644
index 0000000..0a8d6f0
--- /dev/null
+++ b/src/Network/IPv4Range.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Keruald\OmniTools\Network;
+
+use Countable;
+use InvalidArgumentException;
+
+class IPv4Range extends IPRange {
+
+ /**
+ * @var string
+ */
+ private $base;
+
+ /**
+ * @var int
+ */
+ private $networkBits;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (string $base, int $networkBits) {
+ $this->setBase($base);
+ $this->setNetworkBits($networkBits);
+ }
+
+ ///
+ /// Getters and setters
+ ///
+
+ /**
+ * @return string
+ */
+ public function getBase () : string {
+ return $this->base;
+ }
+
+ /**
+ * @param string $base
+ */
+ public function setBase (string $base) : void {
+ if (!IP::isIPv4($base)) {
+ throw new InvalidArgumentException;
+ }
+
+ $this->base = $base;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkBits () : int {
+ return $this->networkBits;
+ }
+
+ /**
+ * @param int $networkBits
+ */
+ public function setNetworkBits (int $networkBits) : void {
+ if ($networkBits < 0 || $networkBits > 32) {
+ throw new InvalidArgumentException;
+ }
+
+ $this->networkBits = $networkBits;
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ public function getFirst () : string {
+ return $this->base;
+ }
+
+ public function getLast () : string {
+ return long2ip(ip2long($this->base) + 2 ** $this->count() - 1);
+ }
+
+ public function contains (string $ip) : bool {
+ if (!IP::isIP($ip)) {
+ throw new InvalidArgumentException;
+ }
+
+ if (!IP::isIPv4($ip)) {
+ return false;
+ }
+
+ $ipAsLong = ip2long($ip);
+ $baseAsLong = ip2long($this->base);
+
+ return $ipAsLong >= $baseAsLong
+ && $ipAsLong <= $baseAsLong + $this->count() - 1;
+
+ return false;
+ }
+
+ ///
+ /// Countable interface
+ ///
+
+ public function count () : int {
+ return 32 - $this->networkBits;
+ }
+
+}
diff --git a/src/Network/IPv6.php b/src/Network/IPv6.php
new file mode 100644
index 0000000..faa475c
--- /dev/null
+++ b/src/Network/IPv6.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Keruald\OmniTools\Network;
+
+use InvalidArgumentException;
+
+class IPv6 extends IP {
+
+ /**
+ * @var string
+ */
+ private $ip;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (string $ip) {
+ $this->ip = $ip;
+ }
+
+ public static function from (string $ip) : self {
+ $ipv6 = new self($ip);
+
+ if (!$ipv6->isValid()) {
+ throw new InvalidArgumentException;
+ }
+
+ $ipv6->normalize();
+
+ return $ipv6;
+ }
+
+ public static function fromBinaryBits (array $bits) : self {
+ $fullBits = $bits + array_fill(0, 128, 0);
+ $hextets = [];
+
+ for ($i = 0 ; $i < 8 ; $i++) {
+ // Read 16 bits
+ $slice = implode("", array_slice($fullBits, $i * 16, 16));
+ $hextets[] = base_convert($slice, 2, 16);
+ }
+
+ return self::from(implode(":", $hextets));
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ public function isValid () : bool {
+ return IP::isIPv6($this->ip);
+ }
+
+ public function increment (int $increment = 1) : self {
+ if ($increment === 0) {
+ return $this;
+ }
+
+ if ($increment < 0) {
+ throw new InvalidArgumentException("This method doesn't support decrementation.");
+ }
+
+ $ipAsNumericBinary = inet_pton($this->ip);
+
+ // See https://gist.github.com/little-apps/88bbd23576008a84e0b6
+ $i = strlen($ipAsNumericBinary) - 1;
+ $remainder = $increment;
+
+ while ($remainder > 0 && $i >= 0) {
+ $sum = ord($ipAsNumericBinary[$i]) + $remainder;
+ $remainder = $sum / 256;
+ $ipAsNumericBinary[$i] = chr($sum % 256);
+
+ --$i;
+ }
+
+ $this->ip = inet_ntop($ipAsNumericBinary);
+ return $this;
+ }
+
+ public function normalize () : self {
+ $this->ip = inet_ntop(inet_pton($this->ip));
+ return $this;
+ }
+
+ public function isNormalized() : bool {
+ return $this->ip === inet_ntop(inet_pton($this->ip));
+ }
+
+ ///
+ /// Magic methods
+ ///
+
+ public function __toString () : string {
+ return $this->ip;
+ }
+}
diff --git a/src/Network/IPv6Range.php b/src/Network/IPv6Range.php
new file mode 100644
index 0000000..ea4a79e
--- /dev/null
+++ b/src/Network/IPv6Range.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace Keruald\OmniTools\Network;
+
+use Countable;
+use InvalidArgumentException;
+
+class IPv6Range extends IPRange {
+
+ /**
+ * @var string
+ */
+ private $base;
+
+ /**
+ * @var int
+ */
+ private $networkBits;
+
+ ///
+ /// Constructors
+ ///
+
+ public function __construct (string $base, int $networkBits) {
+ $this->setBase($base);
+ $this->setNetworkBits($networkBits);
+ }
+
+ ///
+ /// Getters and setters
+ ///
+
+ /**
+ * @return string
+ */
+ public function getBase () : string {
+ return $this->base;
+ }
+
+ /**
+ * @param string $base
+ */
+ public function setBase (string $base) : void {
+ if (!IP::isIPv6($base)) {
+ throw new InvalidArgumentException;
+ }
+
+ $this->base = $base;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNetworkBits () : int {
+ return $this->networkBits;
+ }
+
+ /**
+ * @param int $networkBits
+ */
+ public function setNetworkBits (int $networkBits) : void {
+ if ($networkBits < 0 || $networkBits > 128) {
+ throw new InvalidArgumentException;
+ }
+
+ $this->networkBits = $networkBits;
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ public function getFirst () : string {
+ return $this->base;
+ }
+
+ public function getLast () : string {
+ if ($this->count() === 0) {
+ return $this->base;
+ }
+
+ $base = inet_pton($this->getFirst());
+ $mask = inet_pton($this->getInversedMask());
+ return inet_ntop($base | $mask);
+ }
+
+ private function getInversedMask () : string {
+ $bits = array_fill(0, $this->networkBits, 0) + array_fill(0, 128, 1);
+
+ return (string)IPv6::fromBinaryBits($bits);
+ }
+
+ public function contains (string $ip) : bool {
+ if (!IP::isIP($ip)) {
+ throw new InvalidArgumentException;
+ }
+
+ if (IP::isIPv4($ip)) {
+ $ip = "::ffff:" . $ip; // IPv4-mapped IPv6 address
+ }
+
+ $baseAsNumericBinary = inet_pton($this->getFirst());
+ $lastAsNumericBinary = inet_pton($this->getLast());
+ $ipAsNumericBinary = inet_pton($ip);
+
+ return strlen($ipAsNumericBinary) == strlen($baseAsNumericBinary)
+ && $ipAsNumericBinary >= $baseAsNumericBinary
+ && $ipAsNumericBinary <= $lastAsNumericBinary;
+ }
+
+ ///
+ /// Countable interface
+ ///
+
+ public function count () : int {
+ return 128 - $this->networkBits;
+ }
+
+}
diff --git a/tests/Network/IPv4RangeTest.php b/tests/Network/IPv4RangeTest.php
new file mode 100644
index 0000000..ee36916
--- /dev/null
+++ b/tests/Network/IPv4RangeTest.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\Network;
+
+use Keruald\OmniTools\Network\IPRange;
+use PHPUnit\Framework\TestCase;
+
+class IPv4RangeTest extends TestCase {
+
+ /**
+ * @var IPRange
+ */
+ protected $range;
+
+ ///
+ /// Fixtures
+ ///
+
+ protected function setUp () : void {
+ $this->range = IPRange::from("216.66.0.0/18");
+ }
+
+ ///
+ /// Tests
+ ///
+
+ public function testGetBase () : void {
+ $this->assertEquals("216.66.0.0", $this->range->getBase());
+ }
+
+ public function testGetNetworkBits () : void {
+ $this->assertEquals(18, $this->range->getNetworkBits());
+ }
+
+ public function testCount () : void {
+ $this->assertEquals(14, $this->range->count()); // 14 + 18 = 32 bits
+ }
+
+ public function testGetFirst () : void {
+ $this->assertEquals("216.66.0.0", $this->range->getFirst());
+ }
+
+ public function testGetLast () : void {
+ $this->assertEquals("216.66.63.255", $this->range->getLast());
+ }
+
+}
diff --git a/tests/Network/IPv6RangeTest.php b/tests/Network/IPv6RangeTest.php
new file mode 100644
index 0000000..86655c8
--- /dev/null
+++ b/tests/Network/IPv6RangeTest.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\Network;
+
+use Keruald\OmniTools\Network\IPRange;
+use PHPUnit\Framework\TestCase;
+
+class IPv6RangeTest extends TestCase {
+
+ /**
+ * @var IPRange
+ */
+ protected $range;
+
+ ///
+ /// Fixtures
+ ///
+
+ protected function setUp () : void {
+ $this->range = IPRange::from("2001:400::/23");
+ }
+
+ ///
+ /// Tests
+ ///
+
+ public function testGetBase () : void {
+ $this->assertEquals("2001:400::", $this->range->getBase());
+ }
+
+ public function testGetNetworkBits () : void {
+ $this->assertEquals(23, $this->range->getNetworkBits());
+ }
+
+ public function testCount () : void {
+ $this->assertEquals(105, $this->range->count()); // 23 + 105 = 128 bits
+ }
+
+ public function testGetFirst () : void {
+ $this->assertEquals("2001:400::", $this->range->getFirst());
+ }
+
+ public function testGetLast () : void {
+ $this->assertEquals("2001:5ff:ffff:ffff:ffff:ffff:ffff:ffff", $this->range->getLast());
+ }
+
+ public function testContains () : void {
+ $this->assertTrue($this->range->contains("2001:431::af"));
+ }
+
+ public function testContainsWorksWithIPv4MappedIPv6Address () : void {
+ $this->assertTrue(IPRange::from("::ffff:0.0.0.0/96")->contains("1.2.3.4"));
+ }
+
+}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 15:52 (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2259717
Default Alt Text
(11 KB)

Event Timeline