Skip to content

Commit 81b6c75

Browse files
authored
Merge pull request #24 from xp-framework/refactor/simplify-network
Simplify peer.net API
2 parents cafbcb4 + 0e58332 commit 81b6c75

11 files changed

+331
-236
lines changed

src/main/php/peer/net/Inet4Address.class.php

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,71 @@
55
/**
66
* IPv4 address
77
*
8-
* @test xp://peer.unittest.Inet4AddressTest
9-
* @see php://ip2long
8+
* @test peer.unittest.Inet4AddressTest
109
*/
11-
class Inet4Address implements InetAddress {
10+
class Inet4Address extends InetAddress {
1211
private $addr;
1312

1413
/**
15-
* Convert IPv4 address from dotted form into a long. Supports hexadecimal and
14+
* Constructor
15+
*
16+
* Converts IPv4 address from dotted form into a long. Supports hexadecimal and
1617
* octal notations. Yes, 0177.0.0.1 and 0x7F.0.0.1 are both equivalent with
1718
* 127.0.0.1 - localhost!
1819
*
19-
* @param string $ip
20-
* @return int
20+
* @param string|int $address
21+
* @throws lang.FormatException in case address is illegal
22+
*/
23+
public function __construct($address) {
24+
if (is_int($address)) {
25+
$this->addr= $address;
26+
} else {
27+
$this->addr= self::parse($address);
28+
}
29+
}
30+
31+
/**
32+
* Parse a given input string, either raising exceptions or silently returning NULL.
33+
*
34+
* @see https://www.php.net/ip2long (doesn't support hexadecimal and octal representations)
35+
* @param string $input
36+
* @param bool $throw
37+
* @return ?int
2138
* @throws lang.FormatException
2239
*/
23-
protected static function ip2long($ip) {
24-
$i= 0; $addr= 0; $count= 0;
25-
foreach (explode('.', $ip) as $byte) {
26-
if (++$count > 4) {
27-
throw new FormatException('Given IP string has more than 4 blocks: ['.$ip.']');
28-
}
40+
public static function parse($input, $throw= true) {
41+
$blocks= explode('.', $input);
42+
if (sizeof($blocks) > 4) {
43+
if ($throw) throw new FormatException('Given IP string has more than 4 blocks: '.$input);
44+
return null;
45+
}
2946

30-
$l= strlen($byte);
47+
$r= 0;
48+
foreach ($blocks as $i => $block) {
49+
$l= strlen($block);
3150
$n= -1;
32-
if ($l > 1 && '0' === $byte[0]) {
33-
if (('x' === $byte[1] || 'X' === $byte[1]) && $l === strspn($byte, '0123456789aAbBcCdDeEfF', 2) + 2) {
34-
$n= hexdec($byte);
35-
} else if ($l === strspn($byte, '0123456789')) {
36-
$n= octdec($byte);
51+
if ($l > 1 && '0' === $block[0]) {
52+
if (('x' === $block[1] || 'X' === $block[1]) && $l === strspn($block, '0123456789aAbBcCdDeEfF', 2) + 2) {
53+
$n= hexdec($block);
54+
} else if ($l === strspn($block, '01234567')) {
55+
$n= octdec($block);
3756
}
38-
} else if ($l > 0 && $l === strspn($byte, '0123456789')) {
39-
$n= (int)$byte;
57+
} else if ($l > 0 && $l === strspn($block, '0123456789')) {
58+
$n= (int)$block;
4059
}
4160

4261
if ($n < 0 || $n > 255) {
43-
throw new FormatException('Invalid format of IP address: ['.$ip.']');
62+
if ($throw) throw new FormatException('Invalid format of IP address: '.$input);
63+
return null;
4464
}
4565

46-
$addr|= ($n << (8 * (3 - $i++)));
66+
$r|= $n << (8 * (3 - $i));
4767
}
48-
return $addr;
49-
}
50-
51-
/**
52-
* Constructor
53-
*
54-
* @param string $address
55-
* @throws lang.FormatException in case address is illegal
56-
*/
57-
public function __construct($address) {
58-
$this->addr= self::ip2long($address);
68+
return $r;
5969
}
6070

61-
/**
62-
* Retrieve size of ips of this kind in bits.
63-
*
64-
* @return int
65-
*/
66-
public function sizeInBits() {
67-
return 32;
68-
}
71+
/** @return int */
72+
public function sizeInBits() { return 32; }
6973

7074
/**
7175
* Retrieve IP address notation for DNS reverse query
@@ -97,11 +101,12 @@ public function isLoopback() {
97101
/**
98102
* Determine whether address is in the given subnet
99103
*
100-
* @param string net
101-
* @return bool
102-
* @throws lang.FormatException in case net has invalid format
104+
* @param string|peer.net.Network $subnet
105+
* @return bool
106+
* @throws lang.FormatException in case net has invalid format
103107
*/
104-
public function inSubnet(Network $net) {
108+
public function inSubnet($subnet) {
109+
$net= $subnet instanceof Network ? $subnet : new Network($subnet);
105110
if (!$net->getAddress() instanceof self) return false;
106111

107112
$addrn= $net->getAddress()->addr;

src/main/php/peer/net/Inet6Address.class.php

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,68 +8,68 @@
88
*
99
* @test xp://peer.unittest.net.Inet6AddressTest
1010
*/
11-
class Inet6Address implements InetAddress {
12-
protected $addr;
13-
11+
class Inet6Address extends InetAddress {
12+
private $addr;
13+
1414
/**
1515
* Constructor
1616
*
17-
* @param string addr
18-
* @param bool binary
17+
* @param string $addr
18+
* @param bool $binary
19+
* @throws lang.FormatException in case address is illegal
1920
*/
2021
public function __construct($addr, $binary= false) {
2122
if ($binary) {
2223
$this->addr= $addr;
2324
} else {
24-
$this->addr= pack('H*', self::normalize($addr));
25+
$this->addr= self::parse($addr);
2526
}
2627
}
2728

28-
/**
29-
* Retrieve size of ips of this kind in bits.
30-
*
31-
* @return int
32-
*/
33-
public function sizeInBits() {
34-
return 128;
35-
}
29+
/** @return int */
30+
public function sizeInBits() { return 128; }
3631

3732
/**
38-
* Normalize address
33+
* Parse a given input string, either raising exceptions or silently returning NULL.
3934
*
40-
* @param string addr
41-
* @return string
35+
* @param string $input
36+
* @param bool $throw
37+
* @return ?string
38+
* @throws lang.FormatException
4239
*/
43-
public static function normalize($addr) {
40+
public static function parse($input, $throw= true) {
4441
$out= '';
45-
$hexquads= explode(':', $addr);
42+
$quads= explode(':', $input);
4643

4744
// Shortest address is ::1, this results in 3 parts...
48-
if (sizeof($hexquads) < 3) {
49-
throw new FormatException('Address contains less than 1 hexquad part: ['.$addr.']');
45+
if (sizeof($quads) < 3) {
46+
if ($throw) throw new FormatException('Address contains less than 1 hexquad part: '.$input);
47+
return null;
5048
}
5149

52-
if ('' == $hexquads[0]) array_shift($hexquads);
53-
foreach ($hexquads as $hq) {
54-
if ('' == $hq) {
55-
$out.= str_repeat('0000', 8 - (sizeof($hexquads)- 1));
50+
if ('' === $quads[0]) array_shift($quads);
51+
foreach ($quads as $hq) {
52+
if ('' === $hq) {
53+
$out.= str_repeat('0000', 8 - (sizeof($quads) - 1));
5654
continue;
5755
}
5856

5957
// Catch cases like ::ffaadd00::
6058
if (strlen($hq) > 4) {
61-
throw new FormatException('Detected hexquad w/ more than 4 digits in ['.$addr.']');
59+
if ($throw) throw new FormatException('Detected hexquad w/ more than 4 digits in '.$input);
60+
return null;
6261
}
6362

6463
// Not hex
6564
if (strspn($hq, '0123456789abcdefABCDEF') < strlen($hq)) {
66-
throw new FormatException('Illegal digits in ['.$addr.']');
65+
if ($throw) throw new FormatException('Illegal digits in '.$input);
66+
return null;
6767
}
6868

69-
$out.= str_repeat('0', 4- strlen($hq)).$hq;
69+
$out.= str_repeat('0', 4 - strlen($hq)).$hq;
7070
}
71-
72-
return $out;
71+
72+
return pack('H*', $out);
7373
}
7474

7575
/**
@@ -131,26 +131,27 @@ public function reversedNotation() {
131131
/**
132132
* Determine whether address is in the given subnet
133133
*
134-
* @param string net
135-
* @return bool
136-
* @throws lang.FormatException in case net has invalid format
134+
* @param string|peer.net.Network $subnet
135+
* @return bool
136+
* @throws lang.FormatException in case net has invalid format
137137
*/
138-
public function inSubnet(Network $net) {
138+
public function inSubnet($subnet) {
139+
$net= $subnet instanceof Network ? $subnet : new Network($subnet);
140+
if (!$net->getAddress() instanceof self) return false;
141+
139142
$addr= $net->getAddress();
140143
$mask= $net->getNetmask();
141-
142144
$position= 0;
143145
while ($mask > 8) {
144-
if ($addr->addr[$position] != $this->addr[$position]) return false;
146+
if ($addr->addr[$position] !== $this->addr[$position]) return false;
145147
$position++;
146148
$mask-= 8;
147149
}
148150

149-
if ($mask > 0) {
150-
return ord($addr->addr[$position]) >> (8 - $mask) == ord($this->addr[$position]) >> (8 - $mask);
151-
}
152-
153-
return true;
151+
return $mask > 0
152+
? ord($addr->addr[$position]) >> (8 - $mask) === ord($this->addr[$position]) >> (8 - $mask)
153+
: true
154+
;
154155
}
155156

156157
/**
Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,72 @@
11
<?php namespace peer\net;
22

3-
use lang\Value;
3+
use lang\{Value, FormatException, IllegalArgumentException};
44

55
/**
66
* Common ancestor for IPv4 and IPv6
7+
*
8+
* @test peer.unittest.net.InetAddressTest
79
*/
8-
interface InetAddress extends Value {
10+
abstract class InetAddress implements Value {
911

1012
/**
1113
* Retrieve "human-readable" address
1214
*
13-
* @return string
15+
* @return string
1416
*/
15-
public function asString();
17+
public abstract function asString();
1618

1719
/**
1820
* Check whether this address is a loopback address
1921
*
20-
* @return bool
22+
* @return bool
2123
*/
22-
public function isLoopback();
24+
public abstract function isLoopback();
2325

2426
/**
2527
* Determine whether this address is in the given network.
2628
*
27-
* @param peer.net.Network net
28-
* @return bool
29-
* @throws lang.FormatException in case net has invalid format
29+
* @param string|peer.net.Network $subnet
30+
* @return bool
31+
* @throws lang.FormatException in case net has invalid format
3032
*/
31-
public function inSubnet(Network $net);
33+
public abstract function inSubnet($subnet);
3234

3335
/**
3436
* Create a subnet of this address, with the specified size.
3537
*
36-
* @param int subnetSize
37-
* @return peer.net.Network
38-
* @throws lang.IllegalArgumentException in case the subnetSize is not correct
38+
* @param int $size
39+
* @return peer.net.Network
40+
* @throws lang.IllegalArgumentException in case the $size is not correct
3941
*/
40-
public function createSubnet($subnetSize);
42+
public abstract function createSubnet($size);
4143

4244
/**
4345
* Retrieve size of address in bits
4446
*
45-
* @return int
47+
* @return int
4648
*/
47-
public function sizeInBits();
49+
public abstract function sizeInBits();
4850

4951
/**
5052
* Retrieve reversed notation for reverse DNS lookups
5153
*
52-
* @return string
54+
* @return string
5355
*/
54-
public function reversedNotation();
56+
public abstract function reversedNotation();
57+
58+
/**
59+
* Returns an IPv4 or IPv6 address based on the given input
60+
*
61+
* @throws lang.FormatException
62+
*/
63+
public static function new(string $arg): self {
64+
if (preg_match('/^[a-fA-F0-9x\.]+$/', $arg)) {
65+
return new Inet4Address($arg);
66+
} else if (preg_match('/^[a-f0-9\:]+$/', $arg)) {
67+
return new Inet6Address($arg);
68+
} else {
69+
throw new FormatException('Given argument does not look like an IP address: '.$arg);
70+
}
71+
}
5572
}

0 commit comments

Comments
 (0)