Issues (37)

src/Service/RangesFromBoundaryCalculator.php (1 issue)

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