Обновление клиента (apps, 3rdparty, install)

This commit is contained in:
root
2026-03-16 08:42:57 +00:00
parent b8905de237
commit f390426546
3354 changed files with 505213 additions and 3 deletions
+20
View File
@@ -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.
+13
View File
@@ -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;
}
}
);
+182
View File
@@ -0,0 +1,182 @@
<?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();
/**
* Shift the bits of the address, padding with zeroes.
*
* @param int $bits If negative the bits will be shifted left, if positive the bits will be shifted right
*
* @return self
*
* @since 1.20.0
*
* @example shifting by 1 127.0.0.1 you'll have 63.128.0.0
* @example shifting by -1 127.0.0.1 you'll have 254.0.0.2
*/
public function shift($bits);
/**
* Create a new IP address by adding to this address another address.
*
* @return self|null returns NULL if $other is not compatible with this address, or if it generates an invalid address
*
* @since 1.20.0
*
* @example adding 0.0.0.10 to 127.0.0.1 generates the IP 127.0.0.11
* @example adding 255.0.0.10 to 127.0.0.1 generates NULL
*/
public function add(AddressInterface $other);
}
+140
View File
@@ -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;
}
}
+573
View File
@@ -0,0 +1,573 @@
<?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';
}
/**
* {@inheritdoc}
*
* @see \IPLib\Address\AddressInterface::shift()
*/
public function shift($bits)
{
$bits = (int) $bits;
if ($bits === 0) {
return $this;
}
$absBits = abs($bits);
if ($absBits >= 32) {
return new self('0.0.0.0');
}
$pad = str_repeat('0', $absBits);
$paddedBits = $this->getBits();
if ($bits > 0) {
$paddedBits = $pad . substr($paddedBits, 0, -$bits);
} else {
$paddedBits = substr($paddedBits, $absBits) . $pad;
}
$bytes = array_map('bindec', str_split($paddedBits, 8));
return new static(implode('.', $bytes));
}
/**
* {@inheritdoc}
*
* @see \IPLib\Address\AddressInterface::add()
*/
public function add(AddressInterface $other)
{
if (!$other instanceof self) {
return null;
}
$myBytes = $this->getBytes();
$otherBytes = $other->getBytes();
$sum = array_fill(0, 4, 0);
$carry = 0;
for ($index = 3; $index >= 0; $index--) {
$byte = $myBytes[$index] + $otherBytes[$index] + $carry;
if ($byte > 0xFF) {
$carry = $byte >> 8;
$byte &= 0xFF;
} else {
$carry = 0;
}
$sum[$index] = $byte;
}
if ($carry !== 0) {
return null;
}
return new static(implode('.', $sum));
}
}
+666
View File
@@ -0,0 +1,666 @@
<?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';
}
/**
* {@inheritdoc}
*
* @see \IPLib\Address\AddressInterface::shift()
*/
public function shift($bits)
{
$bits = (int) $bits;
if ($bits === 0) {
return $this;
}
$absBits = abs($bits);
if ($absBits >= 128) {
return new self('0000:0000:0000:0000:0000:0000:0000:0000');
}
$pad = str_repeat('0', $absBits);
$paddedBits = $this->getBits();
if ($bits > 0) {
$paddedBits = $pad . substr($paddedBits, 0, -$bits);
} else {
$paddedBits = substr($paddedBits, $absBits) . $pad;
}
$bytes = array_map('bindec', str_split($paddedBits, 16));
return static::fromWords($bytes);
}
/**
* {@inheritdoc}
*
* @see \IPLib\Address\AddressInterface::add()
*/
public function add(AddressInterface $other)
{
if (!$other instanceof self) {
return null;
}
$myWords = $this->getWords();
$otherWords = $other->getWords();
$sum = array_fill(0, 7, 0);
$carry = 0;
for ($index = 7; $index >= 0; $index--) {
$word = $myWords[$index] + $otherWords[$index] + $carry;
if ($word > 0xFFFF) {
$carry = $word >> 16;
$word &= 0xFFFF;
} else {
$carry = 0;
}
$sum[$index] = $word;
}
if ($carry !== 0) {
return null;
}
return static::fromWords($sum);
}
}
+44
View File
@@ -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);
}
}
}
+298
View File
@@ -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 $range
* @param bool $supportNonDecimalIPv4
*
* @return \IPLib\Range\RangeInterface|null
*
* @see \IPLib\Factory::parseRangeString()
* @since 1.10.0 added the $supportNonDecimalIPv4 argument
*/
public static function rangeFromString($range, $supportNonDecimalIPv4 = false)
{
return static::parseRangeString($range, $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|null $from
* @param \IPLib\Address\AddressInterface|null $to
*
* @return \IPLib\Range\RangeInterface|null
*
* @since 1.2.0
*/
protected static function rangeFromBoundaryAddresses($from = null, $to = null)
{
if (!$from instanceof AddressInterface && !$to instanceof AddressInterface) {
$result = null;
} elseif (!$to instanceof AddressInterface) {
$result = Range\Single::fromAddress($from);
} elseif (!$from instanceof AddressInterface) {
$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;
}
}
+79
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;
}
+170
View File
@@ -0,0 +1,170 @@
<?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;
use OutOfBoundsException;
/**
* 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;
}
/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::split()
*/
public function split($networkPrefix, $forceSubnet = false)
{
$networkPrefix = (int) $networkPrefix;
$myNetworkPrefix = $this->getNetworkPrefix();
if ($networkPrefix === $myNetworkPrefix) {
return array(
$forceSubnet ? $this->asSubnet() : $this,
);
}
if ($networkPrefix < $myNetworkPrefix) {
throw new OutOfBoundsException("The value of the \$networkPrefix parameter can't be smaller than the network prefix of the range ({$myNetworkPrefix})");
}
$startIp = $this->getStartAddress();
$maxPrefix = $startIp::getNumberOfBits();
if ($networkPrefix > $maxPrefix) {
throw new OutOfBoundsException("The value of the \$networkPrefix parameter can't be larger than the maximum network prefix of the range ({$maxPrefix})");
}
if ($startIp instanceof IPv4) {
$one = IPv4::fromBytes(array(0, 0, 0, 1));
} elseif ($startIp instanceof IPv6) {
$one = IPv6::fromBytes(array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1));
}
$delta = $one->shift($networkPrefix - $maxPrefix);
$result = array();
while (true) {
$range = Subnet::parseString("{$startIp}/{$networkPrefix}");
if (!$forceSubnet && $this instanceof Pattern) {
$range = $range->asPattern() ?: $range;
}
$result[] = $range;
$startIp = $startIp->add($delta);
if ($startIp === null || !$this->contains($startIp)) {
break;
}
}
return $result;
}
}
+324
View File
@@ -0,0 +1,324 @@
<?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));
}
/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getNetworkPrefix()
*/
public function getNetworkPrefix()
{
switch ($this->getAddressType()) {
case AddressType::T_IPv4:
return 8 * (4 - $this->asterisksCount);
case AddressType::T_IPv6:
return 16 * (8 - $this->asterisksCount);
}
}
}
+186
View File
@@ -0,0 +1,186 @@
<?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();
/**
* Get the "network prefix", that is how many bits of the address are dedicated to the network portion.
*
* @return int
*
* @since 1.19.0
*
* @example for 10.0.0.0/24 it's 24
* @example for 10.0.0.* it's 24
*/
public function getNetworkPrefix();
/**
* Split the range into smaller ranges.
*
* @param int $networkPrefix
* @param bool $forceSubnet set to true to always have ranges in "subnet format" (ie 1.2.3.4/5), to false to try to keep the original format if possible (that is, pattern to pattern, single to single)
*
* @throws \OutOfBoundsException if $networkPrefix is not valid
*
* @return \IPLib\Range\RangeInterface[]
*
* @since 1.19.0
*/
public function split($networkPrefix, $forceSubnet = false);
}
+254
View File
@@ -0,0 +1,254 @@
<?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()
{
return new Subnet($this->address, $this->address, $this->getNetworkPrefix());
}
/**
* {@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;
}
/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getNetworkPrefix()
*/
public function getNetworkPrefix()
{
switch ($this->getAddressType()) {
case AddressType::T_IPv4:
return 32;
case AddressType::T_IPv6:
return 128;
}
}
}
+353
View File
@@ -0,0 +1,353 @@
<?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;
}
/**
* {@inheritdoc}
*
* @see \IPLib\Range\RangeInterface::getNetworkPrefix()
* @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));
}
}
+152
View File
@@ -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);
}
}
}
+120
View File
@@ -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,
);
}
}
@@ -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);
}
}
@@ -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);
}
}