Issues (37)

src/Range/Subnet.php (5 issues)

1
<?php
2
3
namespace IPLib\Range;
4
5
use IPLib\Address\AddressInterface;
6
use IPLib\Address\IPv4;
7
use IPLib\Address\Type as AddressType;
8
use IPLib\Factory;
9
use IPLib\ParseStringFlag;
10
11
/**
12
 * Represents an address range in subnet format (eg CIDR).
13
 *
14
 * @example 127.0.0.1/32
15
 * @example ::/8
16
 */
17
class Subnet extends AbstractRange
18
{
19
    /**
20
     * Starting address of the range.
21
     *
22
     * @var \IPLib\Address\AddressInterface
23
     */
24
    protected $fromAddress;
25
26
    /**
27
     * Final address of the range.
28
     *
29
     * @var \IPLib\Address\AddressInterface
30
     */
31
    protected $toAddress;
32
33
    /**
34
     * Number of the same bits of the range.
35
     *
36
     * @var int
37
     */
38
    protected $networkPrefix;
39
40
    /**
41
     * The type of the range of this IP range.
42
     *
43
     * @var int|null
44
     *
45
     * @since 1.5.0
46
     */
47
    protected $rangeType;
48
49
    /**
50
     * The 6to4 address IPv6 address range.
51
     *
52
     * @var self|null
53
     */
54
    private static $sixToFour;
55
56
    /**
57
     * Initializes the instance.
58
     *
59
     * @param \IPLib\Address\AddressInterface $fromAddress
60
     * @param \IPLib\Address\AddressInterface $toAddress
61
     * @param int $networkPrefix
62
     *
63
     * @internal
64
     */
65 658
    public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
66
    {
67 658
        $this->fromAddress = $fromAddress;
68 658
        $this->toAddress = $toAddress;
69 658
        $this->networkPrefix = $networkPrefix;
70 658
    }
71
72
    /**
73
     * {@inheritdoc}
74
     *
75
     * @see \IPLib\Range\RangeInterface::__toString()
76
     */
77 167
    public function __toString()
78
    {
79 167
        return $this->toString();
80
    }
81
82
    /**
83
     * @deprecated since 1.17.0: use the parseString() method instead.
84
     * For upgrading:
85
     * - if $supportNonDecimalIPv4 is true, use the ParseStringFlag::IPV4_MAYBE_NON_DECIMAL flag
86
     *
87
     * @param string|mixed $range
88
     * @param bool $supportNonDecimalIPv4
89
     *
90
     * @return static|null
91
     *
92
     * @see \IPLib\Range\Subnet::parseString()
93
     * @since 1.10.0 added the $supportNonDecimalIPv4 argument
94
     */
95 11
    public static function fromString($range, $supportNonDecimalIPv4 = false)
96
    {
97 11
        return static::parseString($range, ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID | ($supportNonDecimalIPv4 ? ParseStringFlag::IPV4_MAYBE_NON_DECIMAL : 0));
98
    }
99
100
    /**
101
     * Try get the range instance starting from its string representation.
102
     *
103
     * @param string|mixed $range
104
     * @param int $flags A combination or zero or more flags
105
     *
106
     * @return static|null
107
     *
108
     * @see \IPLib\ParseStringFlag
109
     * @since 1.17.0
110
     */
111 784
    public static function parseString($range, $flags = 0)
112
    {
113 784
        if (!is_string($range)) {
114 3
            return null;
115
        }
116 781
        $parts = explode('/', $range);
117 781
        if (count($parts) !== 2) {
118 149
            return null;
119
        }
120 639
        $flags = (int) $flags;
121 639
        if (strpos($parts[0], ':') === false && $flags & ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT) {
122 9
            $missingDots = 3 - substr_count($parts[0], '.');
123 9
            if ($missingDots > 0) {
124 7
                $parts[0] .= str_repeat('.0', $missingDots);
125
            }
126
        }
127 639
        $address = Factory::parseAddressString($parts[0], $flags);
128 639
        if ($address === null) {
129 10
            return null;
130
        }
131 636
        if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
132 2
            return null;
133
        }
134 634
        $networkPrefix = (int) $parts[1];
135 634
        $addressBytes = $address->getBytes();
136 634
        $totalBytes = count($addressBytes);
137 634
        $numDifferentBits = $totalBytes * 8 - $networkPrefix;
138 634
        if ($numDifferentBits < 0) {
139 2
            return null;
140
        }
141 632
        $numSameBytes = $networkPrefix >> 3;
142 632
        $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
143 632
        $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
144 632
        $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
145 632
        $startSameBits = $networkPrefix % 8;
146 632
        if ($startSameBits !== 0) {
147 425
            $varyingByte = $addressBytes[$numSameBytes];
148 425
            $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
149 425
            $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
150
        }
151
152 632
        return new static(
153 632
            Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
154 632
            Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
155 632
            $networkPrefix
156
        );
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     *
162
     * @see \IPLib\Range\RangeInterface::toString()
163
     */
164 402
    public function toString($long = false)
165
    {
166 402
        return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     *
172
     * @see \IPLib\Range\RangeInterface::getAddressType()
173
     */
174 521
    public function getAddressType()
175
    {
176 521
        return $this->fromAddress->getAddressType();
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     *
182
     * @see \IPLib\Range\RangeInterface::getStartAddress()
183
     */
184 151
    public function getStartAddress()
185
    {
186 151
        return $this->fromAddress;
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     *
192
     * @see \IPLib\Range\RangeInterface::getEndAddress()
193
     */
194 42
    public function getEndAddress()
195
    {
196 42
        return $this->toAddress;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     *
202
     * @see \IPLib\Range\RangeInterface::getComparableStartString()
203
     */
204 493
    public function getComparableStartString()
205
    {
206 493
        return $this->fromAddress->getComparableString();
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     *
212
     * @see \IPLib\Range\RangeInterface::getComparableEndString()
213
     */
214 492
    public function getComparableEndString()
215
    {
216 492
        return $this->toAddress->getComparableString();
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     *
222
     * @see \IPLib\Range\RangeInterface::asSubnet()
223
     */
224 56
    public function asSubnet()
225
    {
226 56
        return $this;
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     *
232
     * @see \IPLib\Range\RangeInterface::asPattern()
233
     * @since 1.8.0
234
     */
235 62
    public function asPattern()
236
    {
237 62
        $address = $this->getStartAddress();
238 62
        $networkPrefix = $this->getNetworkPrefix();
239 62
        switch ($address->getAddressType()) {
240
            case AddressType::T_IPv4:
241 23
                return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
242
            case AddressType::T_IPv6:
243 39
                return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
244
        }
245
    }
246
247
    /**
248
     * Get the 6to4 address IPv6 address range.
249
     *
250
     * @return self
251
     *
252
     * @since 1.5.0
253
     */
254 72
    public static function get6to4()
255
    {
256 72
        if (self::$sixToFour === null) {
257 1
            self::$sixToFour = self::parseString('2002::/16');
258
        }
259
260 72
        return self::$sixToFour;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     *
266
     * @see \IPLib\Range\RangeInterface::getNetworkPrefix()
267
     * @since 1.7.0
268
     */
269 289
    public function getNetworkPrefix()
270
    {
271 289
        return $this->networkPrefix;
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     *
277
     * @see \IPLib\Range\RangeInterface::getSubnetMask()
278
     */
279 19
    public function getSubnetMask()
280
    {
281 19
        if ($this->getAddressType() !== AddressType::T_IPv4) {
282 1
            return null;
283
        }
284 18
        $bytes = array();
285 18
        $prefix = $this->getNetworkPrefix();
286 18
        while ($prefix >= 8) {
287 10
            $bytes[] = 255;
288 10
            $prefix -= 8;
289
        }
290 18
        if ($prefix !== 0) {
291 13
            $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
292
        }
293 18
        $bytes = array_pad($bytes, 4, 0);
294
295 18
        return IPv4::fromBytes($bytes);
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     *
301
     * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
302
     */
303 24
    public function getReverseDNSLookupName()
304
    {
305 24
        switch ($this->getAddressType()) {
306
            case AddressType::T_IPv4:
307 9
                $unitSize = 8; // bytes
308 9
                $maxUnits = 4;
309 9
                $isHex = false;
310 9
                $rxUnit = '\d+';
311 9
                break;
312
            case AddressType::T_IPv6:
313 15
                $unitSize = 4; // nibbles
314 15
                $maxUnits = 32;
315 15
                $isHex = true;
316 15
                $rxUnit = '[0-9A-Fa-f]';
317 15
                break;
318
        }
319 24
        $totBits = $unitSize * $maxUnits;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $unitSize does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $maxUnits does not seem to be defined for all execution paths leading up to this point.
Loading history...
320 24
        $prefixUnits = (int) ($this->networkPrefix / $unitSize);
321 24
        $extraBits = ($totBits - $this->networkPrefix) % $unitSize;
322 24
        if ($extraBits !== 0) {
323 5
            $prefixUnits += 1;
324
        }
325 24
        $numVariants = 1 << $extraBits;
326 24
        $result = array();
327 24
        $unitsToRemove = $maxUnits - $prefixUnits;
328 24
        $initialPointer = preg_replace("/^(({$rxUnit})\.){{$unitsToRemove}}/", '', $this->getStartAddress()->getReverseDNSLookupName());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $rxUnit does not seem to be defined for all execution paths leading up to this point.
Loading history...
329 24
        $chunks = explode('.', $initialPointer, 2);
330 24
        for ($index = 0; $index < $numVariants; $index++) {
331 24
            if ($index !== 0) {
332 5
                $chunks[0] = $isHex ? dechex(1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
0 ignored issues
show
1 + hexdec($chunks[0]) of type double is incompatible with the type integer expected by parameter $num of dechex(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
                $chunks[0] = $isHex ? dechex(/** @scrutinizer ignore-type */ 1 + hexdec($chunks[0])) : (string) (1 + (int) $chunks[0]);
Loading history...
Comprehensibility Best Practice introduced by
The variable $isHex does not seem to be defined for all execution paths leading up to this point.
Loading history...
333
            }
334 24
            $result[] = implode('.', $chunks);
335
        }
336
337 24
        return $result;
338
    }
339
340
    /**
341
     * {@inheritdoc}
342
     *
343
     * @see \IPLib\Range\RangeInterface::getSize()
344
     */
345 4
    public function getSize()
346
    {
347 4
        $fromAddress = $this->fromAddress;
348 4
        $maxPrefix = $fromAddress::getNumberOfBits();
349 4
        $prefix = $this->getNetworkPrefix();
350
351 4
        return pow(2, ($maxPrefix - $prefix));
352
    }
353
}
354