RangesFromBoundaryCalculator.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  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 = new BinaryMath();
  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. private function setNumBits($numBits)
  75. {
  76. $numBits = (int) $numBits;
  77. $masks = array();
  78. $unmasks = array();
  79. for ($bit = 0; $bit < $numBits; $bit++) {
  80. $masks[$bit] = str_repeat('1', $numBits - $bit) . str_repeat('0', $bit);
  81. $unmasks[$bit] = $bit === 0 ? '0' : str_repeat('1', $bit);
  82. }
  83. $this->numBits = $numBits;
  84. $this->masks = $masks;
  85. $this->unmasks = $unmasks;
  86. }
  87. /**
  88. * Calculate the subnets.
  89. *
  90. * @param string $start the start address (represented in reduced bit form)
  91. * @param string $end the end address (represented in reduced bit form)
  92. * @param int $position the number of bits in the mask we are comparing at this cycle
  93. * @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable
  94. */
  95. private function calculate($start, $end, $position, array &$result)
  96. {
  97. if ($start === $end) {
  98. $result[] = $this->subnetFromBits($start, $this->numBits);
  99. return;
  100. }
  101. for ($index = $position - 1; $index >= 0; $index--) {
  102. $startMasked = $this->math->andX($start, $this->masks[$index]);
  103. $endMasked = $this->math->andX($end, $this->masks[$index]);
  104. if ($startMasked !== $endMasked) {
  105. $position = $index;
  106. break;
  107. }
  108. }
  109. if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') {
  110. $result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position);
  111. return;
  112. }
  113. $middleAddress = $this->math->orX($start, $this->unmasks[$position]);
  114. $this->calculate($start, $middleAddress, $position, $result);
  115. $this->calculate($this->math->increment($middleAddress), $end, $position, $result);
  116. }
  117. /**
  118. * Create an address instance starting from its bits.
  119. *
  120. * @param string $bits the bits of the address (represented in reduced bit form)
  121. *
  122. * @return \IPLib\Address\AddressInterface
  123. */
  124. private function addressFromBits($bits)
  125. {
  126. $bits = str_pad($bits, $this->numBits, '0', STR_PAD_LEFT);
  127. $bytes = array();
  128. foreach (explode("\n", trim(chunk_split($bits, 8, "\n"))) as $byteBits) {
  129. $bytes[] = bindec($byteBits);
  130. }
  131. return Factory::addressFromBytes($bytes);
  132. }
  133. /**
  134. * Create an range instance starting from the bits if the address and the length of the network prefix.
  135. *
  136. * @param string $bits the bits of the address (represented in reduced bit form)
  137. * @param int $networkPrefix the length of the network prefix
  138. *
  139. * @return \IPLib\Range\Subnet
  140. */
  141. private function subnetFromBits($bits, $networkPrefix)
  142. {
  143. $startAddress = $this->addressFromBits($bits);
  144. $numOnes = $this->numBits - $networkPrefix;
  145. if ($numOnes === 0) {
  146. return new Subnet($startAddress, $startAddress, $networkPrefix);
  147. }
  148. $endAddress = $this->addressFromBits(substr($bits, 0, -$numOnes) . str_repeat('1', $numOnes));
  149. return new Subnet($startAddress, $endAddress, $networkPrefix);
  150. }
  151. }