Completed
Pull Request — master (#57)
by Michele
07:38
created

IPv6::getBits()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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