independent-analytics
/
vendor
/
mlocati
/
ip-lib
/
src
/
Service
/
RangesFromBoundaryCalculator.php
BinaryMath.php
1 month ago
NumberInChunks.php
1 month ago
RangesFromBoundaryCalculator.php
1 month ago
UnsignedIntegerMath.php
1 month ago
RangesFromBoundaryCalculator.php
161 lines
| 1 | <?php |
| 2 | |
| 3 | namespace IAWPSCOPED\IPLib\Service; |
| 4 | |
| 5 | use IAWPSCOPED\IPLib\Address\AddressInterface; |
| 6 | use IAWPSCOPED\IPLib\Factory; |
| 7 | use IAWPSCOPED\IPLib\Range\Subnet; |
| 8 | /** |
| 9 | * Helper class to calculate the subnets describing all (and only all) the addresses between two boundaries. |
| 10 | * |
| 11 | * @internal |
| 12 | */ |
| 13 | class RangesFromBoundaryCalculator |
| 14 | { |
| 15 | /** |
| 16 | * The BinaryMath instance to be used to perform bitwise operations. |
| 17 | * |
| 18 | * @var \IPLib\Service\BinaryMath |
| 19 | */ |
| 20 | private $math; |
| 21 | /** |
| 22 | * The number of bits used to represent addresses. |
| 23 | * |
| 24 | * @var int |
| 25 | * |
| 26 | * @example 32 for IPv4, 128 for IPv6 |
| 27 | */ |
| 28 | private $numBits; |
| 29 | /** |
| 30 | * The bit masks for every bit index. |
| 31 | * |
| 32 | * @var string[] |
| 33 | */ |
| 34 | private $masks; |
| 35 | /** |
| 36 | * The bit unmasks for every bit index. |
| 37 | * |
| 38 | * @var string[] |
| 39 | */ |
| 40 | private $unmasks; |
| 41 | /** |
| 42 | * Initializes the instance. |
| 43 | * |
| 44 | * @param int $numBits the number of bits used to represent addresses (32 for IPv4, 128 for IPv6) |
| 45 | */ |
| 46 | public function __construct($numBits) |
| 47 | { |
| 48 | $this->math = BinaryMath::getInstance(); |
| 49 | $this->setNumBits($numBits); |
| 50 | } |
| 51 | /** |
| 52 | * Calculate the subnets describing all (and only all) the addresses between two boundaries. |
| 53 | * |
| 54 | * @param \IPLib\Address\AddressInterface $from |
| 55 | * @param \IPLib\Address\AddressInterface $to |
| 56 | * |
| 57 | * @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) |
| 58 | */ |
| 59 | public function getRanges(AddressInterface $from, AddressInterface $to) |
| 60 | { |
| 61 | if ($from->getNumberOfBits() !== $this->numBits || $to->getNumberOfBits() !== $this->numBits) { |
| 62 | return null; |
| 63 | } |
| 64 | if ($from->getComparableString() > $to->getComparableString()) { |
| 65 | list($from, $to) = array($to, $from); |
| 66 | } |
| 67 | $result = array(); |
| 68 | $this->calculate($this->math->reduce($from->getBits()), $this->math->reduce($to->getBits()), $this->numBits, $result); |
| 69 | return $result; |
| 70 | } |
| 71 | /** |
| 72 | * Set the number of bits used to represent addresses (32 for IPv4, 128 for IPv6). |
| 73 | * |
| 74 | * @param int $numBits |
| 75 | * |
| 76 | * @return void |
| 77 | */ |
| 78 | private function setNumBits($numBits) |
| 79 | { |
| 80 | $numBits = (int) $numBits; |
| 81 | $masks = array(); |
| 82 | $unmasks = array(); |
| 83 | for ($bit = 0; $bit < $numBits; $bit++) { |
| 84 | $masks[$bit] = \str_repeat('1', $numBits - $bit) . \str_repeat('0', $bit); |
| 85 | $unmasks[$bit] = $bit === 0 ? '0' : \str_repeat('1', $bit); |
| 86 | } |
| 87 | $this->numBits = $numBits; |
| 88 | $this->masks = $masks; |
| 89 | $this->unmasks = $unmasks; |
| 90 | } |
| 91 | /** |
| 92 | * Calculate the subnets. |
| 93 | * |
| 94 | * @param string $start the start address (represented in reduced bit form) |
| 95 | * @param string $end the end address (represented in reduced bit form) |
| 96 | * @param int $position the number of bits in the mask we are comparing at this cycle |
| 97 | * @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable |
| 98 | * |
| 99 | * @return void |
| 100 | */ |
| 101 | private function calculate($start, $end, $position, array &$result) |
| 102 | { |
| 103 | if ($start === $end) { |
| 104 | $result[] = $this->subnetFromBits($start, $this->numBits); |
| 105 | return; |
| 106 | } |
| 107 | $startMasked = ''; |
| 108 | for ($index = $position - 1; $index >= 0; $index--) { |
| 109 | $startMasked = $this->math->andX($start, $this->masks[$index]); |
| 110 | $endMasked = $this->math->andX($end, $this->masks[$index]); |
| 111 | if ($startMasked !== $endMasked) { |
| 112 | $position = $index; |
| 113 | break; |
| 114 | } |
| 115 | } |
| 116 | if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') { |
| 117 | $result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position); |
| 118 | return; |
| 119 | } |
| 120 | $middleAddress = $this->math->orX($start, $this->unmasks[$position]); |
| 121 | $this->calculate($start, $middleAddress, $position, $result); |
| 122 | $this->calculate($this->math->increment($middleAddress), $end, $position, $result); |
| 123 | } |
| 124 | /** |
| 125 | * Create an address instance starting from its bits. |
| 126 | * |
| 127 | * @param string $bits the bits of the address (represented in reduced bit form) |
| 128 | * |
| 129 | * @return \IPLib\Address\AddressInterface |
| 130 | */ |
| 131 | private function addressFromBits($bits) |
| 132 | { |
| 133 | $bits = \str_pad($bits, $this->numBits, '0', \STR_PAD_LEFT); |
| 134 | $bytes = array(); |
| 135 | foreach (\explode("\n", \trim(\chunk_split($bits, 8, "\n"))) as $byteBits) { |
| 136 | $bytes[] = (int) \bindec($byteBits); |
| 137 | } |
| 138 | $result = Factory::addressFromBytes($bytes); |
| 139 | /** @var AddressInterface $result */ |
| 140 | return $result; |
| 141 | } |
| 142 | /** |
| 143 | * Create an range instance starting from the bits if the address and the length of the network prefix. |
| 144 | * |
| 145 | * @param string $bits the bits of the address (represented in reduced bit form) |
| 146 | * @param int $networkPrefix the length of the network prefix |
| 147 | * |
| 148 | * @return \IPLib\Range\Subnet |
| 149 | */ |
| 150 | private function subnetFromBits($bits, $networkPrefix) |
| 151 | { |
| 152 | $startAddress = $this->addressFromBits($bits); |
| 153 | $numOnes = $this->numBits - $networkPrefix; |
| 154 | if ($numOnes === 0) { |
| 155 | return new Subnet($startAddress, $startAddress, $networkPrefix); |
| 156 | } |
| 157 | $endAddress = $this->addressFromBits(\substr($bits, 0, -$numOnes) . \str_repeat('1', $numOnes)); |
| 158 | return new Subnet($startAddress, $endAddress, $networkPrefix); |
| 159 | } |
| 160 | } |
| 161 |