Passed
Push — master ( a631ae...ee4321 )
by Colin
03:20 queued 01:02
created

Subnet::getFirstHostAddr()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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