Completed
Push — master ( 334de5...021784 )
by Michele
13s queued 10s
created

IPv4::getBits()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 9
loc 9
c 0
b 0
f 0
ccs 5
cts 5
cp 1
rs 9.9666
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
namespace IPLib\Address;
4
5
use IPLib\Range\RangeInterface;
6
use IPLib\Range\Subnet;
7
use IPLib\Range\Type as RangeType;
8
9
/**
10
 * An IPv4 address.
11
 */
12
class IPv4 implements AddressInterface
13
{
14
    /**
15
     * The string representation of the address.
16
     *
17
     * @var string
18
     *
19
     * @example '127.0.0.1'
20
     */
21
    protected $address;
22
23
    /**
24
     * The byte list of the IP address.
25
     *
26
     * @var int[]|null
27
     */
28
    protected $bytes;
29
30
    /**
31
     * The type of the range of this IP address.
32
     *
33
     * @var int|null
34
     */
35
    protected $rangeType;
36
37
    /**
38
     * A string representation of this address than can be used when comparing addresses and ranges.
39
     *
40
     * @var string
41
     */
42
    protected $comparableString;
43
44
    /**
45
     * An array containing RFC designated address ranges.
46
     *
47
     * @var array|null
48
     */
49
    private static $reservedRanges = null;
50
51
    /**
52
     * Initializes the instance.
53
     *
54
     * @param string $address
55
     */
56 715
    protected function __construct($address)
57
    {
58 715
        $this->address = $address;
59 715
        $this->bytes = null;
60 715
        $this->rangeType = null;
61 715
        $this->comparableString = null;
62 715
    }
63
64
    /**
65
     * {@inheritdoc}
66
     *
67
     * @see \IPLib\Address\AddressInterface::__toString()
68
     */
69 58
    public function __toString()
70
    {
71 58
        return $this->address;
72
    }
73
74
    /**
75
     * Parse a string and returns an IPv4 instance if the string is valid, or null otherwise.
76
     *
77
     * @param string|mixed $address the address to parse
78
     * @param bool $mayIncludePort set to false to avoid parsing addresses with ports
79
     * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
80
     *
81
     * @return static|null
82
     */
83 1187
    public static function fromString($address, $mayIncludePort = true, $supportNonDecimalIPv4 = false)
84
    {
85 1187
        if (!is_string($address) || !strpos($address, '.')) {
86 469
            return null;
87
        }
88 727
        $rxChunk = '0?[0-9]{1,3}';
89 727
        if ($supportNonDecimalIPv4) {
90 32
            $rxChunk = "(?:0[Xx]0*[0-9A-Fa-f]{1,2})|(?:{$rxChunk})";
91
        }
92 727
        $rx = "0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})\.0*?({$rxChunk})";
93 727
        if ($mayIncludePort) {
94 727
            $rx .= '(?::\d+)?';
95
        }
96 727
        $matches = null;
97 727
        if (!preg_match('/^' . $rx . '$/', $address, $matches)) {
98 27
            return null;
99
        }
100 709
        $nums = array();
101 709
        for ($i = 1; $i <= 4; $i++) {
102 709
            $s = $matches[$i];
103 709
            if ($supportNonDecimalIPv4) {
104 32
                if (stripos($s, '0x') === 0) {
105 21
                    $n = hexdec(substr($s, 2));
106 32
                } elseif ($s[0] === '0') {
107 32
                    if (!preg_match('/^[0-7]+$/', $s)) {
108 3
                        return null;
109
                    }
110 29
                    $n = octdec(substr($s, 1));
111
                } else {
112 29
                    $n = (int) $s;
113
                }
114
            } else {
115 689
                $n = (int) $s;
116
            }
117 706
            if ($n < 0 || $n > 255) {
118 6
                return null;
119
            }
120 706
            $nums[] = (string) $n;
121
        }
122
123 700
        return new static(implode('.', $nums));
124
    }
125
126
    /**
127
     * Parse an array of bytes and returns an IPv4 instance if the array is valid, or null otherwise.
128
     *
129
     * @param int[]|array $bytes
130
     *
131
     * @return static|null
132
     */
133 625
    public static function fromBytes(array $bytes)
134
    {
135 625
        $result = null;
136 625
        if (count($bytes) === 4) {
137 353
            $chunks = array_map(
138 353
                function ($byte) {
139 353
                    return (is_int($byte) && $byte >= 0 && $byte <= 255) ? (string) $byte : false;
140 353
                },
141 353
                $bytes
142
            );
143 353
            if (in_array(false, $chunks, true) === false) {
144 353
                $result = new static(implode('.', $chunks));
145
            }
146
        }
147
148 625
        return $result;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     *
154
     * @see \IPLib\Address\AddressInterface::toString()
155
     */
156 594
    public function toString($long = false)
157
    {
158 594
        if ($long) {
159 37
            return $this->getComparableString();
160
        }
161
162 594
        return $this->address;
163
    }
164
165
    /**
166
     * Get the octal representation of this IP address.
167
     *
168
     * @param bool $long
169
     *
170
     * @return string
171
     *
172
     * @example if $long == false: if the decimal representation is '0.7.8.255': '0.7.010.0377'
173
     * @example if $long == true: if the decimal representation is '0.7.8.255': '0000.0007.0010.0377'
174
     */
175 12 View Code Duplication
    public function toOctal($long = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
176
    {
177 12
        $chunks = array();
178 12
        foreach ($this->getBytes() as $byte) {
179 12
            if ($long) {
180 12
                $chunks[] = sprintf('%04o', $byte);
181
            } else {
182 12
                $chunks[] = '0' . decoct($byte);
183
            }
184
        }
185
186 12
        return implode('.', $chunks);
187
    }
188
189
    /**
190
     * Get the hexadecimal representation of this IP address.
191
     *
192
     * @param bool $long
193
     *
194
     * @return string
195
     *
196
     * @example if $long == false: if the decimal representation is '0.9.10.255': '0.9.0xa.0xff'
197
     * @example if $long == true: if the decimal representation is '0.9.10.255': '0x00.0x09.0x0a.0xff'
198
     */
199 12 View Code Duplication
    public function toHexadecimal($long = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
200
    {
201 12
        $chunks = array();
202 12
        foreach ($this->getBytes() as $byte) {
203 12
            if ($long) {
204 12
                $chunks[] = sprintf('0x%02x', $byte);
205
            } else {
206 12
                $chunks[] = '0x' . dechex($byte);
207
            }
208
        }
209
210 12
        return implode('.', $chunks);
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     *
216
     * @see \IPLib\Address\AddressInterface::getBytes()
217
     */
218 536
    public function getBytes()
219
    {
220 536
        if ($this->bytes === null) {
221 536
            $this->bytes = array_map(
222 536
                function ($chunk) {
223 536
                    return (int) $chunk;
224 536
                },
225 536
                explode('.', $this->address)
226
            );
227
        }
228
229 536
        return $this->bytes;
230
    }
231
232
    /**
233
     * {@inheritdoc}
234
     *
235
     * @see \IPLib\Address\AddressInterface::getBits()
236
     */
237 5 View Code Duplication
    public function getBits()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
238
    {
239 5
        $parts = array();
240 5
        foreach ($this->getBytes() as $byte) {
241 5
            $parts[] = sprintf('%08b', $byte);
242
        }
243
244 5
        return implode('', $parts);
245
    }
246
247
    /**
248
     * {@inheritdoc}
249
     *
250
     * @see \IPLib\Address\AddressInterface::getAddressType()
251
     */
252 579
    public function getAddressType()
253
    {
254 579
        return Type::T_IPv4;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     *
260
     * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
261
     */
262 173
    public static function getDefaultReservedRangeType()
263
    {
264 173
        return RangeType::T_PUBLIC;
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     *
270
     * @see \IPLib\Address\AddressInterface::getReservedRanges()
271
     */
272 341
    public static function getReservedRanges()
273
    {
274 341
        if (self::$reservedRanges === null) {
275 1
            $reservedRanges = array();
276
            foreach (array(
277
                // RFC 5735
278 1
                '0.0.0.0/8' => array(RangeType::T_THISNETWORK, array('0.0.0.0/32' => RangeType::T_UNSPECIFIED)),
279
                // RFC 5735
280
                '10.0.0.0/8' => array(RangeType::T_PRIVATENETWORK),
281
                // RFC 6598
282
                '100.64.0.0/10' => array(RangeType::T_CGNAT),
283
                // RFC 5735
284
                '127.0.0.0/8' => array(RangeType::T_LOOPBACK),
285
                // RFC 5735
286
                '169.254.0.0/16' => array(RangeType::T_LINKLOCAL),
287
                // RFC 5735
288
                '172.16.0.0/12' => array(RangeType::T_PRIVATENETWORK),
289
                // RFC 5735
290
                '192.0.0.0/24' => array(RangeType::T_RESERVED),
291
                // RFC 5735
292
                '192.0.2.0/24' => array(RangeType::T_RESERVED),
293
                // RFC 5735
294
                '192.88.99.0/24' => array(RangeType::T_ANYCASTRELAY),
295
                // RFC 5735
296
                '192.168.0.0/16' => array(RangeType::T_PRIVATENETWORK),
297
                // RFC 5735
298
                '198.18.0.0/15' => array(RangeType::T_RESERVED),
299
                // RFC 5735
300
                '198.51.100.0/24' => array(RangeType::T_RESERVED),
301
                // RFC 5735
302
                '203.0.113.0/24' => array(RangeType::T_RESERVED),
303
                // RFC 5735
304
                '224.0.0.0/4' => array(RangeType::T_MULTICAST),
305
                // RFC 5735
306
                '240.0.0.0/4' => array(RangeType::T_RESERVED, array('255.255.255.255/32' => RangeType::T_LIMITEDBROADCAST)),
307
            ) as $range => $data) {
308 1
                $exceptions = array();
309 1 View Code Duplication
                if (isset($data[1])) {
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...
310 1
                    foreach ($data[1] as $exceptionRange => $exceptionType) {
311 1
                        $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
312
                    }
313
                }
314 1
                $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
315
            }
316 1
            self::$reservedRanges = $reservedRanges;
317
        }
318
319 341
        return self::$reservedRanges;
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     *
325
     * @see \IPLib\Address\AddressInterface::getRangeType()
326
     */
327 171
    public function getRangeType()
328
    {
329 171 View Code Duplication
        if ($this->rangeType === null) {
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...
330 171
            $rangeType = null;
331 171
            foreach (static::getReservedRanges() as $reservedRange) {
332 171
                $rangeType = $reservedRange->getAddressType($this);
333 171
                if ($rangeType !== null) {
334 171
                    break;
335
                }
336
            }
337 171
            $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
338
        }
339
340 171
        return $this->rangeType;
341
    }
342
343
    /**
344
     * Create an IPv6 representation of this address (in 6to4 notation).
345
     *
346
     * @return \IPLib\Address\IPv6
347
     */
348 4
    public function toIPv6()
349
    {
350 4
        $myBytes = $this->getBytes();
351
352 4
        return IPv6::fromString('2002:' . sprintf('%02x', $myBytes[0]) . sprintf('%02x', $myBytes[1]) . ':' . sprintf('%02x', $myBytes[2]) . sprintf('%02x', $myBytes[3]) . '::');
353
    }
354
355
    /**
356
     * Create an IPv6 representation of this address (in IPv6 IPv4-mapped notation).
357
     *
358
     * @return \IPLib\Address\IPv6
359
     */
360 4
    public function toIPv6IPv4Mapped()
361
    {
362 4
        return IPv6::fromBytes(array_merge(array(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff), $this->getBytes()));
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     *
368
     * @see \IPLib\Address\AddressInterface::getComparableString()
369
     */
370 391
    public function getComparableString()
371
    {
372 391
        if ($this->comparableString === null) {
373 391
            $chunks = array();
374 391
            foreach ($this->getBytes() as $byte) {
375 391
                $chunks[] = sprintf('%03d', $byte);
376
            }
377 391
            $this->comparableString = implode('.', $chunks);
378
        }
379
380 391
        return $this->comparableString;
381
    }
382
383
    /**
384
     * {@inheritdoc}
385
     *
386
     * @see \IPLib\Address\AddressInterface::matches()
387
     */
388 11
    public function matches(RangeInterface $range)
389
    {
390 11
        return $range->contains($this);
391
    }
392
393
    /**
394
     * {@inheritdoc}
395
     *
396
     * @see \IPLib\Address\AddressInterface::getNextAddress()
397
     */
398 9 View Code Duplication
    public function getNextAddress()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
399
    {
400 9
        $overflow = false;
401 9
        $bytes = $this->getBytes();
402 9
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
403 9
            if ($bytes[$i] === 255) {
404 6
                if ($i === 0) {
405 1
                    $overflow = true;
406 1
                    break;
407
                }
408 6
                $bytes[$i] = 0;
409
            } else {
410 9
                $bytes[$i]++;
411 9
                break;
412
            }
413
        }
414
415 9
        return $overflow ? null : static::fromBytes($bytes);
416
    }
417
418
    /**
419
     * {@inheritdoc}
420
     *
421
     * @see \IPLib\Address\AddressInterface::getPreviousAddress()
422
     */
423 9 View Code Duplication
    public function getPreviousAddress()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
424
    {
425 9
        $overflow = false;
426 9
        $bytes = $this->getBytes();
427 9
        for ($i = count($bytes) - 1; $i >= 0; $i--) {
428 9
            if ($bytes[$i] === 0) {
429 6
                if ($i === 0) {
430 1
                    $overflow = true;
431 1
                    break;
432
                }
433 6
                $bytes[$i] = 255;
434
            } else {
435 9
                $bytes[$i]--;
436 9
                break;
437
            }
438
        }
439
440 9
        return $overflow ? null : static::fromBytes($bytes);
441
    }
442
443
    /**
444
     * {@inheritdoc}
445
     *
446
     * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
447
     */
448 14
    public function getReverseDNSLookupName()
449
    {
450 14
        return implode(
451 14
            '.',
452 14
            array_reverse($this->getBytes())
453 14
        ) . '.in-addr.arpa';
454
    }
455
}
456