RangesFromBoundaryCalculator.php 5.5 KB

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