Completed
Pull Request — master (#53)
by Michele
07:54
created

Subnet::getReverseDNSLookupName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 0
cp 0
rs 9.9666
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 12
1
<?php
2
3
namespace IPLib\Range;
4
5
use IPLib\Address\AddressInterface;
6
use IPLib\Address\IPv4;
7
use IPLib\Address\IPv6;
8
use IPLib\Address\Type as AddressType;
9
use IPLib\Factory;
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
    protected $rangeType;
46
47
    /**
48
     * The 6to4 address IPv6 address range.
49
     *
50
     * @var self|null
51
     */
52
    private static $sixToFour;
53
54
    /**
55
     * Initializes the instance.
56
     *
57
     * @param \IPLib\Address\AddressInterface $fromAddress
58
     * @param \IPLib\Address\AddressInterface $toAddress
59
     * @param int $networkPrefix
60
     *
61
     * @internal
62 512
     */
63
    public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
64 512
    {
65 512
        $this->fromAddress = $fromAddress;
66 512
        $this->toAddress = $toAddress;
67 512
        $this->networkPrefix = $networkPrefix;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     *
73
     * @see \IPLib\Range\RangeInterface::__toString()
74 44
     */
75
    public function __toString()
76 44
    {
77
        return $this->toString();
78
    }
79
80
    /**
81
     * Try get the range instance starting from its string representation.
82
     *
83
     * @param string|mixed $range
84
     * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
85
     *
86
     * @return static|null
87 625
     */
88
    public static function fromString($range, $supportNonDecimalIPv4 = false)
89 625
    {
90 3
        if (!is_string($range)) {
91
            return null;
92 622
        }
93 622
        $parts = explode('/', $range);
94 103
        if (count($parts) !== 2) {
95
            return null;
96 519
        }
97 519
        $address = Factory::addressFromString($parts[0], true, true, $supportNonDecimalIPv4);
98 3
        if ($address === null) {
99
            return null;
100 516
        }
101 2
        if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
102
            return null;
103 514
        }
104 514
        $networkPrefix = (int) $parts[1];
105 514
        $addressBytes = $address->getBytes();
106 514
        $totalBytes = count($addressBytes);
107 514
        $numDifferentBits = $totalBytes * 8 - $networkPrefix;
108 2
        if ($numDifferentBits < 0) {
109
            return null;
110 512
        }
111 512
        $numSameBytes = $networkPrefix >> 3;
112 512
        $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
113 512
        $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
114 512
        $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
115 512
        $startSameBits = $networkPrefix % 8;
116 368
        if ($startSameBits !== 0) {
117 368
            $varyingByte = $addressBytes[$numSameBytes];
118 368
            $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
119
            $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
120
        }
121 512
122 512
        return new static(
123 512
            Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
124 512
            Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
125
            $networkPrefix
126
        );
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     *
132
     * @see \IPLib\Range\RangeInterface::toString()
133 279
     */
134
    public function toString($long = false)
135 279
    {
136
        return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     *
142
     * @see \IPLib\Range\RangeInterface::getAddressType()
143 472
     */
144
    public function getAddressType()
145 472
    {
146
        return $this->fromAddress->getAddressType();
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     *
152
     * @see \IPLib\Range\RangeInterface::getStartAddress()
153 61
     */
154
    public function getStartAddress()
155 61
    {
156
        return $this->fromAddress;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     *
162
     * @see \IPLib\Range\RangeInterface::getEndAddress()
163 5
     */
164
    public function getEndAddress()
165 5
    {
166
        return $this->toAddress;
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     *
172
     * @see \IPLib\Range\RangeInterface::getComparableStartString()
173 467
     */
174
    public function getComparableStartString()
175 467
    {
176
        return $this->fromAddress->getComparableString();
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     *
182
     * @see \IPLib\Range\RangeInterface::getComparableEndString()
183 467
     */
184
    public function getComparableEndString()
185 467
    {
186
        return $this->toAddress->getComparableString();
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     *
192
     * @see \IPLib\Range\RangeInterface::asSubnet()
193 72
     */
194
    public function asSubnet()
195 72
    {
196 1
        return $this;
197
    }
198
199 72
    /**
200
     * Get the pattern (asterisk) representation (if applicable) of this range.
201
     *
202
     * @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
203
     */
204
    public function asPattern()
205
    {
206
        $address = $this->getStartAddress();
207 236
        $networkPrefix = $this->getNetworkPrefix();
208
        switch ($address->getAddressType()) {
209 236 View Code Duplication
            case AddressType::T_IPv4:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
                return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
211 View Code Duplication
            case AddressType::T_IPv6:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
                return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
213
        }
214
    }
215
216
    /**
217 56
     * Get the 6to4 address IPv6 address range.
218
     *
219 56
     * @return self
220 56
     */
221 56
    public static function get6to4()
222 56
    {
223 18
        if (self::$sixToFour === null) {
224 38
            self::$sixToFour = self::fromString('2002::/16');
225 38
        }
226
227
        return self::$sixToFour;
228
    }
229
230
    /**
231
     * Get subnet prefix.
232
     *
233
     * @return int
234 19
     */
235
    public function getNetworkPrefix()
236 19
    {
237 1
        return $this->networkPrefix;
238
    }
239 18
240 18
    /**
241 18
     * {@inheritdoc}
242 10
     *
243 10
     * @see \IPLib\Range\RangeInterface::getSubnetMask()
244
     */
245 18
    public function getSubnetMask()
246 13
    {
247
        if ($this->getAddressType() !== AddressType::T_IPv4) {
248 18
            return null;
249
        }
250 18
        $bytes = array();
251
        $prefix = $this->getNetworkPrefix();
252
        while ($prefix >= 8) {
253
            $bytes[] = 255;
254
            $prefix -= 8;
255
        }
256
        if ($prefix !== 0) {
257
            $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
258
        }
259
        $bytes = array_pad($bytes, 4, 0);
260
261
        return IPv4::fromBytes($bytes);
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     *
267
     * @see \IPLib\Range\RangeInterface::getReverseDNSLookupName()
268
     */
269
    public function getReverseDNSLookupName()
270
    {
271
        switch ($this->getAddressType()) {
272
            case AddressType::T_IPv4:
273
                return $this->getReverseDNSLookupNameIPv4();
274
            case AddressType::T_IPv6:
275
                return $this->getReverseDNSLookupNameIPv6();
276
        }
277
    }
278
279
    /**
280
     * @return string[]
281
     */
282
    private function getReverseDNSLookupNameIPv4()
283
    {
284
        switch ($this->networkPrefix) {
285
            case 0:
286
                return array();
287
            case 32:
288
                return array($this->fromAddress->getReverseDNSLookupName());
289
        }
290
        $prefixBytes = (int) ($this->networkPrefix / 8);
291
        $extraBits = (32 - $this->networkPrefix) % 8;
292
        if ($extraBits !== 0) {
293
            $prefixBytes += 1;
294
        }
295
        $numVariants = 1 << $extraBits;
296
        $result = array();
297
        $initialPointer = preg_replace('/^(\d+\.){' . (4 - $prefixBytes) . '}/', '', $this->getStartAddress()->getReverseDNSLookupName());
298
        $chunks = explode('.', $initialPointer, 2);
299
        for ($index = 0; $index < $numVariants; $index++) {
300
            if ($index !== 0) {
301
                $chunks[0] = (string) (1 + (int) $chunks[0]);
302
            }
303
            $result[] = implode('.', $chunks);
304
        }
305
306
        return $result;
307
    }
308
309
    /**
310
     * @return string[]
311
     */
312
    private function getReverseDNSLookupNameIPv6()
313
    {
314
        switch ($this->networkPrefix) {
315
            case 0:
316
                return array();
317
            case 128:
318
                return array($this->fromAddress->getReverseDNSLookupName());
319
        }
320
        $prefixNibbles = (int) ($this->networkPrefix / 4);
321
        $extraBits = (128 - $this->networkPrefix) % 4;
322
        if ($extraBits !== 0) {
323
            $prefixNibbles += 1;
324
        }
325
        $numVariants = 1 << $extraBits;
326
        $result = array();
327
        $initialPointer = preg_replace('/^([0-9A-Fa-f]\.){' . (32 - $prefixNibbles) . '}/', '', $this->getStartAddress()->getReverseDNSLookupName());
328
        $chunks = explode('.', $initialPointer, 2);
329
        for ($index = 0; $index < $numVariants; $index++) {
330
            if ($index !== 0) {
331
                $chunks[0] = dechex(1 + hexdec($chunks[0]));
332
            }
333
            $result[] = implode('.', $chunks);
334
        }
335
336
        return $result;
337
    }
338
}
339