1 | <?php |
||||
2 | |||||
3 | namespace IPLib\Address; |
||||
4 | |||||
5 | use IPLib\ParseStringFlag; |
||||
6 | use IPLib\Range\RangeInterface; |
||||
7 | use IPLib\Range\Subnet; |
||||
8 | use IPLib\Range\Type as RangeType; |
||||
9 | |||||
10 | /** |
||||
11 | * An IPv6 address. |
||||
12 | */ |
||||
13 | class IPv6 implements AddressInterface |
||||
14 | { |
||||
15 | /** |
||||
16 | * The long string representation of the address. |
||||
17 | * |
||||
18 | * @var string |
||||
19 | * |
||||
20 | * @example '0000:0000:0000:0000:0000:0000:0000:0001' |
||||
21 | */ |
||||
22 | protected $longAddress; |
||||
23 | |||||
24 | /** |
||||
25 | * The long string representation of the address. |
||||
26 | * |
||||
27 | * @var string|null |
||||
28 | * |
||||
29 | * @example '::1' |
||||
30 | */ |
||||
31 | protected $shortAddress; |
||||
32 | |||||
33 | /** |
||||
34 | * The byte list of the IP address. |
||||
35 | * |
||||
36 | * @var int[]|null |
||||
37 | */ |
||||
38 | protected $bytes; |
||||
39 | |||||
40 | /** |
||||
41 | * The word list of the IP address. |
||||
42 | * |
||||
43 | * @var int[]|null |
||||
44 | */ |
||||
45 | protected $words; |
||||
46 | |||||
47 | /** |
||||
48 | * The type of the range of this IP address. |
||||
49 | * |
||||
50 | * @var int|null |
||||
51 | */ |
||||
52 | protected $rangeType; |
||||
53 | |||||
54 | /** |
||||
55 | * An array containing RFC designated address ranges. |
||||
56 | * |
||||
57 | * @var array|null |
||||
58 | */ |
||||
59 | private static $reservedRanges; |
||||
60 | |||||
61 | /** |
||||
62 | * Initializes the instance. |
||||
63 | * |
||||
64 | * @param string $longAddress |
||||
65 | */ |
||||
66 | 709 | public function __construct($longAddress) |
|||
67 | { |
||||
68 | 709 | $this->longAddress = $longAddress; |
|||
69 | 709 | $this->shortAddress = null; |
|||
70 | 709 | $this->bytes = null; |
|||
71 | 709 | $this->words = null; |
|||
72 | 709 | $this->rangeType = null; |
|||
73 | 709 | } |
|||
74 | |||||
75 | /** |
||||
76 | * {@inheritdoc} |
||||
77 | * |
||||
78 | * @see \IPLib\Address\AddressInterface::__toString() |
||||
79 | */ |
||||
80 | 215 | public function __toString() |
|||
81 | { |
||||
82 | 215 | return $this->toString(); |
|||
83 | } |
||||
84 | |||||
85 | /** |
||||
86 | * {@inheritdoc} |
||||
87 | * |
||||
88 | * @see \IPLib\Address\AddressInterface::getNumberOfBits() |
||||
89 | */ |
||||
90 | 33 | public static function getNumberOfBits() |
|||
91 | { |
||||
92 | 33 | return 128; |
|||
93 | } |
||||
94 | |||||
95 | /** |
||||
96 | * @deprecated since 1.17.0: use the parseString() method instead. |
||||
97 | * For upgrading: |
||||
98 | * - if $mayIncludePort is true, use the ParseStringFlag::MAY_INCLUDE_PORT flag |
||||
99 | * - if $mayIncludeZoneID is true, use the ParseStringFlag::MAY_INCLUDE_ZONEID flag |
||||
100 | * |
||||
101 | * @param string|mixed $address |
||||
102 | * @param bool $mayIncludePort |
||||
103 | * @param bool $mayIncludeZoneID |
||||
104 | * |
||||
105 | * @return static|null |
||||
106 | * |
||||
107 | * @see \IPLib\Address\IPv6::parseString() |
||||
108 | * @since 1.1.0 added the $mayIncludePort argument |
||||
109 | * @since 1.3.0 added the $mayIncludeZoneID argument |
||||
110 | */ |
||||
111 | 20 | public static function fromString($address, $mayIncludePort = true, $mayIncludeZoneID = true) |
|||
112 | { |
||||
113 | 20 | return static::parseString($address, 0 | ($mayIncludePort ? ParseStringFlag::MAY_INCLUDE_PORT : 0) | ($mayIncludeZoneID ? ParseStringFlag::MAY_INCLUDE_ZONEID : 0)); |
|||
114 | } |
||||
115 | |||||
116 | /** |
||||
117 | * Parse a string and returns an IPv6 instance if the string is valid, or null otherwise. |
||||
118 | * |
||||
119 | * @param string|mixed $address the address to parse |
||||
120 | * @param int $flags A combination or zero or more flags |
||||
121 | * |
||||
122 | * @return static|null |
||||
123 | * |
||||
124 | * @see \IPLib\ParseStringFlag |
||||
125 | * @since 1.17.0 |
||||
126 | */ |
||||
127 | 788 | public static function parseString($address, $flags = 0) |
|||
128 | { |
||||
129 | 788 | if (!is_string($address)) { |
|||
130 | 3 | return null; |
|||
131 | } |
||||
132 | 785 | $matches = null; |
|||
133 | 785 | $flags = (int) $flags; |
|||
134 | 785 | if ($flags & ParseStringFlag::ADDRESS_MAYBE_RDNS) { |
|||
135 | 5 | if (preg_match('/^([0-9a-f](?:\.[0-9a-f]){31})\.ip6\.arpa\.?/i', $address, $matches)) { |
|||
136 | 5 | $nibbles = array_reverse(explode('.', $matches[1])); |
|||
137 | 5 | $quibbles = array(); |
|||
138 | 5 | foreach (array_chunk($nibbles, 4) as $n) { |
|||
139 | 5 | $quibbles[] = implode('', $n); |
|||
140 | } |
||||
141 | 5 | $address = implode(':', $quibbles); |
|||
142 | } |
||||
143 | } |
||||
144 | 785 | $result = null; |
|||
145 | 785 | if (is_string($address) && strpos($address, ':') !== false && strpos($address, ':::') === false) { |
|||
146 | 722 | if ($flags & ParseStringFlag::MAY_INCLUDE_PORT && $address[0] === '[' && preg_match('/^\[(.+)]:\d+$/', $address, $matches)) { |
|||
147 | 2 | $address = $matches[1]; |
|||
148 | } |
||||
149 | 722 | if ($flags & ParseStringFlag::MAY_INCLUDE_ZONEID) { |
|||
150 | 243 | $percentagePos = strpos($address, '%'); |
|||
151 | 243 | if ($percentagePos > 0) { |
|||
152 | 4 | $address = substr($address, 0, $percentagePos); |
|||
153 | } |
||||
154 | } |
||||
155 | 722 | if (preg_match('/^((?:[0-9a-f]*:+)+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $address, $matches)) { |
|||
156 | 9 | $address6 = static::parseString($matches[1] . '0:0'); |
|||
157 | 9 | if ($address6 !== null) { |
|||
158 | 6 | $address4 = IPv4::parseString($matches[2]); |
|||
159 | 6 | if ($address4 !== null) { |
|||
160 | 6 | $bytes4 = $address4->getBytes(); |
|||
161 | 6 | $address6->longAddress = substr($address6->longAddress, 0, -9) . sprintf('%02x%02x:%02x%02x', $bytes4[0], $bytes4[1], $bytes4[2], $bytes4[3]); |
|||
162 | 9 | $result = $address6; |
|||
163 | } |
||||
164 | } |
||||
165 | } else { |
||||
166 | 722 | if (strpos($address, '::') === false) { |
|||
167 | 413 | $chunks = explode(':', $address); |
|||
168 | } else { |
||||
169 | 330 | $chunks = array(); |
|||
170 | 330 | $parts = explode('::', $address); |
|||
171 | 330 | if (count($parts) === 2) { |
|||
172 | 328 | $before = ($parts[0] === '') ? array() : explode(':', $parts[0]); |
|||
173 | 328 | $after = ($parts[1] === '') ? array() : explode(':', $parts[1]); |
|||
174 | 328 | $missing = 8 - count($before) - count($after); |
|||
175 | 328 | if ($missing >= 0) { |
|||
176 | 326 | $chunks = $before; |
|||
177 | 326 | if ($missing !== 0) { |
|||
178 | 325 | $chunks = array_merge($chunks, array_fill(0, $missing, '0')); |
|||
179 | } |
||||
180 | 326 | $chunks = array_merge($chunks, $after); |
|||
181 | } |
||||
182 | } |
||||
183 | } |
||||
184 | 722 | if (count($chunks) === 8) { |
|||
185 | 713 | $nums = array_map( |
|||
186 | function ($chunk) { |
||||
187 | 713 | return preg_match('/^[0-9A-Fa-f]{1,4}$/', $chunk) ? hexdec($chunk) : false; |
|||
188 | 713 | }, |
|||
189 | 713 | $chunks |
|||
190 | ); |
||||
191 | 713 | if (!in_array(false, $nums, true)) { |
|||
192 | 705 | $longAddress = implode( |
|||
193 | 705 | ':', |
|||
194 | 705 | array_map( |
|||
195 | function ($num) { |
||||
196 | 705 | return sprintf('%04x', $num); |
|||
197 | 705 | }, |
|||
198 | 705 | $nums |
|||
199 | ) |
||||
200 | ); |
||||
201 | 705 | $result = new static($longAddress); |
|||
202 | } |
||||
203 | } |
||||
204 | } |
||||
205 | } |
||||
206 | |||||
207 | 785 | return $result; |
|||
208 | } |
||||
209 | |||||
210 | /** |
||||
211 | * Parse an array of bytes and returns an IPv6 instance if the array is valid, or null otherwise. |
||||
212 | * |
||||
213 | * @param int[]|array $bytes |
||||
214 | * |
||||
215 | * @return static|null |
||||
216 | */ |
||||
217 | 349 | public static function fromBytes(array $bytes) |
|||
218 | { |
||||
219 | 349 | $result = null; |
|||
220 | 349 | if (count($bytes) === 16) { |
|||
221 | 329 | $address = ''; |
|||
222 | 329 | for ($i = 0; $i < 16; $i++) { |
|||
223 | 329 | if ($i !== 0 && $i % 2 === 0) { |
|||
224 | 329 | $address .= ':'; |
|||
225 | } |
||||
226 | 329 | $byte = $bytes[$i]; |
|||
227 | 329 | if (is_int($byte) && $byte >= 0 && $byte <= 255) { |
|||
228 | 329 | $address .= sprintf('%02x', $byte); |
|||
229 | } else { |
||||
230 | $address = null; |
||||
231 | break; |
||||
232 | } |
||||
233 | } |
||||
234 | 329 | if ($address !== null) { |
|||
235 | 329 | $result = new static($address); |
|||
236 | } |
||||
237 | } |
||||
238 | |||||
239 | 349 | return $result; |
|||
240 | } |
||||
241 | |||||
242 | /** |
||||
243 | * Parse an array of words and returns an IPv6 instance if the array is valid, or null otherwise. |
||||
244 | * |
||||
245 | * @param int[]|array $words |
||||
246 | * |
||||
247 | * @return static|null |
||||
248 | */ |
||||
249 | 218 | public static function fromWords(array $words) |
|||
250 | { |
||||
251 | 218 | $result = null; |
|||
252 | 218 | if (count($words) === 8) { |
|||
253 | 198 | $chunks = array(); |
|||
254 | 198 | for ($i = 0; $i < 8; $i++) { |
|||
255 | 198 | $word = $words[$i]; |
|||
256 | 198 | if (is_int($word) && $word >= 0 && $word <= 0xffff) { |
|||
257 | 198 | $chunks[] = sprintf('%04x', $word); |
|||
258 | } else { |
||||
259 | $chunks = null; |
||||
260 | break; |
||||
261 | } |
||||
262 | } |
||||
263 | 198 | if ($chunks !== null) { |
|||
264 | 198 | $result = new static(implode(':', $chunks)); |
|||
265 | } |
||||
266 | } |
||||
267 | |||||
268 | 218 | return $result; |
|||
269 | } |
||||
270 | |||||
271 | /** |
||||
272 | * {@inheritdoc} |
||||
273 | * |
||||
274 | * @see \IPLib\Address\AddressInterface::toString() |
||||
275 | */ |
||||
276 | 518 | public function toString($long = false) |
|||
277 | { |
||||
278 | 518 | if ($long) { |
|||
279 | 47 | $result = $this->longAddress; |
|||
280 | } else { |
||||
281 | 500 | if ($this->shortAddress === null) { |
|||
282 | 500 | if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) { |
|||
283 | 8 | $lastBytes = array_slice($this->getBytes(), -4); |
|||
284 | 8 | $this->shortAddress = '::ffff:' . implode('.', $lastBytes); |
|||
285 | } else { |
||||
286 | 492 | $chunks = array_map( |
|||
287 | function ($word) { |
||||
288 | 492 | return dechex($word); |
|||
289 | 492 | }, |
|||
290 | 492 | $this->getWords() |
|||
291 | ); |
||||
292 | 492 | $shortAddress = implode(':', $chunks); |
|||
293 | 492 | $matches = null; |
|||
294 | 492 | for ($i = 8; $i > 1; $i--) { |
|||
295 | 492 | $search = '(?:^|:)' . rtrim(str_repeat('0:', $i), ':') . '(?:$|:)'; |
|||
296 | 492 | if (preg_match('/^(.*?)' . $search . '(.*)$/', $shortAddress, $matches)) { |
|||
297 | 434 | $shortAddress = $matches[1] . '::' . $matches[2]; |
|||
298 | 434 | break; |
|||
299 | } |
||||
300 | } |
||||
301 | 492 | $this->shortAddress = $shortAddress; |
|||
302 | } |
||||
303 | } |
||||
304 | 500 | $result = $this->shortAddress; |
|||
305 | } |
||||
306 | |||||
307 | 518 | return $result; |
|||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * {@inheritdoc} |
||||
312 | * |
||||
313 | * @see \IPLib\Address\AddressInterface::getBytes() |
||||
314 | */ |
||||
315 | 482 | public function getBytes() |
|||
316 | { |
||||
317 | 482 | if ($this->bytes === null) { |
|||
318 | 482 | $bytes = array(); |
|||
319 | 482 | foreach ($this->getWords() as $word) { |
|||
320 | 482 | $bytes[] = $word >> 8; |
|||
321 | 482 | $bytes[] = $word & 0xff; |
|||
322 | } |
||||
323 | 482 | $this->bytes = $bytes; |
|||
324 | } |
||||
325 | |||||
326 | 482 | return $this->bytes; |
|||
327 | } |
||||
328 | |||||
329 | /** |
||||
330 | * {@inheritdoc} |
||||
331 | * |
||||
332 | * @see \IPLib\Address\AddressInterface::getBits() |
||||
333 | */ |
||||
334 | 159 | public function getBits() |
|||
335 | { |
||||
336 | 159 | $parts = array(); |
|||
337 | 159 | foreach ($this->getBytes() as $byte) { |
|||
338 | 159 | $parts[] = sprintf('%08b', $byte); |
|||
339 | } |
||||
340 | |||||
341 | 159 | return implode('', $parts); |
|||
342 | } |
||||
343 | |||||
344 | /** |
||||
345 | * Get the word list of the IP address. |
||||
346 | * |
||||
347 | * @return int[] |
||||
348 | */ |
||||
349 | 687 | public function getWords() |
|||
350 | { |
||||
351 | 687 | if ($this->words === null) { |
|||
352 | 687 | $this->words = array_map( |
|||
353 | function ($chunk) { |
||||
354 | 687 | return hexdec($chunk); |
|||
355 | 687 | }, |
|||
356 | 687 | explode(':', $this->longAddress) |
|||
357 | ); |
||||
358 | } |
||||
359 | |||||
360 | 687 | return $this->words; |
|||
361 | } |
||||
362 | |||||
363 | /** |
||||
364 | * {@inheritdoc} |
||||
365 | * |
||||
366 | * @see \IPLib\Address\AddressInterface::getAddressType() |
||||
367 | */ |
||||
368 | 296 | public function getAddressType() |
|||
369 | { |
||||
370 | 296 | return Type::T_IPv6; |
|||
371 | } |
||||
372 | |||||
373 | /** |
||||
374 | * {@inheritdoc} |
||||
375 | * |
||||
376 | * @see \IPLib\Address\AddressInterface::getDefaultReservedRangeType() |
||||
377 | */ |
||||
378 | 100 | public static function getDefaultReservedRangeType() |
|||
379 | { |
||||
380 | 100 | return RangeType::T_RESERVED; |
|||
381 | } |
||||
382 | |||||
383 | /** |
||||
384 | * {@inheritdoc} |
||||
385 | * |
||||
386 | * @see \IPLib\Address\AddressInterface::getReservedRanges() |
||||
387 | */ |
||||
388 | 116 | public static function getReservedRanges() |
|||
389 | { |
||||
390 | 116 | if (self::$reservedRanges === null) { |
|||
391 | 1 | $reservedRanges = array(); |
|||
392 | foreach (array( |
||||
393 | // RFC 4291 |
||||
394 | 1 | '::/128' => array(RangeType::T_UNSPECIFIED), |
|||
395 | // RFC 4291 |
||||
396 | '::1/128' => array(RangeType::T_LOOPBACK), |
||||
397 | // RFC 4291 |
||||
398 | '100::/8' => array(RangeType::T_DISCARD, array('100::/64' => RangeType::T_DISCARDONLY)), |
||||
399 | //'2002::/16' => array(RangeType::), |
||||
400 | // RFC 4291 |
||||
401 | '2000::/3' => array(RangeType::T_PUBLIC), |
||||
402 | // RFC 4193 |
||||
403 | 'fc00::/7' => array(RangeType::T_PRIVATENETWORK), |
||||
404 | // RFC 4291 |
||||
405 | 'fe80::/10' => array(RangeType::T_LINKLOCAL_UNICAST), |
||||
406 | // RFC 4291 |
||||
407 | 'ff00::/8' => array(RangeType::T_MULTICAST), |
||||
408 | // RFC 4291 |
||||
409 | //'::/8' => array(RangeType::T_RESERVED), |
||||
410 | // RFC 4048 |
||||
411 | //'200::/7' => array(RangeType::T_RESERVED), |
||||
412 | // RFC 4291 |
||||
413 | //'400::/6' => array(RangeType::T_RESERVED), |
||||
414 | // RFC 4291 |
||||
415 | //'800::/5' => array(RangeType::T_RESERVED), |
||||
416 | // RFC 4291 |
||||
417 | //'1000::/4' => array(RangeType::T_RESERVED), |
||||
418 | // RFC 4291 |
||||
419 | //'4000::/3' => array(RangeType::T_RESERVED), |
||||
420 | // RFC 4291 |
||||
421 | //'6000::/3' => array(RangeType::T_RESERVED), |
||||
422 | // RFC 4291 |
||||
423 | //'8000::/3' => array(RangeType::T_RESERVED), |
||||
424 | // RFC 4291 |
||||
425 | //'a000::/3' => array(RangeType::T_RESERVED), |
||||
426 | // RFC 4291 |
||||
427 | //'c000::/3' => array(RangeType::T_RESERVED), |
||||
428 | // RFC 4291 |
||||
429 | //'e000::/4' => array(RangeType::T_RESERVED), |
||||
430 | // RFC 4291 |
||||
431 | //'f000::/5' => array(RangeType::T_RESERVED), |
||||
432 | // RFC 4291 |
||||
433 | //'f800::/6' => array(RangeType::T_RESERVED), |
||||
434 | // RFC 4291 |
||||
435 | //'fe00::/9' => array(RangeType::T_RESERVED), |
||||
436 | // RFC 3879 |
||||
437 | //'fec0::/10' => array(RangeType::T_RESERVED), |
||||
438 | ) as $range => $data) { |
||||
439 | 1 | $exceptions = array(); |
|||
440 | 1 | if (isset($data[1])) { |
|||
441 | 1 | foreach ($data[1] as $exceptionRange => $exceptionType) { |
|||
442 | 1 | $exceptions[] = new AssignedRange(Subnet::parseString($exceptionRange), $exceptionType); |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
443 | } |
||||
444 | } |
||||
445 | 1 | $reservedRanges[] = new AssignedRange(Subnet::parseString($range), $data[0], $exceptions); |
|||
446 | } |
||||
447 | 1 | self::$reservedRanges = $reservedRanges; |
|||
448 | } |
||||
449 | |||||
450 | 116 | return self::$reservedRanges; |
|||
451 | } |
||||
452 | |||||
453 | /** |
||||
454 | * {@inheritdoc} |
||||
455 | * |
||||
456 | * @see \IPLib\Address\AddressInterface::getRangeType() |
||||
457 | */ |
||||
458 | 59 | public function getRangeType() |
|||
459 | { |
||||
460 | 59 | if ($this->rangeType === null) { |
|||
461 | 59 | $ipv4 = $this->toIPv4(); |
|||
462 | 59 | if ($ipv4 !== null) { |
|||
463 | 6 | $this->rangeType = $ipv4->getRangeType(); |
|||
464 | } else { |
||||
465 | 53 | $rangeType = null; |
|||
466 | 53 | foreach (static::getReservedRanges() as $reservedRange) { |
|||
467 | 53 | $rangeType = $reservedRange->getAddressType($this); |
|||
468 | 53 | if ($rangeType !== null) { |
|||
469 | 16 | break; |
|||
470 | } |
||||
471 | } |
||||
472 | 53 | $this->rangeType = $rangeType === null ? static::getDefaultReservedRangeType() : $rangeType; |
|||
473 | } |
||||
474 | } |
||||
475 | |||||
476 | 59 | return $this->rangeType; |
|||
477 | } |
||||
478 | |||||
479 | /** |
||||
480 | * Create an IPv4 representation of this address (if possible, otherwise returns null). |
||||
481 | * |
||||
482 | * @return \IPLib\Address\IPv4|null |
||||
483 | */ |
||||
484 | 76 | public function toIPv4() |
|||
485 | { |
||||
486 | 76 | if (strpos($this->longAddress, '2002:') === 0) { |
|||
487 | // 6to4 |
||||
488 | 19 | return IPv4::fromBytes(array_slice($this->getBytes(), 2, 4)); |
|||
489 | } |
||||
490 | 57 | if (strpos($this->longAddress, '0000:0000:0000:0000:0000:ffff:') === 0) { |
|||
491 | // IPv4-mapped IPv6 addresses |
||||
492 | 4 | return IPv4::fromBytes(array_slice($this->getBytes(), -4)); |
|||
493 | } |
||||
494 | |||||
495 | 53 | return null; |
|||
496 | } |
||||
497 | |||||
498 | /** |
||||
499 | * Render this IPv6 address in the "mixed" IPv6 (first 12 bytes) + IPv4 (last 4 bytes) mixed syntax. |
||||
500 | * |
||||
501 | * @param bool $ipV6Long render the IPv6 part in "long" format? |
||||
502 | * @param bool $ipV4Long render the IPv4 part in "long" format? |
||||
503 | * |
||||
504 | * @return string |
||||
505 | * |
||||
506 | * @example '::13.1.68.3' |
||||
507 | * @example '0000:0000:0000:0000:0000:0000:13.1.68.3' when $ipV6Long is true |
||||
508 | * @example '::013.001.068.003' when $ipV4Long is true |
||||
509 | * @example '0000:0000:0000:0000:0000:0000:013.001.068.003' when $ipV6Long and $ipV4Long are true |
||||
510 | * |
||||
511 | * @see https://tools.ietf.org/html/rfc4291#section-2.2 point 3. |
||||
512 | * @since 1.9.0 |
||||
513 | */ |
||||
514 | 6 | public function toMixedIPv6IPv4String($ipV6Long = false, $ipV4Long = false) |
|||
515 | { |
||||
516 | 6 | $myBytes = $this->getBytes(); |
|||
517 | 6 | $ipv6Bytes = array_merge(array_slice($myBytes, 0, 12), array(0xff, 0xff, 0xff, 0xff)); |
|||
518 | 6 | $ipv6String = static::fromBytes($ipv6Bytes)->toString($ipV6Long); |
|||
519 | 6 | $ipv4Bytes = array_slice($myBytes, 12, 4); |
|||
520 | 6 | $ipv4String = IPv4::fromBytes($ipv4Bytes)->toString($ipV4Long); |
|||
521 | |||||
522 | 6 | return preg_replace('/((ffff:ffff)|(\d+(\.\d+){3}))$/i', $ipv4String, $ipv6String); |
|||
523 | } |
||||
524 | |||||
525 | /** |
||||
526 | * {@inheritdoc} |
||||
527 | * |
||||
528 | * @see \IPLib\Address\AddressInterface::getComparableString() |
||||
529 | */ |
||||
530 | 186 | public function getComparableString() |
|||
531 | { |
||||
532 | 186 | return $this->longAddress; |
|||
533 | } |
||||
534 | |||||
535 | /** |
||||
536 | * {@inheritdoc} |
||||
537 | * |
||||
538 | * @see \IPLib\Address\AddressInterface::matches() |
||||
539 | */ |
||||
540 | 13 | public function matches(RangeInterface $range) |
|||
541 | { |
||||
542 | 13 | return $range->contains($this); |
|||
543 | } |
||||
544 | |||||
545 | /** |
||||
546 | * {@inheritdoc} |
||||
547 | * |
||||
548 | * @see \IPLib\Address\AddressInterface::getAddressAtOffset() |
||||
549 | */ |
||||
550 | 31 | public function getAddressAtOffset($n) |
|||
551 | { |
||||
552 | 31 | if (!is_int($n)) { |
|||
553 | 1 | return null; |
|||
554 | } |
||||
555 | |||||
556 | 30 | $boundary = 0x10000; |
|||
557 | 30 | $mod = $n; |
|||
558 | 30 | $words = $this->getWords(); |
|||
559 | 30 | for ($i = count($words) - 1; $i >= 0; $i--) { |
|||
560 | 30 | $tmp = ($words[$i] + $mod) % $boundary; |
|||
561 | 30 | $mod = (int) floor(($words[$i] + $mod) / $boundary); |
|||
562 | 30 | if ($tmp < 0) { |
|||
563 | 13 | $tmp += $boundary; |
|||
564 | } |
||||
565 | |||||
566 | 30 | $words[$i] = $tmp; |
|||
567 | } |
||||
568 | |||||
569 | 30 | if ($mod !== 0) { |
|||
570 | 5 | return null; |
|||
571 | } |
||||
572 | |||||
573 | 27 | return static::fromWords($words); |
|||
574 | } |
||||
575 | |||||
576 | /** |
||||
577 | * {@inheritdoc} |
||||
578 | * |
||||
579 | * @see \IPLib\Address\AddressInterface::getNextAddress() |
||||
580 | */ |
||||
581 | 8 | public function getNextAddress() |
|||
582 | { |
||||
583 | 8 | return $this->getAddressAtOffset(1); |
|||
584 | } |
||||
585 | |||||
586 | /** |
||||
587 | * {@inheritdoc} |
||||
588 | * |
||||
589 | * @see \IPLib\Address\AddressInterface::getPreviousAddress() |
||||
590 | */ |
||||
591 | 8 | public function getPreviousAddress() |
|||
592 | { |
||||
593 | 8 | return $this->getAddressAtOffset(-1); |
|||
594 | } |
||||
595 | |||||
596 | /** |
||||
597 | * {@inheritdoc} |
||||
598 | * |
||||
599 | * @see \IPLib\Address\AddressInterface::getReverseDNSLookupName() |
||||
600 | */ |
||||
601 | 22 | public function getReverseDNSLookupName() |
|||
602 | { |
||||
603 | 22 | return implode( |
|||
604 | 22 | '.', |
|||
605 | 22 | array_reverse(str_split(str_replace(':', '', $this->toString(true)), 1)) |
|||
0 ignored issues
–
show
It seems like
str_split(str_replace(':...is->toString(true)), 1) can also be of type true ; however, parameter $array of array_reverse() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
606 | 22 | ) . '.ip6.arpa'; |
|||
607 | } |
||||
608 | |||||
609 | /** |
||||
610 | * {@inheritdoc} |
||||
611 | * |
||||
612 | * @see \IPLib\Address\AddressInterface::shift() |
||||
613 | */ |
||||
614 | 152 | public function shift($bits) |
|||
615 | { |
||||
616 | 152 | $bits = (int) $bits; |
|||
617 | 152 | if ($bits === 0) { |
|||
618 | 8 | return $this; |
|||
619 | } |
||||
620 | 144 | $absBits = abs($bits); |
|||
621 | 144 | if ($absBits >= 128) { |
|||
622 | 16 | return new self('0000:0000:0000:0000:0000:0000:0000:0000'); |
|||
623 | } |
||||
624 | 128 | $pad = str_repeat('0', $absBits); |
|||
0 ignored issues
–
show
It seems like
$absBits can also be of type double ; however, parameter $times of str_repeat() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
625 | 128 | $paddedBits = $this->getBits(); |
|||
626 | 128 | if ($bits > 0) { |
|||
627 | 57 | $paddedBits = $pad . substr($paddedBits, 0, -$bits); |
|||
628 | } else { |
||||
629 | 71 | $paddedBits = substr($paddedBits, $absBits) . $pad; |
|||
0 ignored issues
–
show
It seems like
$absBits can also be of type double ; however, parameter $offset of substr() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
630 | } |
||||
631 | 128 | $bytes = array_map('bindec', str_split($paddedBits, 16)); |
|||
0 ignored issues
–
show
It seems like
str_split($paddedBits, 16) can also be of type true ; however, parameter $array of array_map() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
632 | |||||
633 | 128 | return static::fromWords($bytes); |
|||
634 | } |
||||
635 | |||||
636 | /** |
||||
637 | * {@inheritdoc} |
||||
638 | * |
||||
639 | * @see \IPLib\Address\AddressInterface::add() |
||||
640 | */ |
||||
641 | 18 | public function add(AddressInterface $other) |
|||
642 | { |
||||
643 | 18 | if (!$other instanceof self) { |
|||
644 | 2 | return null; |
|||
645 | } |
||||
646 | 16 | $myWords = $this->getWords(); |
|||
647 | 16 | $otherWords = $other->getWords(); |
|||
648 | 16 | $sum = array_fill(0, 8, 0); |
|||
649 | 16 | $carry = 0; |
|||
650 | 16 | for ($index = 7; $index >= 0; $index--) { |
|||
651 | 16 | $word = $myWords[$index] + $otherWords[$index] + $carry; |
|||
652 | 16 | if ($word > 0xFFFF) { |
|||
653 | 7 | $carry = $word >> 16; |
|||
654 | 7 | $word &= 0xFFFF; |
|||
655 | } else { |
||||
656 | 16 | $carry = 0; |
|||
657 | } |
||||
658 | 16 | $sum[$index] = $word; |
|||
659 | } |
||||
660 | 16 | if ($carry !== 0) { |
|||
661 | 2 | return null; |
|||
662 | } |
||||
663 | |||||
664 | 15 | return static::fromWords($sum); |
|||
665 | } |
||||
666 | } |
||||
667 |