Completed
Push — master ( f9ae58...a2a3bd )
by Michele
01:44 queued 12s
created

IPv6::getNextAddress()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
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 IPv6 address.
11
 */
12
class IPv6 implements AddressInterface
13
{
14
    /**
15
     * The long string representation of the address.
16
     *
17
     * @var string
18
     *
19
     * @example '0000:0000:0000:0000:0000:0000:0000:0001'
20
     */
21
    protected $longAddress;
22
23
    /**
24
     * The long string representation of the address.
25
     *
26
     * @var string|null
27
     *
28
     * @example '::1'
29
     */
30
    protected $shortAddress;
31
32
    /**
33
     * The byte list of the IP address.
34
     *
35
     * @var int[]|null
36
     */
37
    protected $bytes;
38
39
    /**
40
     * The word list of the IP address.
41
     *
42
     * @var int[]|null
43
     */
44
    protected $words;
45
46
    /**
47
     * The type of the range of this IP address.
48
     *
49
     * @var int|null
50
     */
51
    protected $rangeType;
52
53
    /**
54
     * An array containing RFC designated address ranges.
55
     *
56
     * @var array|null
57
     */
58
    private static $reservedRanges = null;
59
60
    /**
61
     * Initializes the instance.
62
     *
63
     * @param string $longAddress
64
     */
65 519
    public function __construct($longAddress)
66
    {
67 519
        $this->longAddress = $longAddress;
68 519
        $this->shortAddress = null;
69 519
        $this->bytes = null;
70 519
        $this->words = null;
71 519
        $this->rangeType = null;
72 519
    }
73
74
    /**
75
     * {@inheritdoc}
76
     *
77
     * @see \IPLib\Address\AddressInterface::__toString()
78
     */
79 35
    public function __toString()
80
    {
81 35
        return $this->toString();
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     *
87
     * @see \IPLib\Address\AddressInterface::getNumberOfBits()
88
     */
89 21
    public static function getNumberOfBits()
90
    {
91 21
        return 128;
92
    }
93
94
    /**
95
     * Parse a string and returns an IPv6 instance if the string is valid, or null otherwise.
96
     *
97
     * @param string|mixed $address the address to parse
98
     * @param bool $mayIncludePort set to false to avoid parsing addresses with ports
99
     * @param bool $mayIncludeZoneID set to false to avoid parsing addresses with zone IDs (see RFC 4007)
100
     *
101
     * @return static|null
102
     */
103 573
    public static function fromString($address, $mayIncludePort = true, $mayIncludeZoneID = true)
104
    {
105 573
        $result = null;
106 573
        if (is_string($address) && strpos($address, ':') !== false && strpos($address, ':::') === false) {
107 532
            $matches = null;
108 532
            if ($mayIncludePort && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) {
109 2
                $address = $matches[1];
110
            }
111 532
            if ($mayIncludeZoneID) {
112 532
                $percentagePos = strpos($address, '%');
113 532
                if ($percentagePos > 0) {
114 4
                    $address = substr($address, 0, $percentagePos);
115
                }
116
            }
117 532
            if (preg_match('/^((?:[0-9a-f]*:+)+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $address, $matches)) {
118 9
                $address6 = static::fromString($matches[1] . '0:0', false);
119 9
                if ($address6 !== null) {
120 6
                    $address4 = IPv4::fromString($matches[2], false);
121 6
                    if ($address4 !== null) {
122 6
                        $bytes4 = $address4->getBytes();
123 6
                        $address6->longAddress = substr($address6->longAddress, 0, -9) . sprintf('%02x%02x:%02x%02x', $bytes4[0], $bytes4[1], $bytes4[2], $bytes4[3]);
124 9
                        $result = $address6;
125
                    }
126
                }
127
            } else {
128 532
                if (strpos($address, '::') === false) {
129 396
                    $chunks = explode(':', $address);
130
                } else {
131 158
                    $chunks = array();
132 158
                    $parts = explode('::', $address);
133 158
                    if (count($parts) === 2) {
134 156
                        $before = ($parts[0] === '') ? array() : explode(':', $parts[0]);
135 156
                        $after = ($parts[1] === '') ? array() : explode(':', $parts[1]);
136 156
                        $missing = 8 - count($before) - count($after);
137 156
                        if ($missing >= 0) {
138 154
                            $chunks = $before;
139 154
                            if ($missing !== 0) {
140 153
                                $chunks = array_merge($chunks, array_fill(0, $missing, '0'));
141
                            }
142 154
                            $chunks = array_merge($chunks, $after);
143
                        }
144
                    }
145
                }
146 532
                if (count($chunks) === 8) {
147 523
                    $nums = array_map(
148
                        function ($chunk) {
149 523
                            return preg_match('/^[0-9A-Fa-f]{1,4}$/', $chunk) ? hexdec($chunk) : false;
150 523
                        },
151 523
                        $chunks
152
                    );
153 523
                    if (!in_array(false, $nums, true)) {
154 515
                        $longAddress = implode(
155 515
                            ':',
156 515
                            array_map(
157
                                function ($num) {
158 515
                                    return sprintf('%04x', $num);
159 515
                                },
160 515
                                $nums
161
                            )
162
                        );
163 515
                        $result = new static($longAddress);
164
                    }
165
                }
166
            }
167
        }
168
169 573
        return $result;
170
    }
171
172
    /**
173
     * Parse an array of bytes and returns an IPv6 instance if the array is valid, or null otherwise.
174
     *
175
     * @param int[]|array $bytes
176
     *
177
     * @return static|null
178
     */
179 329
    public static function fromBytes(array $bytes)
180
    {
181 329
        $result = null;
182 329
        if (count($bytes) === 16) {
183 309
            $address = '';
184 309
            for ($i = 0; $i < 16; $i++) {
185 309
                if ($i !== 0 && $i % 2 === 0) {
186 309
                    $address .= ':';
187
                }
188 309
                $byte = $bytes[$i];
189 309
                if (is_int($byte) && $byte >= 0 && $byte <= 255) {
190 309
                    $address .= sprintf('%02x', $byte);
191
                } else {
192
                    $address = null;
193
                    break;
194
                }
195
            }
196 309
            if ($address !== null) {
197 309
                $result = new static($address);
198
            }
199
        }
200
201 329
        return $result;
202
    }
203
204
    /**
205
     * Parse an array of words and returns an IPv6 instance if the array is valid, or null otherwise.
206
     *
207
     * @param int[]|array $words
208
     *
209
     * @return static|null
210
     */
211 75
    public static function fromWords(array $words)
212
    {
213 75
        $result = null;
214 75
        if (count($words) === 8) {
215 55
            $chunks = array();
216 55
            for ($i = 0; $i < 8; $i++) {
217 55
                $word = $words[$i];
218 55
                if (is_int($word) && $word >= 0 && $word <= 0xffff) {
219 55
                    $chunks[] = sprintf('%04x', $word);
220
                } else {
221
                    $chunks = null;
222
                    break;
223
                }
224
            }
225 55
            if ($chunks !== null) {
226 55
                $result = new static(implode(':', $chunks));
227
            }
228
        }
229
230 75
        return $result;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     *
236
     * @see \IPLib\Address\AddressInterface::toString()
237
     */
238 347
    public function toString($long = false)
239
    {
240 347
        if ($long) {
241 55
            $result = $this->longAddress;
242
        } else {
243 324
            if ($this->shortAddress === null) {
244 324
                if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
245 7
                    $lastBytes = array_slice($this->getBytes(), -4);
246 7
                    $this->shortAddress = '::ffff:' . implode('.', $lastBytes);
247
                } else {
248 317
                    $chunks = array_map(
249
                        function ($word) {
250 317
                            return dechex($word);
251 317
                        },
252 317
                        $this->getWords()
253
                    );
254 317
                    $shortAddress = implode(':', $chunks);
255 317
                    $matches = null;
256 317
                    for ($i = 8; $i > 1; $i--) {
257 317
                        $search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . '(?:$|:)';
258 317
                        if (preg_match('/^(.*?)' . $search . '(.*)$/', $shortAddress, $matches)) {
259 259
                            $shortAddress = $matches[1] . '::' . $matches[2];
260 259
                            break;
261
                        }
262
                    }
263 317
                    $this->shortAddress = $shortAddress;
264
                }
265
            }
266 324
            $result = $this->shortAddress;
267
        }
268
269 347
        return $result;
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     *
275
     * @see \IPLib\Address\AddressInterface::getBytes()
276
     */
277 337
    public function getBytes()
278
    {
279 337
        if ($this->bytes === null) {
280 337
            $bytes = array();
281 337
            foreach ($this->getWords() as $word) {
282 337
                $bytes[] = $word >> 8;
283 337
                $bytes[] = $word & 0xff;
284
            }
285 337
            $this->bytes = $bytes;
286
        }
287
288 337
        return $this->bytes;
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     *
294
     * @see \IPLib\Address\AddressInterface::getBits()
295
     */
296 31 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...
297
    {
298 31
        $parts = array();
299 31
        foreach ($this->getBytes() as $byte) {
300 31
            $parts[] = sprintf('%08b', $byte);
301
        }
302
303 31
        return implode('', $parts);
304
    }
305
306
    /**
307
     * Get the word list of the IP address.
308
     *
309
     * @return int[]
310
     */
311 500
    public function getWords()
312
    {
313 500
        if ($this->words === null) {
314 500
            $this->words = array_map(
315
                function ($chunk) {
316 500
                    return hexdec($chunk);
317 500
                },
318 500
                explode(':', $this->longAddress)
319
            );
320
        }
321
322 500
        return $this->words;
323
    }
324
325
    /**
326
     * {@inheritdoc}
327
     *
328
     * @see \IPLib\Address\AddressInterface::getAddressType()
329
     */
330 281
    public function getAddressType()
331
    {
332 281
        return Type::T_IPv6;
333
    }
334
335
    /**
336
     * {@inheritdoc}
337
     *
338
     * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType()
339
     */
340 100
    public static function getDefaultReservedRangeType()
341
    {
342 100
        return RangeType::T_RESERVED;
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     *
348
     * @see \IPLib\Address\AddressInterface::getReservedRanges()
349
     */
350 116
    public static function getReservedRanges()
351
    {
352 116
        if (self::$reservedRanges === null) {
353 1
            $reservedRanges = array();
354
            foreach (array(
355
                // RFC 4291
356 1
                '::/128' => array(RangeType::T_UNSPECIFIED),
357
                // RFC 4291
358
                '::1/128' => array(RangeType::T_LOOPBACK),
359
                // RFC 4291
360
                '100::/8' => array(RangeType::T_DISCARD, array('100::/64' => RangeType::T_DISCARDONLY)),
361
                //'2002::/16' => array(RangeType::),
362
                // RFC 4291
363
                '2000::/3' => array(RangeType::T_PUBLIC),
364
                // RFC 4193
365
                'fc00::/7' => array(RangeType::T_PRIVATENETWORK),
366
                // RFC 4291
367
                'fe80::/10' => array(RangeType::T_LINKLOCAL_UNICAST),
368
                // RFC 4291
369
                'ff00::/8' => array(RangeType::T_MULTICAST),
370
                // RFC 4291
371
                //'::/8' => array(RangeType::T_RESERVED),
372
                // RFC 4048
373
                //'200::/7' => array(RangeType::T_RESERVED),
374
                // RFC 4291
375
                //'400::/6' => array(RangeType::T_RESERVED),
376
                // RFC 4291
377
                //'800::/5' => array(RangeType::T_RESERVED),
378
                // RFC 4291
379
                //'1000::/4' => array(RangeType::T_RESERVED),
380
                // RFC 4291
381
                //'4000::/3' => array(RangeType::T_RESERVED),
382
                // RFC 4291
383
                //'6000::/3' => array(RangeType::T_RESERVED),
384
                // RFC 4291
385
                //'8000::/3' => array(RangeType::T_RESERVED),
386
                // RFC 4291
387
                //'a000::/3' => array(RangeType::T_RESERVED),
388
                // RFC 4291
389
                //'c000::/3' => array(RangeType::T_RESERVED),
390
                // RFC 4291
391
                //'e000::/4' => array(RangeType::T_RESERVED),
392
                // RFC 4291
393
                //'f000::/5' => array(RangeType::T_RESERVED),
394
                // RFC 4291
395
                //'f800::/6' => array(RangeType::T_RESERVED),
396
                // RFC 4291
397
                //'fe00::/9' => array(RangeType::T_RESERVED),
398
                // RFC 3879
399
                //'fec0::/10' => array(RangeType::T_RESERVED),
400
            ) as $range => $data) {
401 1
                $exceptions = array();
402 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...
403 1
                    foreach ($data[1] as $exceptionRange => $exceptionType) {
404 1
                        $exceptions[] = new AssignedRange(Subnet::fromString($exceptionRange), $exceptionType);
405
                    }
406
                }
407 1
                $reservedRanges[] = new AssignedRange(Subnet::fromString($range), $data[0], $exceptions);
408
            }
409 1
            self::$reservedRanges = $reservedRanges;
410
        }
411
412 116
        return self::$reservedRanges;
413
    }
414
415
    /**
416
     * {@inheritdoc}
417
     *
418
     * @see \IPLib\Address\AddressInterface::getRangeType()
419
     */
420 59
    public function getRangeType()
421
    {
422 59
        if ($this->rangeType === null) {
423 59
            $ipv4 = $this->toIPv4();
424 59 View Code Duplication
            if ($ipv4 !== 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...
425 6
                $this->rangeType = $ipv4->getRangeType();
426
            } else {
427 53
                $rangeType = null;
428 53
                foreach (static::getReservedRanges() as $reservedRange) {
429 53
                    $rangeType = $reservedRange->getAddressType($this);
430 53
                    if ($rangeType !== null) {
431 16
                        break;
432
                    }
433
                }
434 53
                $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType;
435
            }
436
        }
437
438 59
        return $this->rangeType;
439
    }
440
441
    /**
442
     * Create an IPv4 representation of this address (if possible, otherwise returns null).
443
     *
444
     * @return \IPLib\Address\IPv4|null
445
     */
446 76
    public function toIPv4()
447
    {
448 76 View Code Duplication
        if (strpos($this->longAddress, '2002:') === 0) {
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...
449
            // 6to4
450 19
            return IPv4::fromBytes(array_slice($this->getBytes(), 2, 4));
451
        }
452 57 View Code Duplication
        if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) {
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...
453
            // IPv4-mapped IPv6 addresses
454 4
            return IPv4::fromBytes(array_slice($this->getBytes(), -4));
455
        }
456
457 53
        return null;
458
    }
459
460
    /**
461
     * Render this IPv6 address in the "mixed" IPv6 (first 12 bytes) + IPv4 (last 4 bytes) mixed syntax.
462
     *
463
     * @param bool $ipV6Long render the IPv6 part in "long" format?
464
     * @param bool $ipV4Long render the IPv4 part in "long" format?
465
     *
466
     * @return string
467
     *
468
     * @example '::13.1.68.3'
469
     * @example '0000:0000:0000:0000:0000:0000:13.1.68.3' when $ipV6Long is true
470
     * @example '::013.001.068.003' when $ipV4Long is true
471
     * @example '0000:0000:0000:0000:0000:0000:013.001.068.003' when $ipV6Long and $ipV4Long are true
472
     *
473
     * @see https://tools.ietf.org/html/rfc4291#section-2.2 point 3.
474
     */
475 6
    public function toMixedIPv6IPv4String($ipV6Long = false, $ipV4Long = false)
476
    {
477 6
        $myBytes = $this->getBytes();
478 6
        $ipv6Bytes = array_merge(array_slice($myBytes, 0, 12), array(0xff, 0xff, 0xff, 0xff));
479 6
        $ipv6String = static::fromBytes($ipv6Bytes)->toString($ipV6Long);
480 6
        $ipv4Bytes = array_slice($myBytes, 12, 4);
481 6
        $ipv4String = IPv4::fromBytes($ipv4Bytes)->toString($ipV4Long);
482
483 6
        return preg_replace('/((ffff:ffff)|(\d+(\.\d+){3}))$/i', $ipv4String, $ipv6String);
484
    }
485
486
    /**
487
     * {@inheritdoc}
488
     *
489
     * @see \IPLib\Address\AddressInterface::getComparableString()
490
     */
491 179
    public function getComparableString()
492
    {
493 179
        return $this->longAddress;
494
    }
495
496
    /**
497
     * {@inheritdoc}
498
     *
499
     * @see \IPLib\Address\AddressInterface::matches()
500
     */
501 13
    public function matches(RangeInterface $range)
502
    {
503 13
        return $range->contains($this);
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     *
509
     * @see \IPLib\Address\AddressInterface::getAddressAtOffset()
510
     */
511 31 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...
512
    {
513 31
        if (!is_int($n)) {
514 1
            return null;
515
        }
516
517 30
        $boundary = 0x10000;
518 30
        $mod = $n;
519 30
        $words = $this->getWords();
520 30
        for ($i = count($words) - 1; $i >= 0; $i--) {
521 30
            $tmp = ($words[$i] + $mod) % $boundary;
522 30
            $mod = (int) floor(($words[$i] + $mod) / $boundary);
523 30
            if ($tmp < 0) {
524 13
                $tmp += $boundary;
525
            }
526
527 30
            $words[$i] = $tmp;
528
        }
529
530 30
        if ($mod !== 0) {
531 5
            return null;
532
        }
533
534 27
        return static::fromWords($words);
535
    }
536
537
    /**
538
     * {@inheritdoc}
539
     *
540
     * @see \IPLib\Address\AddressInterface::getNextAddress()
541
     */
542 8
    public function getNextAddress()
543
    {
544 8
        return $this->getAddressAtOffset(1);
545
    }
546
547
    /**
548
     * {@inheritdoc}
549
     *
550
     * @see \IPLib\Address\AddressInterface::getPreviousAddress()
551
     */
552 8
    public function getPreviousAddress()
553
    {
554 8
        return $this->getAddressAtOffset(-1);
555
    }
556
557
    /**
558
     * {@inheritdoc}
559
     *
560
     * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName()
561
     */
562 22
    public function getReverseDNSLookupName()
563
    {
564 22
        return implode(
565 22
            '.',
566 22
            array_reverse(str_split(str_replace(':', '', $this->toString(true)), 1))
567 22
        ) . '.ip6.arpa';
568
    }
569
}
570