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

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