update all libraries

This commit is contained in:
El RIDO
2022-02-18 07:36:09 +01:00
parent c8c6a67530
commit 7277d2bb43
30 changed files with 928 additions and 257 deletions

View File

@@ -21,6 +21,8 @@ interface AddressInterface
*
* @return int
*
* @since 1.14.0
*
* @example 32 for IPv4
* @example 128 for IPv6
*/
@@ -51,6 +53,8 @@ interface AddressInterface
*
* @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();
@@ -66,6 +70,8 @@ interface AddressInterface
* 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();
@@ -73,6 +79,8 @@ interface AddressInterface
* 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();
@@ -99,10 +107,28 @@ interface AddressInterface
*/
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();
@@ -110,6 +136,9 @@ interface AddressInterface
* 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();
@@ -118,6 +147,8 @@ interface AddressInterface
*
* @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
*/

View File

@@ -6,6 +6,8 @@ use IPLib\Range\RangeInterface;
/**
* Represents an IP address range with an assigned range type.
*
* @since 1.5.0
*/
class AssignedRange
{

View File

@@ -2,6 +2,7 @@
namespace IPLib\Address;
use IPLib\ParseStringFlag;
use IPLib\Range\RangeInterface;
use IPLib\Range\Subnet;
use IPLib\Range\Type as RangeType;
@@ -46,7 +47,7 @@ class IPv4 implements AddressInterface
*
* @var array|null
*/
private static $reservedRanges = null;
private static $reservedRanges;
/**
* Initializes the instance.
@@ -82,52 +83,94 @@ class IPv4 implements AddressInterface
}
/**
* Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
* @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 set to false to avoid parsing addresses with ports
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @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)
{
if (!is_string($address) || !strpos($address, '.')) {
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;
}
$rxChunk = '0?[0-9]{1,3}';
if ($supportNonDecimalIPv4) {
$rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
$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);
}
}
$rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
if ($mayIncludePort) {
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+)?';
}
$matches = null;
if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
return null;
}
$math = new \IPLib\Service\UnsignedIntegerMath();
$nums = array();
for ($i = 1; $i <= 4; $i++) {
$s = $matches[$i];
if ($supportNonDecimalIPv4) {
if (stripos($s, '0x') === 0) {
$n = hexdec(substr($s, 2));
} elseif ($s[0] === '0') {
if (!preg_match('/^[0-7]+$/', $s)) {
return null;
}
$n = octdec(substr($s, 1));
} else {
$n = (int) $s;
}
} else {
$n = (int) $s;
}
if ($n < 0 || $n > 255) {
$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[] = (string) $n;
$nums = array_merge($nums, $chunkBytes);
}
return new static(implode('.', $nums));
@@ -179,6 +222,8 @@ class IPv4 implements AddressInterface
*
* @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'
*/
@@ -203,6 +248,8 @@ class IPv4 implements AddressInterface
*
* @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'
*/
@@ -318,10 +365,10 @@ class IPv4 implements AddressInterface
$exceptions = array();
if (isset($data[1])) {
foreach ($data[1] as $exceptionRange => $exceptionType) {
$exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
$exceptions[] = new AssignedRange(Subnet::parseString($exceptionRange), $exceptionType);
}
}
$reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
$reservedRanges[] = new AssignedRange(Subnet::parseString($range), $data[0], $exceptions);
}
self::$reservedRanges = $reservedRanges;
}
@@ -359,13 +406,15 @@ class IPv4 implements AddressInterface
{
$myBytes = $this->getBytes();
return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
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()
{
@@ -400,6 +449,37 @@ class IPv4 implements AddressInterface
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}
*
@@ -407,22 +487,7 @@ class IPv4 implements AddressInterface
*/
public function getNextAddress()
{
$overflow = false;
$bytes = $this->getBytes();
for ($i = count($bytes) - 1; $i >= 0; $i--) {
if ($bytes[$i] === 255) {
if ($i === 0) {
$overflow = true;
break;
}
$bytes[$i] = 0;
} else {
$bytes[$i]++;
break;
}
}
return $overflow ? null : static::fromBytes($bytes);
return $this->getAddressAtOffset(1);
}
/**
@@ -432,22 +497,7 @@ class IPv4 implements AddressInterface
*/
public function getPreviousAddress()
{
$overflow = false;
$bytes = $this->getBytes();
for ($i = count($bytes) - 1; $i >= 0; $i--) {
if ($bytes[$i] === 0) {
if ($i === 0) {
$overflow = true;
break;
}
$bytes[$i] = 255;
} else {
$bytes[$i]--;
break;
}
}
return $overflow ? null : static::fromBytes($bytes);
return $this->getAddressAtOffset(-1);
}
/**

View File

@@ -2,6 +2,7 @@
namespace IPLib\Address;
use IPLib\ParseStringFlag;
use IPLib\Range\RangeInterface;
use IPLib\Range\Subnet;
use IPLib\Range\Type as RangeType;
@@ -55,7 +56,7 @@ class IPv6 implements AddressInterface
*
* @var array|null
*/
private static $reservedRanges = null;
private static $reservedRanges;
/**
* Initializes the instance.
@@ -92,32 +93,69 @@ class IPv6 implements AddressInterface
}
/**
* Parse a string and returns an IPv6 instance if the string is valid, or null otherwise.
* @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 the address to parse
* @param bool $mayIncludePort set to false to avoid parsing addresses with ports
* @param bool $mayIncludeZoneID set to false to avoid parsing addresses with zone IDs (see RFC 4007)
* @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) {
$matches = null;
if ($mayIncludePort && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) {
if ($flags & ParseStringFlag::MAY_INCLUDE_PORT && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) {
$address = $matches[1];
}
if ($mayIncludeZoneID) {
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::fromString($matches[1] . '0:0', false);
$address6 = static::parseString($matches[1] . '0:0');
if ($address6 !== null) {
$address4 = IPv4::fromString($matches[2], false);
$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]);
@@ -401,10 +439,10 @@ class IPv6 implements AddressInterface
$exceptions = array();
if (isset($data[1])) {
foreach ($data[1] as $exceptionRange => $exceptionType) {
$exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
$exceptions[] = new AssignedRange(Subnet::parseString($exceptionRange), $exceptionType);
}
}
$reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
$reservedRanges[] = new AssignedRange(Subnet::parseString($range), $data[0], $exceptions);
}
self::$reservedRanges = $reservedRanges;
}
@@ -471,6 +509,7 @@ class IPv6 implements AddressInterface
* @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)
{
@@ -503,6 +542,37 @@ class IPv6 implements AddressInterface
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}
*
@@ -510,22 +580,7 @@ class IPv6 implements AddressInterface
*/
public function getNextAddress()
{
$overflow = false;
$words = $this->getWords();
for ($i = count($words) - 1; $i >= 0; $i--) {
if ($words[$i] === 0xffff) {
if ($i === 0) {
$overflow = true;
break;
}
$words[$i] = 0;
} else {
$words[$i]++;
break;
}
}
return $overflow ? null : static::fromWords($words);
return $this->getAddressAtOffset(1);
}
/**
@@ -535,22 +590,7 @@ class IPv6 implements AddressInterface
*/
public function getPreviousAddress()
{
$overflow = false;
$words = $this->getWords();
for ($i = count($words) - 1; $i >= 0; $i--) {
if ($words[$i] === 0) {
if ($i === 0) {
$overflow = true;
break;
}
$words[$i] = 0xffff;
} else {
$words[$i]--;
break;
}
}
return $overflow ? null : static::fromWords($words);
return $this->getAddressAtOffset(-1);
}
/**

View File

@@ -27,6 +27,8 @@ class Type
* @param int $type
*
* @return string
*
* @since 1.1.0
*/
public static function getName($type)
{

View File

@@ -4,7 +4,7 @@ namespace IPLib;
use IPLib\Address\AddressInterface;
use IPLib\Range\Subnet;
use IPLib\Service\RangesFromBounradyCalculator;
use IPLib\Service\RangesFromBoundaryCalculator;
/**
* Factory methods to build class instances.
@@ -12,23 +12,48 @@ use IPLib\Service\RangesFromBounradyCalculator;
class Factory
{
/**
* Parse an IP address string.
* @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 $address the address to parse
* @param bool $mayIncludePort set to false to avoid parsing addresses with ports
* @param bool $mayIncludeZoneID set to false to avoid parsing IPv6 addresses with zone IDs (see RFC 4007)
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @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::fromString($address, $mayIncludePort, $supportNonDecimalIPv4);
$result = Address\IPv4::parseString($address, $flags);
}
if ($result === null) {
$result = Address\IPv6::fromString($address, $mayIncludePort, $mayIncludeZoneID);
$result = Address\IPv6::parseString($address, $flags);
}
return $result;
@@ -54,59 +79,125 @@ class Factory
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 bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @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 rangeFromString($range, $supportNonDecimalIPv4 = false)
public static function parseRangeString($range, $flags = 0)
{
$result = null;
if ($result === null) {
$result = Range\Subnet::fromString($range, $supportNonDecimalIPv4);
$result = Range\Subnet::parseString($range, $flags);
}
if ($result === null) {
$result = Range\Pattern::fromString($range, $supportNonDecimalIPv4);
$result = Range\Pattern::parseString($range, $flags);
}
if ($result === null) {
$result = Range\Single::fromString($range, $supportNonDecimalIPv4);
$result = Range\Single::parseString($range, $flags);
}
return $result;
}
/**
* Create the smallest address range that comprises two addresses.
* @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 $from
* @param string|\IPLib\Address\AddressInterface $to
* @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @param string|\IPLib\Address\AddressInterface|mixed $from
* @param string|\IPLib\Address\AddressInterface|mixed $to
* @param bool $supportNonDecimalIPv4
*
* @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
* @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)
{
list($from, $to) = self::parseBoundaries($from, $to, $supportNonDecimalIPv4);
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 bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @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 rangesFromBoundaries($from, $to, $supportNonDecimalIPv4 = false)
public static function getRangesFromBoundaries($from, $to, $flags = 0)
{
list($from, $to) = self::parseBoundaries($from, $to, $supportNonDecimalIPv4);
if (($from === false || $to === false) || ($from === null && $to === null)) {
list($from, $to) = self::parseBoundaries($from, $to, $flags);
if ($from === false || $to === false || ($from === null && $to === null)) {
return null;
}
if ($from === null || $to === null) {
@@ -118,7 +209,7 @@ class Factory
if ($to->getNumberOfBits() !== $numberOfBits) {
return null;
}
$calculator = new RangesFromBounradyCalculator($numberOfBits);
$calculator = new RangesFromBoundaryCalculator($numberOfBits);
return $calculator->getRanges($from, $to);
}
@@ -128,6 +219,8 @@ class Factory
* @param \IPLib\Address\AddressInterface $to
*
* @return \IPLib\Range\RangeInterface|null
*
* @since 1.2.0
*/
protected static function rangeFromBoundaryAddresses(AddressInterface $from = null, AddressInterface $to = null)
{
@@ -163,7 +256,7 @@ class Factory
break;
}
}
$result = static::rangeFromString($from->toString(true) . '/' . (string) $sameBits);
$result = static::parseRangeString($from->toString() . '/' . (string) $sameBits);
}
}
}
@@ -174,11 +267,11 @@ class Factory
/**
* @param string|\IPLib\Address\AddressInterface $from
* @param string|\IPLib\Address\AddressInterface $to
* @param bool $supportNonDecimalIPv4
* @param int $flags
*
* @return \IPLib\Address\AddressInterface[]|null[]|false[]
*/
private static function parseBoundaries($from, $to, $supportNonDecimalIPv4 = false)
private static function parseBoundaries($from, $to, $flags = 0)
{
$result = array();
foreach (array('from', 'to') as $param) {
@@ -188,7 +281,7 @@ class Factory
if ($value === '') {
$value = null;
} else {
$value = static::addressFromString($value, true, true, $supportNonDecimalIPv4);
$value = static::parseAddressString($value, $flags);
if ($value === null) {
$value = false;
}

View File

@@ -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;
}

View File

@@ -8,6 +8,9 @@ use IPLib\Address\IPv6;
use IPLib\Address\Type as AddressType;
use IPLib\Factory;
/**
* Base class for range classes.
*/
abstract class AbstractRange implements RangeInterface
{
/**
@@ -20,7 +23,7 @@ abstract class AbstractRange implements RangeInterface
if ($this->rangeType === null) {
$addressType = $this->getAddressType();
if ($addressType === AddressType::T_IPv6 && Subnet::get6to4()->containsRange($this)) {
$this->rangeType = Factory::rangeFromBoundaries($this->fromAddress->toIPv4(), $this->toAddress->toIPv4())->getRangeType();
$this->rangeType = Factory::getRangeFromBoundaries($this->fromAddress->toIPv4(), $this->toAddress->toIPv4())->getRangeType();
} else {
switch ($addressType) {
case AddressType::T_IPv4:
@@ -48,6 +51,33 @@ abstract class AbstractRange implements RangeInterface
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}
*

View File

@@ -6,6 +6,7 @@ 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).
@@ -40,6 +41,8 @@ class Pattern extends AbstractRange
* 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;
@@ -67,24 +70,45 @@ class Pattern extends AbstractRange
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 bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @param int $flags A combination or zero or more flags
*
* @return static|null
*
* @since 1.17.0
* @see \IPLib\ParseStringFlag
*/
public static function fromString($range, $supportNonDecimalIPv4 = false)
public static function parseString($range, $flags = 0)
{
if (!is_string($range) || strpos($range, '*') === false) {
return null;
}
if ($range === '*.*.*.*') {
return new static(IPv4::fromString('0.0.0.0'), IPv4::fromString('255.255.255.255'), 4);
return new static(IPv4::parseString('0.0.0.0'), IPv4::parseString('255.255.255.255'), 4);
}
if ($range === '*:*:*:*:*:*:*:*') {
return new static(IPv6::fromString('::'), IPv6::fromString('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'), 8);
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)) {
@@ -96,7 +120,7 @@ class Pattern extends AbstractRange
$asterisksCount += $missingDots;
}
}
$fromAddress = IPv4::fromString(str_replace('*', '0', $range), true, $supportNonDecimalIPv4);
$fromAddress = IPv4::parseString(str_replace('*', '0', $range), $flags);
if ($fromAddress === null) {
return null;
}
@@ -108,7 +132,7 @@ class Pattern extends AbstractRange
}
if (strpos($range, ':') !== false && preg_match('/^[^*]+((?::\*)+)$/', $range, $matches)) {
$asterisksCount = strlen($matches[1]) >> 1;
$fromAddress = IPv6::fromString(str_replace('*', '0', $range));
$fromAddress = IPv6::parseString(str_replace('*', '0', $range));
if ($fromAddress === null) {
return null;
}
@@ -217,15 +241,11 @@ class Pattern extends AbstractRange
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::asSubnet()
* @since 1.8.0
*/
public function asSubnet()
{
switch ($this->getAddressType()) {
case AddressType::T_IPv4:
return new Subnet($this->getStartAddress(), $this->getEndAddress(), 8 * (4 - $this->asterisksCount));
case AddressType::T_IPv6:
return new Subnet($this->getStartAddress(), $this->getEndAddress(), 16 * (8 - $this->asterisksCount));
}
return new Subnet($this->getStartAddress(), $this->getEndAddress(), $this->getNetworkPrefix());
}
/**
@@ -272,4 +292,31 @@ class Pattern extends AbstractRange
{
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);
}
}
}

View File

@@ -38,9 +38,26 @@ interface RangeInterface
* 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.
*
@@ -56,6 +73,8 @@ interface RangeInterface
* @param \IPLib\Range\RangeInterface $range
*
* @return bool
*
* @since 1.5.0
*/
public function containsRange(RangeInterface $range);
@@ -63,6 +82,8 @@ interface RangeInterface
* Get the initial address contained in this range.
*
* @return \IPLib\Address\AddressInterface
*
* @since 1.4.0
*/
public function getStartAddress();
@@ -70,6 +91,8 @@ interface RangeInterface
* Get the final address contained in this range.
*
* @return \IPLib\Address\AddressInterface
*
* @since 1.4.0
*/
public function getEndAddress();
@@ -91,6 +114,8 @@ interface RangeInterface
* 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();
@@ -98,6 +123,8 @@ interface RangeInterface
* Get the subnet/CIDR representation of this range.
*
* @return \IPLib\Range\Subnet
*
* @since 1.13.0
*/
public function asSubnet();
@@ -105,6 +132,8 @@ interface RangeInterface
* 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();
@@ -113,8 +142,19 @@ interface RangeInterface
*
* @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();
}

View File

@@ -6,6 +6,7 @@ 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).
@@ -41,17 +42,39 @@ class Single extends AbstractRange
}
/**
* Try get the range instance starting from its string representation.
* @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 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @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;
$address = Factory::addressFromString($range, true, true, $supportNonDecimalIPv4);
$flags = (int) $flags;
$address = Factory::parseAddressString($range, $flags);
if ($address !== null) {
$result = new static($address);
}
@@ -65,6 +88,8 @@ class Single extends AbstractRange
* @param \IPLib\Address\AddressInterface $address
*
* @return static
*
* @since 1.2.0
*/
public static function fromAddress(AddressInterface $address)
{
@@ -118,23 +143,6 @@ class Single extends AbstractRange
return $result;
}
/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::containsRange()
*/
public function containsRange(RangeInterface $range)
{
$result = false;
if ($range->getAddressType() === $this->getAddressType()) {
if ($range->toString(false) === $this->toString(false)) {
$result = true;
}
}
return $result;
}
/**
* {@inheritdoc}
*
@@ -223,4 +231,14 @@ class Single extends AbstractRange
{
return array($this->getStartAddress()->getReverseDNSLookupName());
}
/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getSize()
*/
public function getSize()
{
return 1;
}
}

View File

@@ -4,9 +4,9 @@ namespace IPLib\Range;
use IPLib\Address\AddressInterface;
use IPLib\Address\IPv4;
use IPLib\Address\IPv6;
use IPLib\Address\Type as AddressType;
use IPLib\Factory;
use IPLib\ParseStringFlag;
/**
* Represents an address range in subnet format (eg CIDR).
@@ -41,6 +41,8 @@ class Subnet extends AbstractRange
* The type of the range of this IP range.
*
* @var int|null
*
* @since 1.5.0
*/
protected $rangeType;
@@ -77,15 +79,36 @@ class Subnet extends AbstractRange
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 bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
* @param int $flags A combination or zero or more flags
*
* @return static|null
*
* @see \IPLib\ParseStringFlag
* @since 1.17.0
*/
public static function fromString($range, $supportNonDecimalIPv4 = false)
public static function parseString($range, $flags = 0)
{
if (!is_string($range)) {
return null;
@@ -94,7 +117,14 @@ class Subnet extends AbstractRange
if (count($parts) !== 2) {
return null;
}
$address = Factory::addressFromString($parts[0], true, true, $supportNonDecimalIPv4);
$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;
}
@@ -197,9 +227,10 @@ class Subnet extends AbstractRange
}
/**
* Get the pattern (asterisk) representation (if applicable) of this range.
* {@inheritdoc}
*
* @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
* @see \IPLib\Range\RangeInterface::asPattern()
* @since 1.8.0
*/
public function asPattern()
{
@@ -217,11 +248,13 @@ class Subnet extends AbstractRange
* Get the 6to4 address IPv6 address range.
*
* @return self
*
* @since 1.5.0
*/
public static function get6to4()
{
if (self::$sixToFour === null) {
self::$sixToFour = self::fromString('2002::/16');
self::$sixToFour = self::parseString('2002::/16');
}
return self::$sixToFour;
@@ -231,6 +264,8 @@ class Subnet extends AbstractRange
* Get subnet prefix.
*
* @return int
*
* @since 1.7.0
*/
public function getNetworkPrefix()
{
@@ -302,4 +337,18 @@ class Subnet extends AbstractRange
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));
}
}

View File

@@ -102,6 +102,8 @@ class Type
* Carrier-grade NAT address.
*
* @var int
*
* @since 1.10.0
*/
const T_CGNAT = 14;

View File

@@ -4,6 +4,8 @@ namespace IPLib\Service;
/**
* Helper class to work with unsigned binary integers.
*
* @internal
*/
class BinaryMath
{

View File

@@ -7,12 +7,14 @@ use IPLib\Factory;
use IPLib\Range\Subnet;
/**
* Helper class to calculate the subnets describing all (and only all) the addresses between two bouundaries.
* Helper class to calculate the subnets describing all (and only all) the addresses between two boundaries.
*
* @internal
*/
class RangesFromBounradyCalculator
class RangesFromBoundaryCalculator
{
/**
* The BinaryMath instance to be used to perform bitwise poerations.
* The BinaryMath instance to be used to perform bitwise operations.
*
* @var \IPLib\Service\BinaryMath
*/
@@ -53,7 +55,7 @@ class RangesFromBounradyCalculator
}
/**
* Calculate the subnets describing all (and only all) the addresses between two bouundaries.
* Calculate the subnets describing all (and only all) the addresses between two boundaries.
*
* @param \IPLib\Address\AddressInterface $from
* @param \IPLib\Address\AddressInterface $to
@@ -154,8 +156,13 @@ class RangesFromBounradyCalculator
*/
private function subnetFromBits($bits, $networkPrefix)
{
$address = $this->addressFromBits($bits);
$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($address, $address, $networkPrefix);
return new Subnet($startAddress, $endAddress, $networkPrefix);
}
}

View File

@@ -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);
}
}