Completed
Push — master ( 40d9e6...ef0a92 )
by Colin
07:48 queued 03:41
created

Subnet::fromString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace ColinODell\Ipv4;
4
5
/**
6
 * Class for identifying and enumerating an IPv4 Subnet.
7
 */
8
class Subnet implements \Countable, \IteratorAggregate
9
{
10
    /**
11
     * Define some error messages as class constants.
12
     */
13
    const ERROR_NETWORK_FORMAT = 'IP format incorrect';
14
    const ERROR_CIDR_FORMAT = 'Invalid CIDR format';
15
    const ERROR_SUBNET_FORMAT = 'Invalid Subnet format';
16
17
    /**
18
     * Internal storage of network in long format.
19
     *
20
     * @var float
21
     */
22
    private $nw = 0;
23
24
    /**
25
     * Internal storage of subnet in long format.
26
     *
27
     * @var float
28
     */
29
    private $sn = 0;
30
31
    /**
32
     * Public constructor.
33
     *
34
     * @param mixed $n Network
35
     * @param mixed $s Subnet
36
     */
37 18
    public function __construct($n = null, $s = null)
38
    {
39 18
        if ($n instanceof Address) {
40 3
            $n = $n->toString();
41 2
        }
42
43 18
        if ($s instanceof Address) {
44 3
            $s = $s->toString();
45 2
        }
46
47 18
        if (is_string($n) and !$s) {
48 15
            $this->setFromString($n);
49 12
        } elseif ($n and $s) {
50 6
            $this->setNetwork($n)->setNetmask($s);
51 2
        }
52 12
    }
53
54
    /**
55
     * @param string $str
56
     *
57
     * @return self
58
     */
59 3
    public static function fromString($str)
60
    {
61 3
        return new self($str);
62
    }
63
64
    /**
65
     * Static method converts CIDR to dotted-quad IP notation.
66
     *
67
     * @param int $cidr
68
     *
69
     * @return string
70
     */
71 18
    public static function cidrToIp($cidr)
72
    {
73 18
        if (!($cidr >= 0 and $cidr <= 32)) {
74 6
            throw new \InvalidArgumentException(self::ERROR_CIDR_FORMAT);
75
        }
76
77 12
        return long2ip(bindec(str_pad(str_pad('', $cidr, '1'), 32, '0')));
78
    }
79
80
    /**
81
     * Static method to determine if an IP is on a subnet.
82
     *
83
     * @param mixed $sn
84
     * @param mixed $ip
85
     *
86
     * @return bool
87
     */
88 9
    public static function containsAddress($sn, $ip)
89
    {
90 9
        if (is_string($sn)) {
91 6
            $sn = new self($sn);
92 4
        }
93
94 9
        if (is_string($ip)) {
95 6
            $ip = Address::fromString($ip);
96 4
        }
97
98 9
        if (!$sn instanceof self) {
99 3
            throw new \InvalidArgumentException(self::ERROR_SUBNET_FORMAT);
100
        }
101
102 6
        if (!$ip instanceof Address) {
103 3
            throw new \InvalidArgumentException(Address::ERROR_ADDR_FORMAT);
104
        }
105
106 3
        $sn_dec = ip2long($sn->getNetmask());
107
108 3
        return ($ip->toLong() & $sn_dec) == (ip2long($sn->getNetwork()) & $sn_dec);
109
    }
110
111
    /**
112
     * Parse subnet string.
113
     *
114
     * @param string $data
115
     *
116
     * @return self
117
     */
118 12
    private function setFromString($data)
119
    {
120
        // Validate that the input matches an expected pattern
121 12
        if (!preg_match('!^([0-9]{1,3}\.){3}[0-9]{1,3}(( ([0-9]{1,3}\.){3}[0-9]{1,3})|(/[0-9]{1,2}))$!', $data)) {
122
            throw new \InvalidArgumentException(self::ERROR_NETWORK_FORMAT);
123
        }
124
125
        // Parse one of two formats possible, first is /CIDR format
126 12
        if (strpos($data, '/')) {
127 12
            list($network, $cidr) = explode('/', $data, 2);
128
129 12
            $this->setNetwork($network);
130 12
            $this->sn = ip2long(self::cidrToIp($cidr));
131 8
        } else {
132
            // Second format is network space subnet
133 3
            list($network, $subnet) = explode(' ', $data, 2);
134
135 3
            $this->setNetwork($network);
136 3
            $this->setNetmask($subnet);
137
        }
138
139 12
        return $this;
140
    }
141
142
    /**
143
     * Method to check if an IP is on this network.
144
     *
145
     * @param mixed $ip
146
     *
147
     * @return bool
148
     */
149 3
    public function contains($ip)
150
    {
151 3
        return self::containsAddress($this, $ip);
152
    }
153
154
    /**
155
     * Set the network on the object, from dotted-quad notation.
156
     *
157
     * @param string $data
158
     *
159
     * @return self
160
     */
161 12
    private function setNetwork($data)
162
    {
163 12
        $this->nw = Address::fromString($data)->toLong();
164
165 12
        return $this;
166
    }
167
168
    /**
169
     * Set the netmask on the object, from dotted-quad notation.
170
     *
171
     * @param string $data
172
     *
173
     * @return self
174
     */
175 6
    private function setNetmask($data)
176
    {
177 6
        $data = Address::fromString($data);
178
179 6
        if (!preg_match('/^1*0*$/', $data->toBinary())) {
180 3
            throw new \InvalidArgumentException(self::ERROR_SUBNET_FORMAT);
181
        }
182
183 3
        $this->sn = $data->toLong();
184
185 3
        return $this;
186
    }
187
188
    /**
189
     * Return the netmask as dotted-quad string.
190
     *
191
     * @return string
192
     */
193 3
    public function getNetmask()
194
    {
195 3
        return long2ip($this->sn);
196
    }
197
198
    /**
199
     * Return the CIDR value representing the netmask.
200
     *
201
     * @return int
202
     */
203 12
    public function getNetmaskCidr()
204
    {
205 12
        return strlen(rtrim(decbin($this->sn), '0'));
206
    }
207
208
    /**
209
     * Return the network address in dotted-quad notation.
210
     *
211
     * @return string
212
     */
213 12
    public function getNetwork()
214
    {
215 12
        $nw_bin = Address::fromLong($this->nw)->toBinary();
216 12
        $nw_bin = (str_pad(substr($nw_bin, 0, $this->getNetmaskCidr()), 32, 0));
217
218 12
        return Address::fromBinary($nw_bin)->toString();
219
    }
220
221
    /**
222
     * Return the first address of this network.
223
     *
224
     * @return string
225
     */
226 12
    public function getFirstHostAddr()
227
    {
228 12
        $bin_net = Address::fromString($this->getNetwork())->toBinary();
229 12
        $bin_first = (str_pad(substr($bin_net, 0, 31), 32, 1));
230
231 12
        return Address::fromBinary($bin_first)->toString();
232
    }
233
234
    /**
235
     * Return last host of this network.
236
     *
237
     * @return string
238
     */
239 12
    public function getLastHostAddr()
240
    {
241 12
        $bin_bcast = Address::fromString($this->getBroadcastAddr())->toBinary();
242 12
        $bin_last = (str_pad(substr($bin_bcast, 0, 31), 32, 0));
243
244 12
        return Address::fromBinary($bin_last)->toString();
245
    }
246
247
    /**
248
     * Return the broadcast address for this network.
249
     *
250
     * @return string
251
     */
252 12
    public function getBroadcastAddr()
253
    {
254 12
        $bin_host = Address::fromLong($this->nw)->toBinary();
255 12
        $bin_bcast = str_pad(
256 12
            substr($bin_host, 0, $this->getNetmaskCidr()),
257 12
            32,
258 4
            1
259 8
        );
260
261 12
        return Address::fromBinary($bin_bcast)->toString();
262
    }
263
264
    /**
265
     * Return a count of the total number of hosts on this network.
266
     *
267
     * @return int
268
     */
269 3
    public function getTotalHosts()
270
    {
271 3
        return bindec(str_pad('', (32 - $this->getNetmaskCidr()), 1)) - 1;
272
    }
273
274
    /**
275
     * Return an iterator for addresses in this subnet.
276
     *
277
     * @return SubnetIterator
278
     */
279 3
    public function getIterator()
280
    {
281 3
        return new SubnetIterator($this);
282
    }
283
284
    /**
285
     * Magic method prints subnet in IP/cidr format.
286
     *
287
     * @return string
288
     */
289 3
    public function __toString()
290
    {
291 3
        return sprintf('%s/%s', $this->getNetwork(), $this->getNetmaskCidr());
292
    }
293
294
    /**
295
     * Implements Countable interface.
296
     *
297
     * @return int
298
     */
299 3
    public function count()
300
    {
301 3
        return $this->getTotalHosts();
302
    }
303
304
    /**
305
     * @param self $subnet
306
     *
307
     * @return bool
308
     */
309 3
    public function equals(self $subnet)
310
    {
311 3
        return $this->getNetwork() === $subnet->getNetwork() && $this->getNetmaskCidr() === $subnet->getNetmaskCidr();
312
    }
313
}
314