├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── classes
└── Leth
│ └── IPAddress
│ ├── IP
│ ├── Address.php
│ ├── NetworkAddress.php
│ └── NetworkAddressIterator.php
│ ├── IPv4
│ ├── Address.php
│ └── NetworkAddress.php
│ └── IPv6
│ ├── Address.php
│ └── NetworkAddress.php
├── composer.json
├── lgpl-3.0.txt
├── phpstan.neon
├── phpunit.xml.dist
└── tests
├── IPAddressTest.php
├── IPNetworkAddressTest.php
├── IPv4AddressTest.php
├── IPv4NetworkAddressTest.php
├── IPv6AddressTest.php
├── IPv6NetworkAddressTest.php
└── bootstrap.php
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | run:
7 | strategy:
8 | max-parallel: 3
9 | matrix:
10 | os: [ubuntu-latest, windows-latest, macos-latest]
11 | php: ["8.1", "8.2", "8.3"]
12 | math_biginteger_mode: [INTERNAL, GMP, BCMATH]
13 | include:
14 | - os: ubuntu-latest
15 | phpstan: yes
16 |
17 | name: "PHP ${{ matrix.php }} (bigint mode: ${{ matrix.math_biginteger_mode }}) (OS: ${{ matrix.os }})"
18 | runs-on: ${{ matrix.os }}
19 | steps:
20 | - uses: actions/checkout@v3
21 |
22 | - name: Setup PHP
23 | uses: shivammathur/setup-php@v2
24 | with:
25 | php-version: ${{ matrix.php }}
26 | extensions: xdebug, dom, gmp, bcmath
27 | coverage: xdebug
28 | env:
29 | fail-fast: true
30 |
31 | - name: Validate composer.json and composer.lock
32 | run: composer validate
33 |
34 | - name: Install dependencies
35 | run: composer install --prefer-dist --no-progress --no-suggest
36 |
37 | - name: Run test suite
38 | run: composer run-script test
39 | env:
40 | MATH_BIGINTEGER_MODE: ${{ matrix.math_biginteger_mode }}
41 |
42 | - if: ${{ matrix.phpstan }}
43 | uses: php-actions/phpstan@v3
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /.idea/
3 | composer.phar
4 | .phpunit.result.cache
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [2.0.0] - 2019-02-15
4 |
5 | ### Added
6 |
7 | - PHP 8.1 support [@mrcnpdlk](https://github.com/mrcnpdlk).
8 |
9 | ### Changed
10 |
11 | - PHP 8.1 is now the minimum required version [@mrcnpdlk](https://github.com/mrcnpdlk).
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | lgpl-3.0.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHP-IPAddress
2 |
3 | A set of utility classes for working with IP addresses in PHP.
4 | Supports both IPv4 and IPv6 schemes.
5 |
6 | ### Requirements
7 |
8 | * PHP version 5.3.0 or greater.
9 | * The [PEAR](http://pear.php.net/) [Math_BigInteger](http://pear.php.net/package/Math_BigInteger/) class
10 |
11 | Required for add & subtract operations on IPv6 addresses, and finding IPs in IPv6 address blocks.
12 |
13 | ## Examples
14 |
15 | ```php
16 | get_network_address() . "\n";
45 | // Prints '192.168.0.255'
46 | echo $net_addr->get_broadcast_address() . "\n";
47 | // Prints '255.255.255.0'
48 | echo $net_addr->get_subnet_mask() . "\n";
49 |
50 | /**
51 | * For each address of the specified network.
52 | */
53 | $network = IPv4\NetworkAddress::factory('192.168.0.0/24');
54 | foreach ($network as $ip) {
55 | // $ip is instance of IPv4\Address with value:
56 | // 192.168.0.0
57 | // 192.168.0.1
58 | // ...
59 | }
60 |
61 | $network = IPv4\NetworkAddress::factory('192.168.0.0/24');
62 | // Prints '256'
63 | echo count($network);
64 |
65 | /**
66 | * Merge adjacent NetworkAddress blocks into larger blocks
67 | */
68 | $small = array(
69 | IPv4\NetworkAddress::factory('192.168.0.0/24'),
70 | IPv4\NetworkAddress::factory('192.168.1.0/24')
71 | );
72 | $merged = IP\NetworkAddress::merge($small);
73 | // Prints '1'
74 | echo count($merged);
75 | // Prints '1'
76 | echo $merged[0] == IP\NetworkAddress::factory('192.168.0.0/23');
77 |
78 | /**
79 | * Get specified octet from IP
80 | */
81 | $ipv4 = IP\Address::factory('192.168.1.102');
82 | // Prints '102'
83 | echo $ipv4->get_octet(-1);
84 | // Prints '168'
85 | echo $ipv4[1];
86 |
87 | $ipv6 = IP\Address::factory('2490::fa');
88 | // Prints '250'
89 | echo $ipv6->get_octet(-1);
90 | // Prints '0'
91 | echo $ipv6[5];
92 | ```
93 |
94 | ## Test Cases
95 |
96 | To run the test cases, the following commands will do the trick:
97 |
98 | * No-frills tests:
99 |
100 | phpunit -c phpunit.xml.dist
101 |
102 | * Generate code coverage reports into './coverage/':
103 |
104 | phpunit -c phpunit.xml.dist --coverage-html coverage
105 |
106 | * With colours and verbose output:
107 |
108 | phpunit -c phpunit.xml.dist --colors --verbose
109 |
110 | * All together:
111 |
112 | phpunit -c phpunit.xml.dist --coverage-html coverage --colors --verbose
113 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IP/Address.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IP;
21 | use \Leth\IPAddress\IP, \Leth\IPAddress\IPv4, \Leth\IPAddress\IPv6;
22 | use ReturnTypeWillChange;
23 |
24 | /**
25 | * An abstract representation of an IP Address.
26 | *
27 | * @author Marcus Cobden
28 | */
29 | abstract class Address implements \ArrayAccess
30 | {
31 | public const IP_VERSION = -1;
32 | public const FORMAT_FULL = 0;
33 | public const FORMAT_COMPACT = 1;
34 |
35 | /**
36 | * Internal representation of the address. Format may vary.
37 | * @var numeric
38 | */
39 | protected $address;
40 |
41 | /**
42 | * Create an IP address object from the supplied address.
43 | *
44 | * @param IP\Address|int|string|\Math_BigInteger $address The address to represent.
45 | *
46 | * @return \Leth\IPAddress\IP\Address|\Leth\IPAddress\IPv4\Address|\Leth\IPAddress\IPv6\Address An instance of a subclass of IP\Address; either IPv4\Address or IPv6\Address
47 | */
48 | public static function factory(IP\Address|int|string|\Math_BigInteger $address): IP\Address|Ipv4\Address|IPv6\Address
49 | {
50 | if ($address instanceof self)
51 | {
52 | return $address;
53 | }
54 | elseif (is_int($address) || (is_string($address) && filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)))
55 | {
56 | return IPv4\Address::factory($address);
57 | }
58 | elseif ($address instanceof \Math_BigInteger || (is_string($address) && filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)))
59 | {
60 | return IPv6\Address::factory($address);
61 | }
62 | else
63 | {
64 | throw new \InvalidArgumentException('Unable to guess IP address type from \''.$address.'\'.');
65 | }
66 | }
67 |
68 | /**
69 | * Compare 2 IP Address objects.
70 | *
71 | * This method is a wrapper for the compare_to method and is useful in callback situations, e.g.
72 | * usort($addresses, array('IP\Address', 'compare'));
73 | *
74 | * @param IP\Address $a The left hand side of the comparison.
75 | * @param IP\Address $b The right hand side of the comparison.
76 | * @return int The result of the comparison.
77 | */
78 | public static function compare(IP\Address $a, IP\Address $b): int
79 | {
80 | return $a->compare_to($b);
81 | }
82 |
83 | /**
84 | * Create a new IP Address object.
85 | *
86 | * @param int|string $address The address to represent.
87 | */
88 | protected function __construct(int|string $address)
89 | {
90 | $this->address = $address;
91 | }
92 |
93 | /**
94 | * Add the given value to this address.
95 | *
96 | * @param integer|\Math_BigInteger $value
97 | * @return IP\Address An address representing the result of the operation.
98 | */
99 | abstract public function add($value): Address;
100 |
101 | /**
102 | * Subtract the given value from this address.
103 | *
104 | * @param integer|\Math_BigInteger $value
105 | * @return IP\Address An address representing the result of the operation.
106 | */
107 | abstract public function subtract($value): Address;
108 |
109 | /**
110 | * Compute the bitwise AND of this address and another.
111 | *
112 | * @param IP\Address $other The other operand.
113 | * @return IP\Address An address representing the result of the operation.
114 | */
115 | abstract public function bitwise_and(IP\Address $other);
116 |
117 | /**
118 | * Compute the bitwise OR of this address and another.
119 | *
120 | * @param IP\Address $other The other operand.
121 | * @return IP\Address An address representing the result of the operation.
122 | */
123 | abstract public function bitwise_or(IP\Address $other): Address;
124 |
125 | /**
126 | * Compute the bitwise XOR (Exclusive OR) of this address and another.
127 | *
128 | * @param IP\Address $other The other operand.
129 | * @return IP\Address An address representing the result of the operation.
130 | */
131 | abstract public function bitwise_xor(IP\Address $other): Address;
132 |
133 | /**
134 | * Compute the bitwise NOT of this address.
135 | *
136 | * @return IP\Address An address representing the result of the operation.
137 | */
138 | abstract public function bitwise_not(): Address;
139 |
140 | /**
141 | * Compare this IP Address with another.
142 | *
143 | * @param IP\Address $other The instance to compare to.
144 | * @return int The result of the comparison.
145 | */
146 | abstract public function compare_to(IP\Address $other): int;
147 |
148 | /**
149 | * Convert this object to a string representation
150 | *
151 | * @return string This IP address expressed as a string.
152 | */
153 | public function __toString(): string
154 | {
155 | return $this->format(self::FORMAT_COMPACT);
156 | }
157 |
158 | /**
159 | * Return the string representation of the address
160 | *
161 | * @return string This IP address expressed as a string.
162 | */
163 | abstract public function format(int $mode): string;
164 |
165 | /**
166 | * Check that this instance and the supplied instance are of the same class.
167 | *
168 | * @param IP\Address $other The object to check.
169 | * @throws \InvalidArgumentException if objects are of the same class.
170 | */
171 | protected function check_types(IP\Address $other): void
172 | {
173 | if (get_class($this) !== get_class($other)) {
174 | throw new \InvalidArgumentException('Incompatible types.');
175 | }
176 | }
177 |
178 | /**
179 | * Get the specified octet from this address.
180 | *
181 | * @param integer $number
182 | *
183 | * @return ?integer An octet value the result of the operation.
184 | */
185 | public function get_octet(int $number): ?int
186 | {
187 | $address = unpack("C*", $this->address);
188 | $index = (($number >= 0) ? $number : count($address) + $number);
189 | $index++;
190 |
191 | return $address[$index] ?? null;
192 | }
193 |
194 | /**
195 | * Whether octet index in allowed range
196 | *
197 | * @param integer $offset
198 | * @return boolean
199 | */
200 | public function offsetExists($offset): bool
201 | {
202 | return ($this->get_octet($offset) !== NULL);
203 | }
204 |
205 | /**
206 | * Get the octet value from index
207 | *
208 | * @param integer $offset
209 | *
210 | * @return integer|null
211 | */
212 | public function offsetGet($offset): ?int
213 | {
214 | return $this->get_octet($offset);
215 | }
216 |
217 | /**
218 | * Operation unsupported
219 | *
220 | * @param integer $offset
221 | * @param mixed $value
222 | * @throws \LogicException
223 | */
224 | #[ReturnTypeWillChange]
225 | public function offsetSet($offset, $value): mixed
226 | {
227 | throw new \LogicException('Operation unsupported');
228 | }
229 |
230 | /**
231 | * Operation unsupported
232 | *
233 | * @param integer $offset
234 | * @throws \LogicException
235 | */
236 | #[ReturnTypeWillChange]
237 | public function offsetUnset($offset): void
238 | {
239 | throw new \LogicException('Operation unsupported');
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IP/NetworkAddress.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IP;
21 | use \Leth\IPAddress\IP, \Leth\IPAddress\IPv4, \Leth\IPAddress\IPv6;
22 | use ReturnTypeWillChange;
23 |
24 | /**
25 | * An abstract representation of an IP Address in a given network
26 | *
27 | * @package default
28 | * @author Marcus Cobden
29 | */
30 | abstract class NetworkAddress implements \IteratorAggregate, \Countable
31 | {
32 | public const IP_VERSION = -1;
33 | public const MAX_SUBNET = -1;
34 |
35 | /**
36 | * The IP Address
37 | *
38 | * @var IP\Address
39 | */
40 | protected IP\Address $address;
41 |
42 | /**
43 | * The CIDR number
44 | *
45 | * @var int
46 | */
47 | protected int $cidr;
48 |
49 | /**
50 | * Generates the subnet mask for a given CIDR
51 | *
52 | * @param int $cidr The CIDR number
53 | * @return IP\Address An IP address representing the mask.
54 | */
55 | public static function generate_subnet_mask(int $cidr): Address
56 | {
57 | throw new \LogicException(__METHOD__.' not implemented in subclass of '.__CLASS__);
58 | }
59 |
60 | /**
61 | * Gets the Global subnet mask for this IP Protocol
62 | *
63 | * @return IP\Address An IP Address representing the mask.
64 | * @author Marcus Cobden
65 | */
66 | public static function get_global_netmask(): Address
67 | {
68 | throw new \LogicException(__METHOD__.' not implemented in subclass of '.__CLASS__);
69 | }
70 |
71 | /**
72 | * Creates an IP\NetworkAddress for the supplied string
73 | *
74 | * @param \Leth\IPAddress\IP\NetworkAddress|\Leth\IPAddress\IP\Address|string $address IP Network Address string.
75 | * @param int|string|null $cidr Optional CIDR number. If not supplied It is assumed to be part of the address string
76 | *
77 | * @return \Leth\IPAddress\IPv4\NetworkAddress|\Leth\IPAddress\IP\NetworkAddress|\Leth\IPAddress\IPv6\NetworkAddress
78 | */
79 | public static function factory(NetworkAddress|Address|string $address, int|string|null $cidr = NULL): IPv4\NetworkAddress|NetworkAddress|IPv6\NetworkAddress
80 | {
81 | if ($address instanceof self)
82 | {
83 | if ($cidr !== NULL && $cidr !== $address->cidr)
84 | {
85 | $class = get_class($address);
86 | return new $class($address->address, $cidr);
87 | }
88 | return $address;
89 | }
90 |
91 | if(is_string($address)){
92 | $parts = explode('/', $address, 2);
93 | if (count($parts) === 2) {
94 | if ($cidr === NULL)
95 | // Parse CIDR from $address variable because $cidr is null
96 | {
97 | [$address, $cidr] = $parts;
98 | }
99 | else
100 | // Ignore CIDR into $address variable
101 | {
102 | [$address] = $parts;
103 | }
104 | }
105 | }
106 |
107 | if (is_string($cidr))
108 | {
109 | if ( ! ctype_digit($cidr)) {
110 | throw new \InvalidArgumentException("Malformed CIDR suffix '$cidr'.");
111 | }
112 |
113 | $cidr = (int)$cidr;
114 | }
115 |
116 | if ( ! $address instanceof IP\Address)
117 | {
118 | $address = IP\Address::factory($address);
119 | }
120 |
121 | if ($address instanceof IPv4\Address) {
122 | return new IPv4\NetworkAddress($address, $cidr);
123 | }
124 | elseif ($address instanceof IPv6\Address) {
125 | return new IPv6\NetworkAddress($address, $cidr);
126 | }
127 | else {
128 | throw new \InvalidArgumentException('Unsupported IP Address type \'' . get_class($address) . '\'.');
129 | }
130 | }
131 |
132 | /**
133 | * Compare 2 IP Network Address objects.
134 | *
135 | * This method is a wrapper for the compare_to method and is useful in callback situations, e.g.
136 | * usort($addresses, array('IP\NetworkAddress', 'compare'));
137 | *
138 | * @param \Leth\IPAddress\IP\NetworkAddress $a The left hand side of the comparison.
139 | * @param \Leth\IPAddress\IP\NetworkAddress $b The right hand side of the comparison.
140 | *
141 | * @return int The result of the comparison.
142 | */
143 | public static function compare(IP\NetworkAddress $a, IP\NetworkAddress $b): int
144 | {
145 | return $a->compare_to($b);
146 | }
147 |
148 | /**
149 | * Merge adjacent network blocks
150 | *
151 | * Ajacent blocks can only be merged if they belong to the same parent block
152 | *
153 | * @param array $network_addresses NetworkAddresses to merge
154 | * @return array NetworkAddresses remaining after merging
155 | */
156 | public static function merge(array $network_addresses): array
157 | {
158 | $net_addr_index = array();
159 | foreach ($network_addresses as $net_addr) {
160 | // Ensure sure we're only dealing with network identifiers
161 | $net_addr = $net_addr->get_network_identifier();
162 | $net_addr_index[$net_addr::IP_VERSION][$net_addr->cidr][] = $net_addr;
163 | }
164 | // We're done with this structure now
165 | unset($network_addresses);
166 |
167 | $out = array();
168 | foreach ($net_addr_index as $version => $cidr_addrs)
169 | {
170 | $max = $version === 4 ? IPv4\NetworkAddress::MAX_SUBNET : IPv6\NetworkAddress::MAX_SUBNET;
171 | // smallest networks first (largest cidr)
172 | // We have to loop by index because we modify the array while we iterate
173 | for ($cidr = $max; $cidr > 0; $cidr--)
174 | {
175 | if (! array_key_exists($cidr, $cidr_addrs))
176 | continue;
177 | $net_addrs = $cidr_addrs[$cidr];
178 | if (count($net_addrs) === 1)
179 | {
180 | $out[] = $net_addrs[0];
181 | continue;
182 | }
183 | usort($net_addrs, array(__CLASS__, 'compare'));
184 |
185 | $last_added = NULL;
186 | $a = $b = NULL;
187 | for ($i = 0; $i < count($net_addrs) - 1; $i++) {
188 | $a = $net_addrs[$i];
189 | $b = $net_addrs[$i + 1];
190 | if ($a->compare_to($b) === 0)
191 | continue;
192 | $parent = $a->get_parent();
193 | if ($parent->compare_to($b->get_parent()) === 0)
194 | {
195 | $cidr_addrs[$parent->cidr][] = $parent;
196 | $last_added = $b;
197 | }
198 | elseif($a !== $last_added)
199 | {
200 | $out[] = $a;
201 | $last_added = $a;
202 | }
203 | }
204 | if ($last_added === NULL || ($last_added !== $b && $last_added->compare_to($b) !== 0))
205 | $out[] = $b;
206 | // We're done with these, remove them to allow GC
207 | unset($cidr_addrs[$cidr]);
208 | }
209 | }
210 | return $out;
211 | }
212 |
213 | /**
214 | * Construct an IP\NetworkAddress.
215 | *
216 | * @param \Leth\IPAddress\IP\Address $address The IP Address of the host
217 | * @param mixed|null $cidr The CIDR size of the network
218 | */
219 | protected function __construct(IP\Address $address, mixed $cidr)
220 | {
221 | // Default CIDR equal single host
222 | if ($cidr === NULL) {
223 | $cidr = static::MAX_SUBNET;
224 | }
225 | if ( ! is_int($cidr) || $cidr < 0 || $cidr > static::MAX_SUBNET) {
226 | throw new \InvalidArgumentException("Invalid CIDR '.$cidr'.Invalid type or out of range for class " . get_class($this) . ".");
227 | }
228 |
229 | $this->address = $address;
230 | $this->cidr = $cidr;
231 | }
232 |
233 | public function get_address(): Address
234 | {
235 | return $this->address;
236 | }
237 |
238 | public function get_cidr(): int
239 | {
240 | return $this->cidr;
241 | }
242 |
243 | /**
244 | * Get the NetworkAddress immediately enclosing this one
245 | *
246 | * @return \Leth\IPAddress\IPv4\NetworkAddress|\Leth\IPAddress\IP\NetworkAddress|\Leth\IPAddress\IPv6\NetworkAddress|null
247 | */
248 | public function get_parent(): IPv4\NetworkAddress|\Leth\IPAddress\IP\NetworkAddress|IPv6\NetworkAddress|null
249 | {
250 | if ($this->cidr === 0) {
251 | return null;
252 | }
253 | $parent_cidr = $this->cidr - 1;
254 | $parent_addr = $this->address->bitwise_and(static::generate_subnet_mask($parent_cidr));
255 | return static::factory($parent_addr, $parent_cidr);
256 | }
257 |
258 | /**
259 | * Calculates the first address in this subnet.
260 | *
261 | * @return IP\Address
262 | */
263 | public function get_network_start(): Address
264 | {
265 | return $this->address->bitwise_and($this->get_subnet_mask());
266 | }
267 |
268 | /**
269 | * Calculates the last address in this subnet.
270 | *
271 | * @return IP\Address
272 | */
273 | public function get_network_end(): Address
274 | {
275 | return $this->get_subnet_mask()->bitwise_not()->bitwise_or($this->address);
276 | }
277 |
278 | /**
279 | * Calculates the number of address in this subnet.
280 | *
281 | * @return integer
282 | */
283 | public function get_NetworkAddress_count(): int
284 | {
285 | return 2 ** (static::MAX_SUBNET - $this->cidr);
286 | }
287 |
288 | public function get_address_in_network(int|\Math_BigInteger $offset, bool $from_start = NULL): Address
289 | {
290 | $positive = false;
291 | if (is_int($offset))
292 | {
293 | $positive = ($offset >= 0);
294 | }
295 | elseif ($offset instanceOf \Math_BigInteger)
296 | {
297 | $positive = ($offset->compare(new \Math_BigInteger(0)) >= 0);
298 | }
299 | if ($from_start === NULL)
300 | {
301 | $from_start = $positive;
302 | }
303 | else
304 | {
305 | $from_start = ($from_start === TRUE);
306 | }
307 |
308 | if ($from_start)
309 | {
310 | $point = $this->get_network_start();
311 | }
312 | else
313 | {
314 | $point = $this->get_network_end();
315 | }
316 |
317 | if ( ! $positive)
318 | {
319 | if (is_int($offset))
320 | {
321 | $offset = (int)abs($offset);
322 | }
323 | elseif ($offset instanceOf \Math_BigInteger)
324 | {
325 | $offset = $offset->abs();
326 | }
327 | }
328 |
329 | if ($positive && $from_start) {
330 | return $point->add($offset);
331 | }
332 | else {
333 | return $point->subtract($offset);
334 | }
335 | }
336 |
337 | /**
338 | * Checks whether this is a Network Identifier
339 | *
340 | * @return boolean
341 | */
342 | public function is_network_identifier(): bool
343 | {
344 | return $this->address->compare_to($this->get_network_start()) === 0;
345 | }
346 |
347 | /**
348 | * Get the Network Identifier for the network this address is in.
349 | *
350 | * @return IP\NetworkAddress
351 | */
352 | public function get_network_identifier(): NetworkAddress
353 | {
354 | $classname = get_class($this);
355 | return new $classname($this->get_network_start(), $this->cidr);
356 | }
357 |
358 | /**
359 | * Get the subnet mask for this network
360 | *
361 | * @return IP\Address
362 | */
363 | public function get_subnet_mask(): Address
364 | {
365 | return static::generate_subnet_mask($this->cidr);
366 | }
367 |
368 | /**
369 | * Calculates whether two subnets share any portion of their address space.
370 | *
371 | * @param \Leth\IPAddress\IP\NetworkAddress $other The other subnet to compare to.
372 | *
373 | * @return bool
374 | */
375 | public function shares_subnet_space(IP\NetworkAddress $other): bool
376 | {
377 | $this->check_types($other);
378 |
379 | $first = $this;
380 |
381 | if ($this->cidr > $other->cidr)
382 | {
383 | [$first, $other] = array($other, $first);
384 | }
385 |
386 | return
387 | ($first->get_network_start()->compare_to($other->get_network_start()) <= 0)
388 | &&
389 | ($first->get_network_end() ->compare_to($other->get_network_end() ) >= 0);
390 | }
391 |
392 | /**
393 | * Checks whether this subnet encloses the supplied subnet.
394 | *
395 | * @param \Leth\IPAddress\IP\NetworkAddress $other Subnet to test against.
396 | *
397 | * @return boolean
398 | */
399 | public function encloses_subnet(IP\NetworkAddress $other): bool
400 | {
401 | $this->check_types($other);
402 |
403 | if ($this->cidr > $other->cidr)
404 | return FALSE;
405 |
406 | return $this->shares_subnet_space($other);
407 | }
408 |
409 | /**
410 | * Checks whether the supplied IP fits within this subnet.
411 | *
412 | * @param IP\Address $ip IP to test against.
413 | * @return boolean
414 | */
415 | public function encloses_address(IP\Address $ip)
416 | {
417 | $this->check_IP_version($ip);
418 |
419 | return
420 | ($this->get_network_start()->compare_to($ip) <= 0)
421 | AND
422 | ($this->get_network_end() ->compare_to($ip) >= 0);
423 | }
424 |
425 | /**
426 | * Check that this and the argument are of the same type.
427 | *
428 | * @param IP\NetworkAddress $other The object to check.
429 | * @return void
430 | * @throws \InvalidArgumentException If they are not of the same type.
431 | */
432 | protected function check_types($other)
433 | {
434 | if (get_class($this) != get_class($other))
435 | throw new \InvalidArgumentException('Incompatible types.');
436 | }
437 |
438 | /**
439 | * Check that this and the argument are of the same IP protocol version
440 | *
441 | * @param IP\Address $other
442 | * @return void
443 | * @throws \InvalidArgumentException If they are not of the same type.
444 | */
445 | protected function check_IP_version(IP\Address $other): void
446 | {
447 | if ($other::IP_VERSION !== static::IP_VERSION) {
448 | throw new \InvalidArgumentException("Incompatible types ('" . get_class($this) . "' and '" . get_class($other) . "').");
449 | }
450 | }
451 |
452 | /**
453 | * Compare this instance to another IP\NetworkAddress
454 | *
455 | * @param IP\NetworkAddress $other The instance to compare to
456 | * @return integer
457 | */
458 | public function compare_to(IP\NetworkAddress $other)
459 | {
460 | $cmp = $this->address->compare_to($other->address);
461 |
462 | if ($cmp == 0)
463 | {
464 | $cmp = $this->cidr - $other->cidr;
465 | }
466 |
467 | return $cmp;
468 | }
469 |
470 | /**
471 | * Provides a string representation of this object
472 | *
473 | * @return string
474 | */
475 | public function __toString()
476 | {
477 | return $this->address.'/'.$this->cidr;
478 | }
479 |
480 | /**
481 | * Find a block of a given size within the smallest network address among the blocks given
482 | *
483 | * @param array $blocks An array of network addresses to search in.
484 | * @param integer $block_size The desired network block size
485 | *
486 | * @return array(IP\NetworkAddress found, IP\NetworkAddress within), or array(NULL, NULL) if none found.
487 | */
488 | public static function get_block_in_smallest(array $blocks, int $block_size): array
489 | {
490 | $smallest = NULL;
491 | $smallest_cidr = 0;
492 |
493 | foreach ($blocks as $block)
494 | {
495 | $cidr = $block->get_cidr();
496 | if ($cidr === $block_size)
497 | {
498 | return array($block, $block);
499 | }
500 | elseif ($cidr > $block_size)
501 | {
502 | continue;
503 | }
504 | elseif ($cidr > $smallest_cidr)
505 | {
506 | $smallest = $block;
507 | $smallest_cidr = $block->get_cidr();
508 | }
509 | }
510 |
511 | if ($smallest) {
512 | return [static::factory($smallest, $block_size), $smallest];
513 | } else {
514 | return [null, null];
515 | }
516 | }
517 |
518 | /**
519 | * Find the portion of this network address block that does not overlap with the given blocks.
520 | *
521 | * @param array $excluding An array of network addresses to exclude, each of type IP\NetworkAddress
522 | * @return array
523 | */
524 | public function excluding(array $excluding): array
525 | {
526 | $candidates = array($this);
527 | foreach ($excluding as $exclude)
528 | {
529 | $stack = $candidates;
530 | $candidates = array();
531 |
532 | while ( ! empty($stack))
533 | {
534 | $candidate = array_shift($stack);
535 |
536 | // Null == ok, TRUE == split, FALSE == excluded
537 | $split = NULL;
538 | if ($candidate->shares_subnet_space($exclude))
539 | {
540 | $split = ($candidate->cidr < $exclude->cidr);
541 | }
542 | if ($split === TRUE)
543 | {
544 | $stack = array_merge($candidate->split(), $stack);
545 | }
546 | elseif ($split === NULL)
547 | {
548 | $candidates[] = $candidate;
549 | }
550 | }
551 |
552 | if (empty($candidates))
553 | break;
554 | }
555 | return $candidates;
556 | }
557 |
558 | /**
559 | * Split the network address to create 2^n network addresses.
560 | *
561 | * @param int $times The number of times to split the network address
562 | *
563 | * @return array
564 | */
565 | public function split(int $times = 1): array
566 | {
567 | if (0 === $times)
568 | return array($this);
569 |
570 | $new_cidr = $this->cidr + $times;
571 | $shift = static::MAX_SUBNET - $new_cidr;
572 | if ($shift < 0)
573 | throw new \InvalidArgumentException('Cannot split beyond smallest subnet size');
574 |
575 | $one = new \Math_BigInteger(1);
576 | $offset = $one->bitwise_leftShift($shift);
577 |
578 | $out = array();
579 | $pos = $this->address;
580 | for ($i=0; $i < (2 ** $times); $i++)
581 | {
582 | $out[] = static::factory($pos, $new_cidr);
583 | $pos = $pos->add($offset);
584 | }
585 |
586 | return $out;
587 | }
588 |
589 | /**
590 | * Get iterator for this network
591 | * Implement \IteratorAggregate
592 | *
593 | * @return NetworkAddressIterator
594 | */
595 | #[ReturnTypeWillChange]
596 | public function getIterator(): NetworkAddressIterator
597 | {
598 | return new NetworkAddressIterator($this);
599 | }
600 |
601 | /**
602 | * Get array of addresses in this network
603 | *
604 | * Warning: May use a lot of memory if used with large networks.
605 | * Consider using an iterator and the count() method instead.
606 | * @return array
607 | */
608 | public function toArray()
609 | {
610 | return iterator_to_array($this, false);
611 | }
612 |
613 | /**
614 | * Get count addresses in this network
615 | * Implement \Countable
616 | *
617 | * @return integer
618 | */
619 | public function count(): int
620 | {
621 | return $this->get_NetworkAddress_count();
622 | }
623 | }
624 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IP/NetworkAddressIterator.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IP;
21 | use \Leth\IPAddress\IP;
22 |
23 | class NetworkAddressIterator implements \Iterator
24 | {
25 | /**
26 | * The network of iterator
27 | *
28 | * @var IP\NetworkAddress
29 | */
30 | protected $network;
31 |
32 | /**
33 | * The position of iterator
34 | *
35 | * @var IP\Address
36 | */
37 | protected $position;
38 |
39 | public function __construct(NetworkAddress $network)
40 | {
41 | $this->network = $network;
42 | $this->rewind();
43 | }
44 |
45 | /**
46 | * Set the pointer of iterator to a first network address
47 | * Implement \Iterator
48 | *
49 | * @return void
50 | */
51 | public function rewind(): void
52 | {
53 | $this->position = $this->network->get_network_start();
54 | }
55 |
56 | /**
57 | * Get the value from iterator
58 | * Implement \Iterator
59 | *
60 | * @return IP\Address
61 | */
62 | public function current(): IP\Address
63 | {
64 | return $this->position;
65 | }
66 |
67 | /**
68 | * Get the key from iterator
69 | * Implement \Iterator
70 | *
71 | * @return string
72 | */
73 | public function key(): string
74 | {
75 | return $this->position->__toString();
76 | }
77 |
78 | /**
79 | * Move the pointer of iterator to a next network address
80 | * Implement \Iterator
81 | *
82 | * @return void
83 | */
84 | public function next(): void
85 | {
86 | $this->position = $this->position->add(1);
87 | }
88 |
89 | /**
90 | * Next network address is valid
91 | * Implement \Iterator
92 | *
93 | * @return boolean
94 | */
95 | public function valid(): bool
96 | {
97 | return ($this->position->compare_to($this->network->get_network_end()) <= 0);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IPv4/Address.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IPv4;
21 | use \Leth\IPAddress\IP, \Leth\IPAddress\IPv4, \Leth\IPAddress\IPv6;
22 |
23 | class Address extends IP\Address
24 | {
25 | public const IP_VERSION = 4;
26 | public const MAX_IP = '255.255.255.255';
27 | public const FORMAT_INTEGER = 3;
28 |
29 | public static function factory(IP\Address|int|string|\Math_BigInteger $address): IPv4\Address
30 | {
31 | if ($address instanceof self)
32 | {
33 | return $address;
34 | }
35 | elseif (is_string($address))
36 | {
37 | $tmp = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
38 | if ($tmp === FALSE) {
39 | throw new \InvalidArgumentException("'$address' is not a valid IPv4 Address");
40 | }
41 |
42 | $address = static::_pack(ip2long($address));
43 | }
44 | elseif ($address instanceOf \Math_BigInteger)
45 | {
46 | if ($address->compare(new \Math_BigInteger(pack('N', ip2long(static::MAX_IP)), 256)) > 0) {
47 | throw new \InvalidArgumentException("IP value out of range.");
48 | }
49 |
50 | $address = str_pad($address->toBytes(), 4, chr(0), STR_PAD_LEFT);
51 | }
52 | elseif (is_int($address))
53 | {
54 | $address = static::_pack($address);
55 | }
56 | else
57 | {
58 | throw new \InvalidArgumentException("Unsupported argument type.");
59 | }
60 |
61 | return new IPv4\Address($address);
62 | }
63 |
64 | protected function __construct($address)
65 | {
66 | parent::__construct($address);
67 | }
68 |
69 | protected static function _pack(int $address): string
70 | {
71 | return pack('N', $address);
72 | }
73 |
74 | protected static function _unpack(string $address): int
75 | {
76 | $out = unpack('N', $address);
77 | return $out[1];
78 | }
79 |
80 | public function add($value): Address
81 | {
82 | if ($value instanceof \Math_BigInteger)
83 | {
84 | $value = (int)(string)$value;
85 | }
86 | return new IPv4\Address(static::_pack(static::_unpack($this->address) + $value));
87 | }
88 |
89 | public function subtract($value): Address
90 | {
91 | if ($value instanceof \Math_BigInteger)
92 | {
93 | $value = (int)(string)$value;
94 | }
95 | return new IPv4\Address(static::_pack(static::_unpack($this->address) - $value));
96 | }
97 |
98 | /**
99 | * Calculates the Bitwise & (AND) of a given IP address.
100 | * @param IPv4\Address $other is the ip to be compared against
101 | */
102 | public function bitwise_and(IP\Address $other): Address
103 | {
104 | $this->check_types($other);
105 | return new IPv4\Address($this->address & $other->address);
106 | }
107 |
108 | /**
109 | * Calculates the Bitwise | (OR) of a given IP address.
110 | * @param IP\Address $other is the ip to be compared against
111 | * @return IPv4\Address
112 | */
113 | public function bitwise_or(IP\Address $other): IPv4\Address
114 | {
115 | $this->check_types($other);
116 | return new IPv4\Address($this->address | $other->address);
117 | }
118 |
119 | /**
120 | * Calculates the Bitwise ^ (XOR) of a given IP address.
121 | * @param IP\Address $other is the ip to be compared against
122 | * @return IPv4\Address
123 | */
124 | public function bitwise_xor(IP\Address $other): IPv4\Address
125 | {
126 | $this->check_types($other);
127 | return new IPv4\Address($this->address ^ $other->address);
128 | }
129 |
130 | /**
131 | * Calculates the Bitwise ~ (NOT) of a given IP address.
132 | * @return IPv4\Address
133 | */
134 | public function bitwise_not(): Address
135 | {
136 | return new IPv4\Address(~ $this->address);
137 | }
138 |
139 | /**
140 | * Creates a IPv6 address object representing the 'IPv4-Mapped' IPv6 address of this object
141 | *
142 | * @return IPv6\Address
143 | */
144 | public function as_IPv6_address(): IPv6\Address
145 | {
146 | list( , $address) = unpack('H*', $this->address);
147 | $address = join(':', str_split($address, 4));
148 | $address = '::ffff:'.$address;
149 |
150 | return IPv6\Address::factory($address);
151 | }
152 |
153 | public function compare_to(IP\Address $other): int
154 | {
155 | $this->check_types($other);
156 |
157 | if ($this->address < $other->address) {
158 | return -1;
159 | }
160 | elseif ($this->address > $other->address) {
161 | return 1;
162 | }
163 | else {
164 | return 0;
165 | }
166 | }
167 |
168 | public function format(int $mode): string
169 | {
170 | $address = static::_unpack($this->address);
171 | switch ($mode) {
172 | case static::FORMAT_INTEGER:
173 | return sprintf('%u', $address);
174 | case IP\Address::FORMAT_COMPACT:
175 | return long2ip($address);
176 | case IP\Address::FORMAT_FULL:
177 | $parts = explode('.', long2ip($address));
178 | foreach ($parts as $i => $octet) {
179 | $parts[$i] = str_pad($octet, 3, '0', STR_PAD_LEFT);
180 | }
181 | return implode('.', $parts);
182 | default:
183 | throw new \InvalidArgumentException('Unsupported format mode: '.$mode);
184 | }
185 | }
186 |
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IPv4/NetworkAddress.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IPv4;
21 | use \Leth\IPAddress\IPv4;
22 |
23 | class NetworkAddress extends \Leth\IPAddress\IP\NetworkAddress
24 | {
25 | public const IP_VERSION = 4;
26 | public const MAX_SUBNET = 32;
27 |
28 | public static function generate_subnet_mask(int $cidr): IPv4\Address
29 | {
30 | $mask = 0;
31 | // left shift operates over arch-specific integer sizes,
32 | // so we have to special case 32 bit shifts
33 | if ($cidr > 0)
34 | {
35 | $mask = (~$mask) << (static::MAX_SUBNET - $cidr);
36 | }
37 |
38 | return IPv4\Address::factory(implode('.', unpack('C4', pack('N', $mask))));
39 | }
40 |
41 | /**
42 | * Gets the Global subnet mask for this IP Protocol
43 | * @return IPv4\Address An IP Address representing the mask.
44 | *
45 | * @author Marcus Cobden
46 | */
47 | public static function get_global_netmask(): Address
48 | {
49 | return static::generate_subnet_mask(static::MAX_SUBNET);
50 | }
51 |
52 | /**
53 | * Calculates the Network Address for this address (IPv4) or the first ip of the subnet (IPv6)
54 | *
55 | */
56 | public function get_NetworkAddress(): \Leth\IPAddress\IP\Address
57 | {
58 | return $this->get_network_start();
59 | }
60 |
61 | public function get_network_class(): string
62 | {
63 | if ($this->cidr > 24)
64 | {
65 | return '1/'. (2 ** ($this->cidr - 24)) .' C';
66 | }
67 | elseif ($this->cidr > 16)
68 | {
69 | return (2 ** (24 - $this->cidr)) .' C';
70 |
71 | }
72 | elseif ($this->cidr > 8)
73 | {
74 | return (2 ** (16 - $this->cidr)) .' B';
75 | }
76 | else
77 | {
78 | return (2 ** (8 - $this->cidr)) .' A';
79 | }
80 | }
81 |
82 | /**
83 | * Calculates the Broadcast Address for this address.
84 | *
85 | */
86 | public function get_broadcast_address(): \Leth\IPAddress\IP\Address
87 | {
88 | return $this->get_network_end();
89 | }
90 |
91 | // TODO Check this
92 | // public function as_IPv6\NetworkAddress()
93 | // {
94 | // $address = $this->address->as_IPv6\address();
95 | // $cidr = (IPv6\NetworkAddress::MAX_SUBNET - IPv4\NetworkAddress::MAX_SUBNET) + $this->cidr;
96 | // return new IPv6\NetworkAddress($address, $cidr);
97 | // }
98 | }
99 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IPv6/Address.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IPv6;
21 | use \Leth\IPAddress\IP, \Leth\IPAddress\IPv6, \Leth\IPAddress\IPv4;
22 |
23 | class Address extends IP\Address
24 | {
25 | public const IP_VERSION = 6;
26 | public const FORMAT_ABBREVIATED = 2;
27 | public const FORMAT_MAPPED_IPV4 = 3;
28 | // format mapped v4 if possible, else compact
29 | public const FORMAT_MAY_MAPPED_COMPACT = 4;
30 |
31 | public static function factory(IP\Address|int|string|\Math_BigInteger $address): IPv6\Address
32 | {
33 | if ($address instanceof self)
34 | {
35 | return $address;
36 | }
37 | elseif (is_string($address))
38 | {
39 | if ( ! filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
40 | {
41 | throw new \InvalidArgumentException("'$address' is not a valid IPv6 Address.");
42 | }
43 | $address = inet_pton($address);
44 | }
45 | elseif ($address instanceOf \Math_BigInteger)
46 | {
47 | // Do nothing
48 | }
49 | elseif (is_int($address))
50 | {
51 | $address = new \Math_BigInteger($address);
52 | }
53 | else
54 | {
55 | throw new \InvalidArgumentException('Unsupported argument type.');
56 | }
57 |
58 | return new IPv6\Address($address);
59 | }
60 |
61 | /**
62 | * This makes an IPv6 address fully qualified. It replaces :: with appropriate 0000 blocks, and
63 | * pads out all dropped 0s
64 | *
65 | * IE: 2001:630:d0:: becomes 2001:0630:00d0:0000:0000:0000:0000:0000
66 | *
67 | * @param string $address IPv6 address to be padded
68 | * @return string A fully padded string IPv6 address
69 | */
70 | public static function pad(string $address): string
71 | {
72 | $parts = explode(':', $address);
73 | $count = count($parts);
74 |
75 | $hextets = array();
76 | foreach ($parts as $i => $part)
77 | {
78 | if (isset($part[3])) // not need pad
79 | {
80 | $hextets[] = $part;
81 | }
82 | elseif ($part === '' && 0 < $i && $i < $count - 1) // missing hextets in ::
83 | {
84 | $missing = 8 - $count + 1;
85 | while ($missing--)
86 | {
87 | $hextets[] = '0000';
88 | }
89 | }
90 | else
91 | {
92 | $hextets[] = str_pad($part, 4, '0', STR_PAD_LEFT);
93 | }
94 | }
95 |
96 | return implode(':', $hextets);
97 | }
98 |
99 | protected function __construct(\Math_BigInteger|int|string $address)
100 | {
101 | if ($address instanceOf \Math_BigInteger)
102 | {
103 | parent::__construct(str_pad($address->abs()->toBytes(), 16, chr(0), STR_PAD_LEFT));
104 | }
105 | else
106 | {
107 | parent::__construct($address);
108 | }
109 | }
110 |
111 | public function is_encoded_IPv4_address(): bool
112 | {
113 | return strncmp($this->address, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 12) === 0;
114 | }
115 |
116 | public function as_IPv4_address(): IPv4\Address
117 | {
118 | if(!$this->is_encoded_IPv4_address())
119 | throw new \InvalidArgumentException('Not an IPv4 Address encoded in an IPv6 Address');
120 | [,$hex] = unpack('H*', $this->address);
121 | $parts = array_map('hexdec', array_slice(str_split($hex, 2), 12));
122 | $address = implode('.', $parts);
123 | return IPv4\Address::factory($address);
124 | }
125 |
126 | public function add($value): Address
127 | {
128 | $left = new \Math_BigInteger($this->address, 256);
129 | $right = ($value instanceof \Math_BigInteger) ? $value : new \Math_BigInteger($value);
130 | return new IPv6\Address($left->add($right));
131 | }
132 |
133 | public function subtract($value): IPv6\Address
134 | {
135 | $left = new \Math_BigInteger($this->address, 256);
136 | $right = ($value instanceof \Math_BigInteger) ? $value : new \Math_BigInteger($value);
137 | return new IPv6\Address($left->subtract($right));
138 | }
139 |
140 | /**
141 | * Calculates the Bitwise & (AND) of a given IP address.
142 | * @param IP\Address $other is the ip to be compared against
143 | * @return IPv6\Address
144 | */
145 | public function bitwise_and(IP\Address $other): IPv6\Address
146 | {
147 | return $this->bitwise_operation('&', $other);
148 | }
149 |
150 | /**
151 | * Calculates the Bitwise | (OR) of a given IP address.
152 | * @param IP\Address $other is the ip to be compared against
153 | * @return IPv6\Address
154 | */
155 | public function bitwise_or(IP\Address $other): IPv6\Address
156 | {
157 | return $this->bitwise_operation('|', $other);
158 | }
159 |
160 | /**
161 | * Calculates the Bitwise ^ (XOR) of a given IP address.
162 | * @param IP\Address $other is the ip to be compared against
163 | * @return IPv6\Address
164 | */
165 | public function bitwise_xor(IP\Address $other): IPv6\Address
166 | {
167 | return $this->bitwise_operation('^', $other);
168 | }
169 |
170 | /**
171 | * Calculates the Bitwise ~ (NOT) of a given IP address.
172 | * @return IPv6\Address
173 | */
174 | public function bitwise_not(): IPv6\Address
175 | {
176 | return $this->bitwise_operation('~');
177 | }
178 |
179 | public function bitwise_operation(string $operation, IP\Address $other = NULL): IPv6\Address
180 | {
181 | if ($operation !== '~')
182 | {
183 | $this->check_types($other);
184 | }
185 |
186 | $result = match ($operation) {
187 | '&' => $other->address & $this->address,
188 | '|' => $other->address | $this->address,
189 | '^' => $other->address ^ $this->address,
190 | '~' => ~$this->address,
191 | default => throw new \InvalidArgumentException('Unknown Operation type \'' . $operation . '\'.'),
192 | };
193 |
194 | return new IPv6\Address($result);
195 | }
196 |
197 | public function compare_to(IP\Address $other): int
198 | {
199 | $this->check_types($other);
200 |
201 | if ($this->address < $other->address) {
202 | return -1;
203 | }
204 | elseif ($this->address > $other->address) {
205 | return 1;
206 | }
207 | else {
208 | return 0;
209 | }
210 | }
211 |
212 | public function format(int $mode): string
213 | {
214 | [, $hex] = unpack('H*', $this->address);
215 | $parts = str_split($hex, 4);
216 |
217 | if ($mode === self::FORMAT_MAY_MAPPED_COMPACT) {
218 | if ($this->is_encoded_IPv4_address()) {
219 | $mode = self::FORMAT_MAPPED_IPV4;
220 | } else {
221 | $mode = IP\Address::FORMAT_COMPACT;
222 | }
223 | }
224 |
225 | switch ($mode) {
226 | case IP\Address::FORMAT_FULL:
227 | // Do nothing
228 | break;
229 |
230 | case IPv6\Address::FORMAT_ABBREVIATED:
231 | foreach ($parts as $i => $quad)
232 | {
233 | $parts[$i] = ($quad === '0000') ? '0' : ltrim($quad, '0');
234 | }
235 | break;
236 |
237 | case IPv6\Address::FORMAT_MAPPED_IPV4:
238 | list($a, $b) = str_split($parts[6], 2);
239 | list($c, $d) = str_split($parts[7], 2);
240 | return '::ffff:' . implode('.', array(hexdec($a), hexdec($b), hexdec($c), hexdec($d)));
241 |
242 | case IP\Address::FORMAT_COMPACT:
243 | $best_pos = $zeros_pos = FALSE;
244 | $best_count = 1;
245 | $zeros_count = 0;
246 | foreach ($parts as $i => $quad)
247 | {
248 | $parts[$i] = ($quad === '0000') ? '0' : ltrim($quad, '0');
249 |
250 | if ($quad === '0000')
251 | {
252 | if ($zeros_pos === FALSE)
253 | {
254 | $zeros_pos = $i;
255 | }
256 | $zeros_count++;
257 |
258 | if ($zeros_count > $best_count)
259 | {
260 | $best_count = $zeros_count;
261 | $best_pos = $zeros_pos;
262 | }
263 | }
264 | else
265 | {
266 | $zeros_count = 0;
267 | $zeros_pos = FALSE;
268 |
269 | $parts[$i] = ltrim($quad, '0');
270 | }
271 | }
272 |
273 |
274 | if ($best_pos !== FALSE)
275 | {
276 | $insert = array(NULL);
277 |
278 | if ($best_pos == 0 OR $best_pos + $best_count == 8)
279 | {
280 | $insert[] = NULL;
281 | if ($best_count == count($parts))
282 | {
283 | $best_count--;
284 | }
285 | }
286 | array_splice($parts, $best_pos, $best_count, $insert);
287 | }
288 |
289 | break;
290 |
291 | default:
292 | throw new \InvalidArgumentException('Unsupported format mode: '.$mode);
293 | }
294 |
295 | return implode(':', $parts);
296 | }
297 |
298 | public function __toString()
299 | {
300 | return $this->format(IPv6\Address::FORMAT_MAY_MAPPED_COMPACT);
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/classes/Leth/IPAddress/IPv6/NetworkAddress.php:
--------------------------------------------------------------------------------
1 | .
19 | */
20 | namespace Leth\IPAddress\IPv6;
21 | use \Leth\IPAddress\IPv6;
22 |
23 | class NetworkAddress extends \Leth\IPAddress\IP\NetworkAddress
24 | {
25 | public const IP_VERSION = 6;
26 | public const MAX_SUBNET = 128;
27 |
28 | public static function generate_subnet_mask(int $cidr): IPv6\Address
29 | {
30 | $masks = array();
31 | for ($i=1; $i <= 4; $i++)
32 | {
33 | // left shift operates over arch-specific integer sizes,
34 | // so we have to special case 32 bit shifts
35 | $shift = min(32, max(0, 32*$i - $cidr));
36 | if ($shift === 32)
37 | {
38 | $masks[] = 0;
39 | }
40 | else
41 | {
42 | $masks[] = (~0) << $shift;
43 | }
44 | }
45 | $result = unpack('H*', pack('N4', $masks[0], $masks[1], $masks[2], $masks[3]));
46 | return IPv6\Address::factory(implode(':', str_split($result[1], 4)));
47 | }
48 |
49 | /**
50 | * Gets the Global subnet mask for this IP Protocol
51 | *
52 | * @return Address An IP Address representing the mask.
53 | * @author Marcus Cobden
54 | */
55 | public static function get_global_netmask(): Address
56 | {
57 | return static::generate_subnet_mask(static::MAX_SUBNET);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "leth/ip-address",
3 | "description": "IPv4 and IPv6 address and subnet classes with awesome utility functions.",
4 | "license": "LGPL-3.0-or-later",
5 | "support": {
6 | "source": "https://github.com/leth/PHP-IPAddress",
7 | "issues": "https://github.com/leth/PHP-IPAddress/issues"
8 | },
9 | "require": {
10 | "php": ">=8.1",
11 | "pear/math_biginteger": "1.0.3"
12 | },
13 | "require-dev": {
14 | "phpunit/phpunit": "^9",
15 | "phpstan/phpstan": "^1",
16 | "phpstan/phpstan-phpunit": "^1",
17 | "phpstan/phpstan-deprecation-rules": "^1",
18 | "phpstan/phpstan-strict-rules": "^1"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "Leth\\IPAddress\\": "classes/Leth/IPAddress"
23 | }
24 | },
25 | "scripts": {
26 | "test": "vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-text",
27 | "phpstan": "phpstan analyse --memory-limit=2G --error-format=table"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lgpl-3.0.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 6
3 | paths:
4 | - classes
5 | - tests
6 | fileExtensions:
7 | - php
8 | reportUnmatchedIgnoredErrors: false
9 | checkMissingIterableValueType: false
10 | checkGenericClassInNonGenericObjectType: false
11 | parallel:
12 | processTimeout: 300.0
13 | jobSize: 20
14 | maximumNumberOfProcesses: 32
15 | minimumNumberOfJobsPerProcess: 4
16 | ignoreErrors:
17 | - '#Parameter \#1 \$x of class Math_BigInteger constructor expects optional, .* given#'
18 | includes:
19 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
19 |
20 |
21 | ./classes
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/tests/IPAddressTest.php:
--------------------------------------------------------------------------------
1 | assertNotNull($instance);
47 | $this->assertEquals($full, $instance->format(IP\Address::FORMAT_FULL));
48 | $this->assertEquals($compact, $instance->format(IP\Address::FORMAT_COMPACT));
49 | $this->assertEquals($expected_class, get_class($instance));
50 | }
51 | }
52 |
53 | public function providerFactoryException(): array
54 | {
55 | return array(
56 | array('cake'),
57 | array('12345'),
58 | array('-12345'),
59 | );
60 | }
61 |
62 | /**
63 | *
64 | * @dataProvider providerFactoryException
65 | */
66 | public function testFactoryException(mixed $input): void
67 | {
68 | $this->expectException(\InvalidArgumentException::class);
69 | IP\Address::factory($input);
70 | }
71 |
72 | public function providerCompare(): array
73 | {
74 | return array(
75 | array('127.0.0.1', '127.0.0.1', 0),
76 | array('127.0.0.0', '127.0.0.1', -1),
77 | array('127.0.0.0', '127.0.0.2', -1),
78 | array('127.0.0.1', '127.0.0.2', -1),
79 | array('127.0.0.2', '127.0.0.1', 1),
80 | array('10.0.0.1', '127.0.0.2', -1)
81 | );
82 | }
83 |
84 | /**
85 | * @dataProvider providerCompare
86 | */
87 | public function testCompare(string $a, string $b, int $expected): void
88 | {
89 | $result = IP\Address::compare(IP\Address::factory($a), IP\Address::factory($b));
90 |
91 | // Division is to ensure things are either -1, 0 or 1. abs() is to preseve sign.
92 | $this->assertEquals($expected, $result === 0 ? 0: $result / abs($result));
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/IPNetworkAddressTest.php:
--------------------------------------------------------------------------------
1 | check_IP_version($other->address);
39 | }
40 | }
41 |
42 | class IPv6_NetworkAddress_Tester extends IPv6\NetworkAddress
43 | {
44 | public static function factory(NetworkAddress|Address|string $address, int|string|null $cidr = NULL): IPv6_NetworkAddress_Tester
45 | {
46 | $ip = IPv6\Address::factory($address);
47 | return new IPv6_NetworkAddress_Tester($ip, $cidr);
48 | }
49 | public function test_check_IP_version(NetworkAddress $other): void
50 | {
51 | $this->check_IP_version($other->address);
52 | }
53 | }
54 |
55 | /**
56 | * Tests for the IP\NetworkAddress Class
57 | *
58 | * @package default
59 | * @author Marcus Cobden
60 | */
61 | class IP_NetworkAddress_Test extends TestCase
62 | {
63 | public function providerFactory(): array
64 | {
65 | return array(
66 | array('127.0.0.1/16', NULL, '127.0.0.1', 16, '127.0.0.0'),
67 | array('127.0.0.1', 16, '127.0.0.1', 16, '127.0.0.0'),
68 | array('127.0.0.1/32', NULL, '127.0.0.1', 32, '127.0.0.1'),
69 | array('127.0.0.1', 32, '127.0.0.1', 32, '127.0.0.1'),
70 | array(IP\NetworkAddress::factory('127.0.0.1/16'), NULL, '127.0.0.1', 16, '127.0.0.0'),
71 | array(IP\NetworkAddress::factory('127.0.0.1/16'), 10, '127.0.0.1', 10, '127.0.0.0'),
72 |
73 | array('::1/16', NULL, '::1', 16, '::0'),
74 | array('::1', 16, '::1', 16, '::0'),
75 | array('::1/128', NULL, '::1', 128, '::1'),
76 | array('::1', 128, '::1', 128, '::1'),
77 |
78 | );
79 | }
80 |
81 | /**
82 | * @dataProvider providerFactory
83 | */
84 | public function testFactory(string|IP\NetworkAddress $address, string|int|null $cidr, string $expected_address, int $expected_cidr, string $expected_subnet): void
85 | {
86 | $ip = IP\NetworkAddress::factory($address, $cidr);
87 |
88 | $this->assertEquals($expected_cidr, $ip->get_cidr());
89 | $this->assertEquals($expected_address, (string) $ip->get_address());
90 | $this->assertEquals($expected_subnet, (string) $ip->get_network_start());
91 | }
92 |
93 |
94 | public function providerFactoryThrowsException(): array
95 | {
96 | return array(
97 | array(new IP_Address_Tester(), 1),
98 | array(new IP_Address_Tester(), 3)
99 | );
100 | }
101 |
102 | /**
103 | * @dataProvider providerFactoryThrowsException
104 | *
105 | */
106 | public function testFactoryThrowsException(IP_Address_Tester $address, int $cidr): void
107 | {
108 | $this->expectException(\InvalidArgumentException::class);
109 | IP\NetworkAddress::factory($address, $cidr);
110 | }
111 |
112 | public function provideFactoryParseCIDR(): array
113 | {
114 | return array(
115 | array('127.0.0.1/16', 24, 24),
116 | array('127.0.0.1', NULL, 32),
117 | array('127.0.0.1/24', NULL, 24),
118 | array('::1', NULL, 128),
119 | array('::1/58', 64, 64),
120 | array('::1/58', NULL, 58),
121 | );
122 | }
123 |
124 | /**
125 | * @dataProvider provideFactoryParseCIDR
126 | */
127 | public function testParseCIDR(string $address, string|int|null $cidr, int $expected): void
128 | {
129 | $network = IP\NetworkAddress::factory($address, $cidr);
130 | $this->assertEquals($expected, $network->get_cidr());
131 | }
132 |
133 | public function providerUnimplementedException(): array
134 | {
135 | return array(
136 | #array('IP_NetworkAddress_Tester', 'generate_subnet_mask'),
137 | array('IP_NetworkAddress_Tester', 'get_global_netmask'),
138 | );
139 | }
140 |
141 | /**
142 | *
143 | * @dataProvider providerUnimplementedException
144 | */
145 | public function testUnimplementedException(string $class, string $method): void
146 | {
147 | $this->expectException(\LogicException::class);
148 | $class::$method(NULL);
149 | }
150 |
151 | public function providerCompare(): array
152 | {
153 | $data = array(
154 | array('0.0.0.0/16', '0.0.0.0/16', 0),
155 | array('0.0.0.0/16', '0.0.0.1/16', -1),
156 | array('0.0.0.1/16', '0.0.0.0/16', 1),
157 | array('127.0.0.1/16' , '127.0.0.1/16', 0),
158 | array('127.0.10.1/16', '127.0.2.1/16', 1),
159 | array('127.0.2.1/16' , '127.0.10.1/16', -1),
160 | // TODO add more addresses and v6 addresses
161 | );
162 | foreach ($data as &$d)
163 | {
164 | $d[0] = IP\NetworkAddress::factory($d[0]);
165 | $d[1] = IP\NetworkAddress::factory($d[1]);
166 | }
167 |
168 | return $data;
169 | }
170 |
171 | /**
172 | * @dataProvider providerCompare
173 | */
174 | public function testCompare(IP\NetworkAddress $left, IP\NetworkAddress $right, int $expected): void
175 | {
176 | $cmp = IP\NetworkAddress::compare($left, $right);
177 |
178 | if ($cmp !== 0) {
179 | $cmp /= abs($cmp);
180 | }
181 |
182 | $this->assertEquals($expected, $cmp);
183 | }
184 |
185 | public function providerAddressInNetwork(): array
186 | {
187 | return array(
188 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 0, NULL, '192.168.1.0'),
189 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 1, NULL, '192.168.1.1'),
190 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 2, NULL, '192.168.1.2'),
191 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 0, FALSE, '192.168.1.255'),
192 | array(IP\NetworkAddress::factory('192.168.1.1/24'), -1, NULL, '192.168.1.254'),
193 | array(IP\NetworkAddress::factory('192.168.1.1/24'), -2, NULL, '192.168.1.253'),
194 | array(IP\NetworkAddress::factory('192.168.1.1/24'), -3, NULL, '192.168.1.252'),
195 |
196 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 0, NULL, '192.168.1.0'),
197 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 1, NULL, '192.168.1.1'),
198 | array(IP\NetworkAddress::factory('192.168.1.1/24'), 0, FALSE, '192.168.1.255'),
199 | array(IP\NetworkAddress::factory('192.168.1.1/24'), -1, NULL, '192.168.1.254'),
200 | array(IP\NetworkAddress::factory('192.168.1.1/24'), -2, NULL, '192.168.1.253'),
201 |
202 | array(IP\NetworkAddress::factory('10.13.1.254/24'), 0, NULL, '10.13.1.0'),
203 | array(IP\NetworkAddress::factory('10.13.1.254/24'), 1, NULL, '10.13.1.1'),
204 | array(IP\NetworkAddress::factory('10.13.1.254/24'), 0, FALSE, '10.13.1.255'),
205 | array(IP\NetworkAddress::factory('10.13.1.254/24'), -1, NULL, '10.13.1.254'),
206 |
207 | array(IP\NetworkAddress::factory('10.13.1.254/24'), new \Math_BigInteger( 0), NULL, '10.13.1.0'),
208 | array(IP\NetworkAddress::factory('10.13.1.254/24'), new \Math_BigInteger( 1), NULL, '10.13.1.1'),
209 | array(IP\NetworkAddress::factory('10.13.1.254/24'), new \Math_BigInteger( 0), FALSE, '10.13.1.255'),
210 | array(IP\NetworkAddress::factory('10.13.1.254/24'), new \Math_BigInteger(-1), NULL, '10.13.1.254'),
211 | );
212 | }
213 |
214 | /**
215 | * @dataProvider providerAddressInNetwork
216 | */
217 | public function testAddressInNetwork(IP\NetworkAddress $network, int|Math_BigInteger $index, ?bool $from_start, string $expected): void
218 | {
219 | $address = $network->get_address_in_network($index, $from_start);
220 | $this->assertEquals($expected, (string) $address);
221 | }
222 |
223 | public function providerCheck_IP_version(): array
224 | {
225 | return array(
226 | array(
227 | IPv4_NetworkAddress_Tester::factory('10.1.0.0', 24),
228 | IPv4_NetworkAddress_Tester::factory('10.2.0.0', 24),
229 | IPv6_NetworkAddress_Tester::factory('::1', 24),
230 | IPv6_NetworkAddress_Tester::factory('1::1', 24)
231 | )
232 | );
233 | }
234 |
235 | public function providerCheck_IP_version_fail(): array
236 | {
237 | [[$a4, $b4, $a6, $b6]] = $this->providerCheck_IP_version();
238 | return array(
239 | array($a4, $a6),
240 | array($a4, $b6),
241 | array($a6, $a4),
242 | array($a6, $b4),
243 |
244 | array($b4, $a6),
245 | array($b4, $b6),
246 | array($b6, $a4),
247 | array($b6, $b4),
248 | );
249 | }
250 |
251 | /**
252 | * @dataProvider providerCheck_IP_version_fail
253 | */
254 | public function test_check_IP_version_fail(IPv4_NetworkAddress_Tester|IPv6_NetworkAddress_Tester $left, IPv4_NetworkAddress_Tester|IPv6_NetworkAddress_Tester $right): void
255 | {
256 | try
257 | {
258 | $left->test_check_IP_version($right);
259 | $this->fail('An expected exception was not raised.');
260 | }
261 | catch (\InvalidArgumentException $e) {
262 | // We expect this
263 | $this->assertTrue(true);
264 | }
265 | catch (\PHPUnit\Framework\AssertionFailedError $e)
266 | {
267 | // We expect this
268 | $this->assertTrue(true);
269 | }
270 | catch (Exception $e) {
271 | $this->fail('An unexpected exception was raised.' . $e->getMessage());
272 | }
273 | }
274 |
275 | /**
276 | * @dataProvider providerCheck_IP_version
277 | */
278 | public function test_check_IP_version(IPv4_NetworkAddress_Tester $a4, IPv4_NetworkAddress_Tester $b4, IPv6_NetworkAddress_Tester $a6, IPv6_NetworkAddress_Tester $b6): void
279 | {
280 | try
281 | {
282 | $a4->test_check_IP_version($b4);
283 | $b4->test_check_IP_version($a4);
284 |
285 | $a6->test_check_IP_version($b6);
286 | $b6->test_check_IP_version($a6);
287 | }
288 | catch (Exception $e) {
289 | $this->fail('An unexpected exception was raised.' . $e->getMessage());
290 | }
291 | $this->assertTrue(true);
292 | }
293 |
294 | public function providerSubnets(): array
295 | {
296 | $data = array(
297 | array('2000::/3','2001:630:d0:f104::80a/128', true, true),
298 | array('2000::/3','2001:630:d0:f104::80a/96', true, true),
299 | array('2000::/3','2001:630:d0:f104::80a/48', true, true),
300 |
301 | array('2001:630:d0:f104::80a/96', '2000::/3', true, false),
302 | array('2001:630:d0:f104::80a/48', '2000::/3', true, false),
303 |
304 | array('2000::/3','4000::/3', false, false),
305 | array('2000::/3','1000::/3', false, false),
306 | );
307 |
308 | foreach ($data as &$d)
309 | {
310 | $d[0] = IP\NetworkAddress::factory($d[0]);
311 | $d[1] = IP\NetworkAddress::factory($d[1]);
312 | }
313 |
314 | return $data;
315 | }
316 |
317 | /**
318 | * @dataProvider providerSubnets
319 | */
320 | public function testSubnets(IP\NetworkAddress $sub1, IP\NetworkAddress $sub2, bool $shares, bool $encloses): void
321 | {
322 | $this->assertEquals($shares, $sub1->shares_subnet_space($sub2));
323 | $this->assertEquals($encloses, $sub1->encloses_subnet($sub2));
324 | }
325 |
326 | public function providerEnclosesAddress(): array
327 | {
328 | $data = array(
329 | array('2000::/3','2001:630:d0:f104::80a', true),
330 | array('2000::/3','2001:630:d0:f104::80a', true),
331 | array('2000::/3','2001:630:d0:f104::80a', true),
332 |
333 | array('2001:630:d0:f104::80a/96', '2000::', false),
334 | array('2001:630:d0:f104::80a/48', '2000::', false),
335 |
336 | array('2000::/3','4000::', false),
337 | array('2000::/3','1000::', false),
338 | );
339 |
340 | foreach ($data as &$d)
341 | {
342 | $d[0] = IP\NetworkAddress::factory($d[0]);
343 | $d[1] = IP\Address::factory($d[1]);
344 | }
345 |
346 | return $data;
347 | }
348 |
349 | /**
350 | * @dataProvider providerEnclosesAddress
351 | */
352 | public function testEnclosesAddress(IP\NetworkAddress $subnet, IP\Address $address, bool $expected): void
353 | {
354 | $this->assertEquals($expected, $subnet->encloses_address($address));
355 | }
356 |
357 | public function provideNetworkIdentifiers(): array
358 | {
359 | $data = array(
360 | array('2000::/3', true),
361 | array('2000::1/3', false),
362 |
363 | array('2000::/3', true),
364 | array('2000::1/3', false),
365 | );
366 |
367 | foreach ($data as &$d)
368 | {
369 | $d[0] = IP\NetworkAddress::factory($d[0]);
370 | }
371 | return $data;
372 | }
373 |
374 | /**
375 | * @dataProvider provideNetworkIdentifiers
376 | */
377 | public function testNetworkIdentifiers(IP\NetworkAddress $subnet, bool $expected): void
378 | {
379 | $this->assertEquals($expected, $subnet->is_network_identifier());
380 | $this->assertTrue($subnet->get_network_identifier()->is_network_identifier());
381 | }
382 |
383 | public function test__toString(): void
384 | {
385 | $ip = '192.128.1.1/24';
386 | $this->assertEquals($ip, (string) IP\NetworkAddress::factory($ip));
387 |
388 | $ip = '::1/24';
389 | $this->assertEquals($ip, (string) IP\NetworkAddress::factory($ip));
390 | }
391 |
392 | public function providerExcluding(): array
393 | {
394 | $data = array(
395 | array('192.168.0.0/24',
396 | array(),
397 | array('192.168.0.0/24')),
398 | array('192.168.0.0/24',
399 | array('192.168.0.0/25'),
400 | array('192.168.0.128/25')),
401 | array('192.168.0.0/24',
402 | array('192.168.0.64/26', '192.168.0.128/26'),
403 | array('192.168.0.0/26', '192.168.0.192/26')),
404 | array('192.168.0.0/24',
405 | array('192.168.0.0/26'),
406 | array('192.168.0.64/26', '192.168.0.128/25')),
407 | array('192.168.0.0/24',
408 | array('192.168.0.0/27'),
409 | array('192.168.0.32/27', '192.168.0.64/26', '192.168.0.128/25')),
410 | // Test out of range exclusions
411 | array('192.168.0.0/24',
412 | array('10.0.0.0/24'),
413 | array('192.168.0.0/24')),
414 | array('192.168.0.0/24',
415 | array('10.0.0.0/24', '192.168.0.0/25'),
416 | array('192.168.0.128/25')),
417 | // Test an encompassing subnet
418 | array('192.168.0.0/24',
419 | array('192.168.0.0/23'),
420 | array()),
421 | );
422 | foreach ($data as &$d)
423 | {
424 | $d[0] = IP\NetworkAddress::factory($d[0]);
425 | for ($i=1, $iMax = count($d); $i < $iMax; $i++)
426 | {
427 | foreach ($d[$i] as &$e)
428 | {
429 | $e = IP\NetworkAddress::factory($e);
430 | }
431 | }
432 | }
433 | return $data;
434 | }
435 |
436 | /**
437 | * @dataProvider providerExcluding
438 | */
439 | public function testExcluding(IP\NetworkAddress $block, array $excluded, array $expected): void
440 | {
441 | $this->assertEquals($expected, $block->excluding($excluded));
442 | }
443 |
444 | public function provideMerge(): array
445 | {
446 | $data = array(
447 | // Simple merge
448 | array(
449 | array('0.0.0.0/32', '0.0.0.1/32'),
450 | array('0.0.0.0/31'),
451 | ),
452 | // No merge
453 | array(
454 | array('0.0.0.1/32'),
455 | array('0.0.0.1/32'),
456 | ),
457 | array(
458 | array('0.0.0.0/32', '0.0.0.2/32'),
459 | array('0.0.0.0/32', '0.0.0.2/32'),
460 | ),
461 | // Duplicate entries
462 | array(
463 | array('0.0.0.0/32', '0.0.0.1/32', '0.0.0.1/32'),
464 | array('0.0.0.0/31'),
465 | ),
466 | array(
467 | array('0.0.0.0/32', '0.0.0.0/32', '0.0.0.1/32'),
468 | array('0.0.0.0/31'),
469 | ),
470 | array(
471 | array('0.0.0.0/32', '0.0.0.0/32', '0.0.0.1/32', '0.0.0.1/32'),
472 | array('0.0.0.0/31'),
473 | ),
474 | // Single merge with remainder
475 | array(
476 | array('0.0.0.0/32', '0.0.0.1/32', '0.0.0.2/32'),
477 | array('0.0.0.2/32', '0.0.0.0/31'),
478 | ),
479 | // Double merge
480 | array(
481 | array('0.0.0.0/32', '0.0.0.1/32', '0.0.0.2/31'),
482 | array('0.0.0.0/30'),
483 | ),
484 | // Non-network identifier
485 | array(
486 | array('0.0.0.0/31', '0.0.0.3/31'),
487 | array('0.0.0.0/30'),
488 | ),
489 | // IPv6 merges
490 | array(
491 | array('::0/128', '::1/128'),
492 | array('::0/127'),
493 | ),
494 | array(
495 | array('::0/128', '::1/128', '::2/127'),
496 | array('::0/126'),
497 | ),
498 | // Mixed subnets
499 | array(
500 | array('0.0.0.0/32', '0.0.0.1/32', '::0/128', '::1/128'),
501 | array('0.0.0.0/31', '::0/127'),
502 | ),
503 | // Merge with duplicate resultant entry
504 | array(
505 | array('0.0.0.0/22', '0.0.0.0/24', '0.0.1.0/24', '0.0.2.0/24', '0.0.3.0/24'),
506 | array('0.0.0.0/22'),
507 | ),
508 | );
509 |
510 | foreach ($data as &$x)
511 | {
512 | foreach ($x as &$y)
513 | {
514 | foreach ($y as &$addr)
515 | {
516 | $addr = IP\NetworkAddress::factory($addr);
517 | }
518 | }
519 | }
520 | return $data;
521 | }
522 |
523 | /**
524 | * @dataProvider provideMerge
525 | */
526 | public function testMerge(array $net_addrs, array $expected): void
527 | {
528 | $this->assertEquals($expected, IP\NetworkAddress::merge($net_addrs));
529 | }
530 | }
531 |
--------------------------------------------------------------------------------
/tests/IPv4AddressTest.php:
--------------------------------------------------------------------------------
1 | assertNotNull($instance);
49 | $this->assertEquals($expected, (string) $instance);
50 | }
51 |
52 | public function providerFactoryException(): array
53 | {
54 | return array(
55 | array('256.0.0.1'),
56 | array('127.-1.0.1'),
57 | array('127.128.256.1'),
58 | array(new \Math_BigInteger('99999999999999999')),
59 | #array(123.45), throws TypeError
60 | #array(-123.45), throws TypeError
61 | array('cake'),
62 | array('12345'),
63 | array('-12345'),
64 | array('0000:0000:0000:ffff:0127:0000:0000:0001'),
65 | );
66 | }
67 |
68 | public function testFormatInteger(): void
69 | {
70 | $ip = IPv4\Address::factory('127.0.0.1');
71 | $this->assertEquals(2130706433, $ip->format(IPv4\Address::FORMAT_INTEGER));
72 | }
73 |
74 | public function providerFormatException(): array
75 | {
76 | $bad_mode = -1;
77 | $data = $this->providerFactory();
78 | foreach ($data as $i => $entry) {
79 | $data[$i] = array($entry[0], $bad_mode);
80 | }
81 |
82 | return $data;
83 | }
84 |
85 | /**
86 | *
87 | * @dataProvider providerFormatException
88 | */
89 | public function testFormatException(mixed $input, mixed $mode): void
90 | {
91 | $this->expectException(InvalidArgumentException::class);
92 | $instance = IPv4\Address::factory($input);
93 | echo $instance->format($mode);
94 | }
95 |
96 | /**
97 | *
98 | * @dataProvider providerFactoryException
99 | */
100 | public function testFactoryException(mixed $input): void
101 | {
102 | $this->expectException(InvalidArgumentException::class);
103 | IPv4\Address::factory($input);
104 | }
105 |
106 | public function providerBitwise(): array
107 | {
108 | return array(
109 | // OP1 OP2 AND OR XOR NOT
110 | array('0.0.0.1', '0.0.0.1', '0.0.0.1', '0.0.0.1', '0.0.0.0', '255.255.255.254'),
111 | array('0.0.0.0', '0.0.0.1', '0.0.0.0', '0.0.0.1', '0.0.0.1', '255.255.255.255'),
112 | array('0.0.0.1', '0.0.0.0', '0.0.0.0', '0.0.0.1', '0.0.0.1', '255.255.255.254'),
113 | array('0.0.0.0', '0.0.0.0', '0.0.0.0', '0.0.0.0', '0.0.0.0', '255.255.255.255'),
114 | );
115 | }
116 |
117 | /**
118 | * @dataProvider providerBitwise
119 | */
120 | public function testBitwise(string $ip1, string $ip2, string $ex_and, string $ex_or, string $ex_xor, string $ex_not): void
121 | {
122 | $ip1 = IPv4\Address::factory($ip1);
123 | $ip2 = IPv4\Address::factory($ip2);
124 |
125 | $this->assertEquals($ex_and, (string) $ip1->bitwise_and($ip2));
126 | $this->assertEquals($ex_or , (string) $ip1->bitwise_or($ip2));
127 | $this->assertEquals($ex_xor, (string) $ip1->bitwise_xor($ip2));
128 | $this->assertEquals($ex_not, (string) $ip1->bitwise_not());
129 | }
130 |
131 | // TODO Check this
132 | // public function providerAsIPv6Address()
133 | // {
134 | // return array(
135 | // array('127.0.0.1', '0000:0000:0000:ffff:0127:0000:0000:0001'),
136 | // );
137 | // }
138 | //
139 | // /**
140 | // * @dataProvider providerAsIPv6Address
141 | // */
142 | // public function testAsIPv6Address($v4, $v6)
143 | // {
144 | // $ip = IPv4\Address::factory($v4);
145 | //
146 | // $this->assertEquals($v6, (string) $ip->asIPv6Address());
147 | // }
148 |
149 | public function providerAddSubtract(): array
150 | {
151 | $data = array(
152 | array('0.0.0.0' , 0, '0.0.0.0'),
153 | array('0.0.0.0' , 1, '0.0.0.1'),
154 | array('0.0.0.1' , 0, '0.0.0.1'),
155 | array('0.0.0.1' , 1, '0.0.0.2'),
156 | array('0.0.0.10' , 1, '0.0.0.11'),
157 | array('0.0.0.255', 1, '0.0.1.0'),
158 | array('0.0.255.0', 257, '0.1.0.1'),
159 | array('255.255.0.0' , 0, '255.255.0.0'),
160 | array('255.255.0.0' , 1, '255.255.0.1'),
161 | array('255.255.0.1' , 0, '255.255.0.1'),
162 | array('255.255.0.1' , 1, '255.255.0.2'),
163 | array('255.255.0.10' , 1, '255.255.0.11'),
164 | array('255.255.0.255', 1, '255.255.1.0'),
165 | array('255.0.255.0', 257, '255.1.0.1'),
166 | array('192.168.0.0', 4, '192.168.0.4'),
167 | );
168 |
169 | for ($i=0, $iMax = count($data); $i < $iMax; $i++) {
170 | $data[$i][0] = IPv4\Address::factory($data[$i][0]);
171 | $data[$i][2] = IPv4\Address::factory($data[$i][2]);
172 | }
173 |
174 | return $data;
175 | }
176 |
177 | /**
178 | * @dataProvider providerAddSubtract
179 | */
180 | public function testAddSubtract(IPv4\Address $left, int $right, IPv4\Address $expected): void
181 | {
182 | $result = $left->add($right);
183 | $this->assertEquals(0, $result->compare_to($expected));
184 | $result = $result->subtract($right);
185 | $this->assertEquals(0, $result->compare_to($left));
186 | }
187 |
188 | public function providerAsIPv6Address(): array
189 | {
190 | $data = array(
191 | array('0.0.0.0' , '::ffff:0:0' ),
192 | array('0.0.0.1' , '::ffff:0:1' ),
193 | array('0.0.0.255', '::ffff:0:ff' ),
194 | array('0.0.255.0', '::ffff:0:ff00'),
195 | array('0.255.0.0', '::ffff:ff:0' ),
196 | array('255.0.0.0', '::ffff:ff00:0'),
197 | );
198 |
199 | foreach ($data as $i => $entry) {
200 | $data[$i] = array(
201 | IPv4\Address::factory($entry[0]),
202 | IPv6\Address::factory($entry[1]));
203 | }
204 |
205 | return $data;
206 | }
207 |
208 | /**
209 | * @dataProvider providerAsIPv6Address
210 | */
211 | public function testAsIPv6Address(IPv4\Address $input, IPv6\Address $expected_equal): void
212 | {
213 | $converted = $input->as_IPv6_address();
214 |
215 | $this->assertInstanceOf(Address::class, $converted);
216 | $this->assertEquals(0, $converted->compare_to($expected_equal));
217 | }
218 |
219 | public function testGetOctet(): void
220 | {
221 | $ip = IPv4\Address::factory('10.250.30.40');
222 | $this->assertEquals(10, $ip->get_octet(-4));
223 | $this->assertEquals(250, $ip->get_octet(-3));
224 | $this->assertEquals(30, $ip->get_octet(-2));
225 | $this->assertEquals(40, $ip->get_octet(-1));
226 | $this->assertEquals(10, $ip->get_octet(0));
227 | $this->assertEquals(250, $ip->get_octet(1));
228 | $this->assertEquals(30, $ip->get_octet(2));
229 | $this->assertEquals(40, $ip->get_octet(3));
230 |
231 | $this->assertNull($ip->get_octet(4));
232 | }
233 |
234 | public function testArrayAccess(): void
235 | {
236 | $ip = IPv4\Address::factory('10.250.30.40');
237 | $this->assertEquals(10, $ip[-4]);
238 | $this->assertEquals(250, $ip[1]);
239 | $this->assertNotEmpty($ip[1]);
240 |
241 | $this->assertNull($ip[4]);
242 | $this->assertFalse(isset($ip[4]));
243 | }
244 |
245 | public function testArrayAccessSet(): void
246 | {
247 | $this->expectException(\LogicException::class);
248 | $ip = IPv4\Address::factory('10.250.30.40');
249 | $ip[0] = 0;
250 | }
251 |
252 | public function testArrayAccessUnset(): void
253 | {
254 | $this->expectException(\LogicException::class);
255 | $ip = IPv4\Address::factory('10.250.30.40');
256 | unset($ip[0]);
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/tests/IPv4NetworkAddressTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($subnet, (string) $net->get_subnet_mask());
70 | $this->assertEquals($address_count, $net->get_NetworkAddress_count());
71 | $this->assertEquals($network_class, $net->get_network_class());
72 | }
73 |
74 | public function testGlobalNetmask(): void
75 | {
76 | $this->assertEquals('255.255.255.255', (string) IPv4\NetworkAddress::get_global_netmask());
77 | }
78 |
79 | public function testDodgyBitwiseStuff(): void
80 | {
81 | $block = IPv4\NetworkAddress::factory('10.13.112.20/30');
82 | $address = IPv4\Address::factory('10.13.112.21');
83 |
84 | $this->assertTrue($block->encloses_address($address));
85 | }
86 |
87 | public function providerNetworkBroadcastAddress(): array
88 | {
89 | return array(
90 | array(IPv4\NetworkAddress::factory('192.168.1.1/24'), '192.168.1.0', '192.168.1.255'),
91 | array(IPv4\NetworkAddress::factory('192.168.0.10/24'), '192.168.0.0', '192.168.0.255'),
92 | );
93 | }
94 |
95 | /**
96 | * @dataProvider providerNetworkBroadcastAddress
97 | */
98 | public function testNetworkBroadcastAddress(IPv4\NetworkAddress $ip, string $ex_network, string $ex_broadcast): void
99 | {
100 | $this->assertEquals($ex_network, (string) $ip->get_NetworkAddress());
101 | $this->assertEquals($ex_broadcast, (string) $ip->get_broadcast_address());
102 | }
103 |
104 | public function providerSplit(): array
105 | {
106 | $data = array(
107 | array('192.168.0.0/24', 0, array('192.168.0.0/24')),
108 | array('192.168.0.0/24', 1, array('192.168.0.0/25', '192.168.0.128/25')),
109 | array('192.168.0.0/24', 2, array('192.168.0.0/26', '192.168.0.64/26', '192.168.0.128/26', '192.168.0.192/26')),
110 | );
111 | foreach ($data as &$d)
112 | {
113 | $d[0] = IPv4\NetworkAddress::factory($d[0]);
114 | foreach ($d[2] as &$e)
115 | {
116 | $e = IPv4\NetworkAddress::factory($e);
117 | }
118 | }
119 | return $data;
120 | }
121 |
122 | /**
123 | * @dataProvider providerSplit
124 | */
125 | public function testSplit(IPv4\NetworkAddress $block, int $degree, array $expected): void
126 | {
127 | $this->assertEquals($expected, $block->split($degree));
128 | }
129 |
130 | public function testSplitBeyondRange(): void
131 | {
132 | $this->expectException(InvalidArgumentException::class);
133 | $block = IPv4\NetworkAddress::factory('192.168.0.0/32');
134 | $block->split();
135 | }
136 |
137 | public function testIteratorInterface(): void
138 | {
139 | $block = IPv4\NetworkAddress::factory('192.168.0.0/30');
140 | $expected = array('192.168.0.0', '192.168.0.1', '192.168.0.2', '192.168.0.3');
141 | $actual = array();
142 | foreach ($block as $key => $ip)
143 | {
144 | $actual[] = (string)$ip;
145 | }
146 | $this->assertEquals($expected, $actual);
147 | $this->assertEquals($expected, array_map('strval', $block->toArray()));
148 | }
149 |
150 | public function testTwoIterators(): void
151 | {
152 | $block = IPv4\NetworkAddress::factory('192.168.0.0/31');
153 | $expected = array('192.168.0.0', '192.168.0.0', '192.168.0.1', '192.168.0.1', '192.168.0.0', '192.168.0.1');
154 | $actual = array();
155 | foreach ($block as $key => $ip)
156 | {
157 | $actual[] = (string)$ip;
158 | foreach ($block as $key2 => $ip2)
159 | {
160 | $actual[] = (string)$ip2;
161 | }
162 | }
163 | $this->assertEquals($expected, $actual);
164 | }
165 |
166 | public function testCountableInterface(): void
167 | {
168 | $block = IPv4\NetworkAddress::factory('192.168.0.0/30');
169 | $this->assertCount(4, $block);
170 | $block = IPv4\NetworkAddress::factory('192.168.0.0/24');
171 | $this->assertCount(2 ** 8, $block);
172 | $block = IPv4\NetworkAddress::factory('192.168.0.0/16');
173 | $this->assertCount(2 ** 16, $block);
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/tests/IPv6AddressTest.php:
--------------------------------------------------------------------------------
1 | bitwise_operation($flag, $other);
15 | }
16 | }
17 |
18 | /**
19 | * Tests for the IPv6\Address Class
20 | *
21 | * @package default
22 | * @author Marcus Cobden
23 | */
24 | class IPv6_Address_Test extends TestCase
25 | {
26 |
27 | public function providerFactory(): array
28 | {
29 | return array(
30 | array(
31 | '::1',
32 | '::1',
33 | '0:0:0:0:0:0:0:1',
34 | '0000:0000:0000:0000:0000:0000:0000:0001'),
35 | array(
36 | 1,
37 | '::1',
38 | '0:0:0:0:0:0:0:1',
39 | '0000:0000:0000:0000:0000:0000:0000:0001'),
40 | array(
41 | 'fe80::226:bbff:fe14:7372',
42 | 'fe80::226:bbff:fe14:7372',
43 | 'fe80:0:0:0:226:bbff:fe14:7372',
44 | 'fe80:0000:0000:0000:0226:bbff:fe14:7372'),
45 | array(
46 | '::ffff:127:0:0:1',
47 | '::ffff:127:0:0:1',
48 | '0:0:0:ffff:127:0:0:1',
49 | '0000:0000:0000:ffff:0127:0000:0000:0001'),
50 | array(
51 | '2001:504:0:1:0:3:1898:1',
52 | '2001:504:0:1:0:3:1898:1',
53 | '2001:504:0:1:0:3:1898:1',
54 | '2001:0504:0000:0001:0000:0003:1898:0001'),
55 | );
56 | }
57 |
58 | /**
59 | * @dataProvider providerFactory
60 | */
61 | public function testFactory(string|int $input,string $compact,string $abbr,string $full): void
62 | {
63 | $instance = IPv6\Address::factory($input);
64 |
65 | $this->assertNotNull($instance);
66 | $this->assertEquals($compact, $instance->format(IP\Address::FORMAT_COMPACT));
67 | $this->assertEquals($abbr, $instance->format(IPv6\Address::FORMAT_ABBREVIATED));
68 | $this->assertEquals($full, $instance->format(IP\Address::FORMAT_FULL));
69 | }
70 |
71 | public function providerFormatException(): array
72 | {
73 | $bad_mode = -1;
74 | $data = static::providerFactory();
75 | foreach ($data as $i => $entry) {
76 | $data[$i] = array($entry[0], $bad_mode);
77 | }
78 |
79 | return $data;
80 | }
81 |
82 | /**
83 | *
84 | * @dataProvider providerFormatException
85 | */
86 | public function testFormatException(string|int $input, int $mode): void
87 | {
88 | $this->expectException(InvalidArgumentException::class);
89 | $instance = IPv6\Address::factory($input);
90 | echo $instance->format($mode);
91 | }
92 |
93 | public function providerFactoryException(): array
94 | {
95 | return array(
96 | array('256.0.0.1'),
97 | array('127.-1.0.1'),
98 | array('127.128.256.1'),
99 | array('cake'),
100 | array('12345'),
101 | array('-12345'),
102 | array('0000:0000:0000:ffff:0127:0000:0000:000g'),
103 | array('000000000000ffff0127000000000001'),
104 | );
105 | }
106 |
107 | /**
108 | *
109 | * @dataProvider providerFactoryException
110 | */
111 | public function testFactoryException(string $input): void
112 | {
113 | $this->expectException(InvalidArgumentException::class);
114 | IPv6\Address::factory($input);
115 | }
116 |
117 | public function providerAddSubtract(): array
118 | {
119 | $data = array(
120 | array('::' , 0, '::' ),
121 | array('::1' , 0, '::1' ),
122 | array('::1' , 1, '::2' ),
123 | array('::1' , 2, '::3' ),
124 | array('::5' , 6, '::b' ),
125 | array('::10', 1, '::11' ),
126 | array('::10', new \Math_BigInteger(1), '::11' ),
127 | array('::10', new \Math_BigInteger(2), '::12' ),
128 | );
129 |
130 | for ($i=0, $iMax = count($data); $i < $iMax; $i++)
131 | {
132 | $data[$i][0] = IPv6\Address::factory($data[$i][0]);
133 | $data[$i][2] = IPv6\Address::factory($data[$i][2]);
134 | }
135 | return $data;
136 | }
137 |
138 | /**
139 | * @dataProvider providerAddSubtract
140 | */
141 | public function testAddSubtract(IPv6\Address $left, int|\Math_BigInteger $right, IPv6\Address $expected): void
142 | {
143 | $result = $left->add($right);
144 | $this->assertEquals(0, $result->compare_to($expected));
145 | $again = $result->subtract($right);
146 | $this->assertEquals(0, $again->compare_to($left));
147 | }
148 |
149 | public function providerCompareTo(): array
150 | {
151 | $data = array(
152 | array('::', '::', 0),
153 | array('::1', '::1', 0),
154 | array('::1', '::2', -1),
155 | array('::2', '::1', 1),
156 | array('::f', '::1', 1),
157 | array('::a', '::b', -1),
158 | );
159 |
160 | for ($i=0, $iMax = count($data); $i < $iMax; $i++){
161 | $data[$i][0] = IPv6\Address::factory($data[$i][0]);
162 | $data[$i][1] = IPv6\Address::factory($data[$i][1]);
163 | }
164 | return $data;
165 | }
166 |
167 | /**
168 | * @dataProvider providerCompareTo
169 | */
170 | public function testCompareTo(IPv6\Address $left, IPv6\Address $right, int $expected): void
171 | {
172 | $this->assertEquals($expected, $left->compare_to($right));
173 | }
174 |
175 | public function providerBitwise(): array
176 | {
177 | $data = array(
178 | // OP1 OP2 AND OR XOR NOT
179 | array('::1', '::1', '::1', '::1', '::0', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe'),
180 | array('::' , '::1', '::0', '::1', '::1', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
181 | array('::1', '::' , '::0', '::1', '::1', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe'),
182 | array('::' , '::' , '::0', '::0', '::0', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
183 | );
184 |
185 | for ($i=0, $iMax = count($data); $i < $iMax; $i++) {
186 | for ($j=0; $j < 6; $j++) {
187 | $data[$i][$j] = IPv6\Address::factory($data[$i][$j]);
188 | }
189 | }
190 |
191 | return $data;
192 | }
193 |
194 | /**
195 | * @dataProvider providerBitwise
196 | */
197 | public function testBitwise(IPv6\Address $ip1, IPv6\Address $ip2, IPv6\Address $ex_and, IPv6\Address $ex_or, IPv6\Address $ex_xor, IPv6\Address $ex_not): void
198 | {
199 | $this->assertEquals((string) $ex_and, (string) $ip1->bitwise_and($ip2));
200 | $this->assertEquals((string) $ex_or , (string) $ip1->bitwise_or($ip2));
201 | $this->assertEquals((string) $ex_xor, (string) $ip1->bitwise_xor($ip2));
202 | $this->assertEquals((string) $ex_not, (string) $ip1->bitwise_not());
203 | }
204 |
205 | public function testBitwiseException(): void
206 | {
207 |
208 | $ip = TestingIPv6_Address::factory('::1');
209 |
210 | try
211 | {
212 | $ip->call_bitwise_operation('!', $ip);
213 | $this->fail('An expected exception has not been raised.');
214 | }
215 | catch (InvalidArgumentException $e)
216 | {
217 | $exception_message = $e->getMessage();
218 | $expected_exception_message = "Unknown Operation type '!'.";
219 | $this->assertEquals($expected_exception_message, $exception_message);
220 | }
221 |
222 | $ip->call_bitwise_operation('&', $ip);
223 | $ip->call_bitwise_operation('|', $ip);
224 | $ip->call_bitwise_operation('^', $ip);
225 | $ip->call_bitwise_operation('~');
226 | }
227 |
228 | //
229 | // public function provider_as_IPv4\Address()
230 | // {
231 | // return array(
232 | // array('0000:0000:0000:ffff:0127:0000:0000:0001', '127.0.0.1'),
233 | // );
234 | // }
235 | //
236 | // /**
237 | // * @dataProvider provider_as_IPv4\Address
238 | // */
239 | // public function test_as_IPv4\Address($v6, $v4 = NULL)
240 | // {
241 | // $ip = new IPv6\Address($v6);
242 | //
243 | // if ($v4 === NULL)
244 | // $this->assertFalse($ip->isEncodedIPv4Address());
245 | // else
246 | // $this->assertEquals($v4, (string) $ip->asIPv4Address());
247 | //
248 | // }
249 |
250 | public function testGetOctet(): void
251 | {
252 | $ip = IPv6\Address::factory('0001:0002:aaaa:1234:abcd:1000:2020:fffe');
253 | $this->assertEquals(0, $ip->get_octet(-16));
254 | $this->assertEquals(1, $ip->get_octet(-15));
255 | $this->assertEquals(0xAA, $ip->get_octet(-11));
256 | $this->assertEquals(0x12, $ip->get_octet(-10));
257 | $this->assertEquals(0x20, $ip->get_octet(-4));
258 | $this->assertEquals(0x20, $ip->get_octet(-3));
259 | $this->assertEquals(0xFF, $ip->get_octet(-2));
260 | $this->assertEquals(0xFE, $ip->get_octet(-1));
261 | $this->assertEquals(0, $ip->get_octet(0));
262 | $this->assertEquals(1, $ip->get_octet(1));
263 | $this->assertEquals(0x10, $ip->get_octet(10));
264 | $this->assertEquals(0xFE, $ip->get_octet(15));
265 |
266 | $this->assertNull($ip->get_octet(16));
267 | }
268 |
269 | public function testMappedIPv4(): void
270 | {
271 | /** @var IPv6\Address $ip */
272 | $ip = IP\Address::factory('::ffff:141.44.23.50');
273 | $this->assertEquals(1, $ip->is_encoded_IPv4_address());
274 | $ipv4 = $ip->as_IPv4_address();
275 | $this->assertEquals( '141.44.23.50',$ipv4->format(IP\Address::FORMAT_COMPACT));
276 | }
277 |
278 | public function testMayMappedIPv4Format(): void
279 | {
280 | $mappedIPv4String = '::ffff:141.44.23.50';
281 | $ordinaryIPv6String = '1:2:aaaa:1234:abcd:1000:2020:fffe';
282 | $mappedIPv4Address = IP\Address::factory($mappedIPv4String);
283 | $ordinaryIPv6Address = IP\Address::factory($ordinaryIPv6String);
284 | $this->assertEquals($mappedIPv4Address->format(IPv6\Address::FORMAT_MAY_MAPPED_COMPACT), $mappedIPv4String);
285 | $this->assertEquals($ordinaryIPv6Address->format(IPv6\Address::FORMAT_MAY_MAPPED_COMPACT), $ordinaryIPv6String);
286 | }
287 |
288 | public function testArrayAccess(): void
289 | {
290 | $ip = IPv6\Address::factory('0001:0002:aaaa:1234:abcd:1000:2020:fffe');
291 | $this->assertEquals(0x12, $ip[-10]);
292 | $this->assertEquals(0x10, $ip[10]);
293 |
294 | $this->assertNull($ip[16]);
295 | }
296 |
297 | /**
298 | * @return array
299 | */
300 | public function providerPadIps(): array
301 | {
302 | return array(
303 | array('::', '0000:0000:0000:0000:0000:0000:0000:0000'),
304 | array('::fff', '0000:0000:0000:0000:0000:0000:0000:0fff'),
305 | array('::ff:fff', '0000:0000:0000:0000:0000:0000:00ff:0fff'),
306 | array('::f:ff:fff', '0000:0000:0000:0000:0000:000f:00ff:0fff'),
307 | array('fff::', '0fff:0000:0000:0000:0000:0000:0000:0000'),
308 | array('fff:ff::', '0fff:00ff:0000:0000:0000:0000:0000:0000'),
309 | array('fff:ff:f::', '0fff:00ff:000f:0000:0000:0000:0000:0000'),
310 | array('2001:630:d0::', '2001:0630:00d0:0000:0000:0000:0000:0000'),
311 | array('f:f:f:f:f:f:f:f', '000f:000f:000f:000f:000f:000f:000f:000f'),
312 | array('fff::fff', '0fff:0000:0000:0000:0000:0000:0000:0fff'),
313 | array('fff:0000:bb::aa:0000:fff', '0fff:0000:00bb:0000:0000:00aa:0000:0fff'),
314 | // not need pad
315 | array('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'),
316 | array('0000:0000:0000:0000:0000:0000:0000:0000', '0000:0000:0000:0000:0000:0000:0000:0000'),
317 | );
318 | }
319 |
320 | /**
321 | * @dataProvider providerPadIps
322 | *
323 | * @param string $actual
324 | * @param string $expected
325 | */
326 | public function testPad(string $actual, string $expected): void
327 | {
328 | $this->assertEquals($expected, IPv6\Address::pad($actual));
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/tests/IPv6NetworkAddressTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', (string) IPv6\NetworkAddress::get_global_netmask());
16 | }
17 |
18 | public function providerSplit(): array
19 | {
20 | $data = array(
21 | array('::0/126', 0, array('::0/126')),
22 | array('::0/126', 1, array('::0/127', '::2/127')),
23 | array('::0/126', 2, array('::0/128', '::1/128', '::2/128', '::3/128')),
24 | );
25 | foreach ($data as &$d)
26 | {
27 | $d[0] = IPv6\NetworkAddress::factory($d[0]);
28 | foreach ($d[2] as &$e)
29 | {
30 | $e = IPv6\NetworkAddress::factory($e);
31 | }
32 | }
33 | return $data;
34 | }
35 |
36 | /**
37 | * @dataProvider providerSplit
38 | */
39 | public function testSplit(IPv6\NetworkAddress $block, int $degree, array $expected): void
40 | {
41 | $this->assertEquals($expected, $block->split($degree));
42 | }
43 |
44 | public function testSplitBeyondRange(): void
45 | {
46 | $this->expectException(InvalidArgumentException::class);
47 | $block = IPv6\NetworkAddress::factory('::0/128');
48 | $block->split();
49 | }
50 |
51 | public function testIterationInterface(): void
52 | {
53 | $block = IPv6\NetworkAddress::factory('::0/126');
54 | $expected = array('::0', '::1', '::2', '::3');
55 | $actual = array();
56 | foreach ($block as $key => $ip)
57 | {
58 | $actual[] = (string)$ip;
59 | }
60 | $this->assertEquals($expected, $actual);
61 | }
62 |
63 | public function testCountableInterface(): void
64 | {
65 | $block = IPv6\NetworkAddress::factory('::0/126');
66 | $this->assertCount(4, $block);
67 | $block = IPv6\NetworkAddress::factory('::0/120');
68 | $this->assertCount(2 ** 8, $block);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |