Completed
Pull Request — master (#60)
by Michele
08:45
created

IPv4::getAddressAtOffset()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 25

Duplication

Lines 25
Ratio 100 %

Code Coverage

Tests 12
CRAP Score 5

Importance

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