RangesFromBounradyCalculator.php 5.0 KB

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