Completed
Pull Request — master (#52)
by Michele
05:58
created

Subnet::fromString()   B

Complexity

Conditions 9
Paths 13

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 30
cts 30
cp 1
rs 7.7244
c 0
b 0
f 0
cc 9
nc 13
nop 2
crap 9
1
<?php
2
3
namespace IPLib\Range;
4
5
use IPLib\Address\AddressInterface;
6
use IPLib\Address\IPv4;
7
use IPLib\Address\Type as AddressType;
8
use IPLib\Factory;
9
10
/**
11
 * Represents an address range in subnet format (eg CIDR).
12
 *
13
 * @example 127.0.0.1/32
14
 * @example ::/8
15
 */
16
class Subnet extends AbstractRange
17
{
18
    /**
19
     * Starting address of the range.
20
     *
21
     * @var \IPLib\Address\AddressInterface
22
     */
23
    protected $fromAddress;
24
25
    /**
26
     * Final address of the range.
27
     *
28
     * @var \IPLib\Address\AddressInterface
29
     */
30
    protected $toAddress;
31
32
    /**
33
     * Number of the same bits of the range.
34
     *
35
     * @var int
36
     */
37
    protected $networkPrefix;
38
39
    /**
40
     * The type of the range of this IP range.
41
     *
42
     * @var int|null
43
     */
44
    protected $rangeType;
45
46
    /**
47
     * The 6to4 address IPv6 address range.
48
     *
49
     * @var self|null
50
     */
51
    private static $sixToFour;
52
53
    /**
54
     * Initializes the instance.
55
     *
56
     * @param \IPLib\Address\AddressInterface $fromAddress
57
     * @param \IPLib\Address\AddressInterface $toAddress
58
     * @param int $networkPrefix
59
     *
60
     * @internal
61
     */
62 512
    public function __construct(AddressInterface $fromAddress, AddressInterface $toAddress, $networkPrefix)
63
    {
64 512
        $this->fromAddress = $fromAddress;
65 512
        $this->toAddress = $toAddress;
66 512
        $this->networkPrefix = $networkPrefix;
67 512
    }
68
69
    /**
70
     * {@inheritdoc}
71
     *
72
     * @see \IPLib\Range\RangeInterface::__toString()
73
     */
74 44
    public function __toString()
75
    {
76 44
        return $this->toString();
77
    }
78
79
    /**
80
     * Try get the range instance starting from its string representation.
81
     *
82
     * @param string|mixed $range
83
     * @param bool $supportNonDecimalIPv4 set to true to support parsing non decimal (that is, octal and hexadecimal) IPv4 addresses
84
     *
85
     * @return static|null
86
     */
87 625
    public static function fromString($range, $supportNonDecimalIPv4 = false)
88
    {
89 625
        if (!is_string($range)) {
90 3
            return null;
91
        }
92 622
        $parts = explode('/', $range);
93 622
        if (count($parts) !== 2) {
94 103
            return null;
95
        }
96 519
        $address = Factory::addressFromString($parts[0], true, true, $supportNonDecimalIPv4);
97 519
        if ($address === null) {
98 3
            return null;
99
        }
100 516
        if (!preg_match('/^[0-9]{1,9}$/', $parts[1])) {
101 2
            return null;
102
        }
103 514
        $networkPrefix = (int) $parts[1];
104 514
        $addressBytes = $address->getBytes();
105 514
        $totalBytes = count($addressBytes);
106 514
        $numDifferentBits = $totalBytes * 8 - $networkPrefix;
107 514
        if ($numDifferentBits < 0) {
108 2
            return null;
109
        }
110 512
        $numSameBytes = $networkPrefix >> 3;
111 512
        $sameBytes = array_slice($addressBytes, 0, $numSameBytes);
112 512
        $differentBytesStart = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 0);
113 512
        $differentBytesEnd = ($totalBytes === $numSameBytes) ? array() : array_fill(0, $totalBytes - $numSameBytes, 255);
114 512
        $startSameBits = $networkPrefix % 8;
115 512
        if ($startSameBits !== 0) {
116 368
            $varyingByte = $addressBytes[$numSameBytes];
117 368
            $differentBytesStart[0] = $varyingByte & bindec(str_pad(str_repeat('1', $startSameBits), 8, '0', STR_PAD_RIGHT));
118 368
            $differentBytesEnd[0] = $differentBytesStart[0] + bindec(str_repeat('1', 8 - $startSameBits));
119
        }
120
121 512
        return new static(
122 512
            Factory::addressFromBytes(array_merge($sameBytes, $differentBytesStart)),
123 512
            Factory::addressFromBytes(array_merge($sameBytes, $differentBytesEnd)),
124 512
            $networkPrefix
125
        );
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     *
131
     * @see \IPLib\Range\RangeInterface::toString()
132
     */
133 279
    public function toString($long = false)
134
    {
135 279
        return $this->fromAddress->toString($long) . '/' . $this->networkPrefix;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     *
141
     * @see \IPLib\Range\RangeInterface::getAddressType()
142
     */
143 472
    public function getAddressType()
144
    {
145 472
        return $this->fromAddress->getAddressType();
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     *
151
     * @see \IPLib\Range\RangeInterface::getStartAddress()
152
     */
153 61
    public function getStartAddress()
154
    {
155 61
        return $this->fromAddress;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     *
161
     * @see \IPLib\Range\RangeInterface::getEndAddress()
162
     */
163 5
    public function getEndAddress()
164
    {
165 5
        return $this->toAddress;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     *
171
     * @see \IPLib\Range\RangeInterface::getComparableStartString()
172
     */
173 467
    public function getComparableStartString()
174
    {
175 467
        return $this->fromAddress->getComparableString();
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     *
181
     * @see \IPLib\Range\RangeInterface::getComparableEndString()
182
     */
183 467
    public function getComparableEndString()
184
    {
185 467
        return $this->toAddress->getComparableString();
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     *
191
     * @see \IPLib\Range\RangeInterface::asSubnet()
192
     */
193 72
    public function asSubnet()
194
    {
195 72
        return $this;
196 1
    }
197
198
    /**
199 72
     * Get the pattern (asterisk) representation (if applicable) of this range.
200
     *
201
     * @return \IPLib\Range\Pattern|null return NULL if this range can't be represented by a pattern notation
202
     */
203
    public function asPattern()
204
    {
205
        $address = $this->getStartAddress();
206
        $networkPrefix = $this->getNetworkPrefix();
207 236
        switch ($address->getAddressType()) {
208 View Code Duplication
            case AddressType::T_IPv4:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
209 236
                return $networkPrefix % 8 === 0 ? new Pattern($address, $address, 4 - $networkPrefix / 8) : null;
210 View Code Duplication
            case AddressType::T_IPv6:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
211
                return $networkPrefix % 16 === 0 ? new Pattern($address, $address, 8 - $networkPrefix / 16) : null;
212
        }
213
    }
214
215
    /**
216
     * Get the 6to4 address IPv6 address range.
217 56
     *
218
     * @return self
219 56
     */
220 56
    public static function get6to4()
221 56
    {
222 56
        if (self::$sixToFour === null) {
223 18
            self::$sixToFour = self::fromString('2002::/16');
224 38
        }
225 38
226
        return self::$sixToFour;
227
    }
228
229
    /**
230
     * Get subnet prefix.
231
     *
232
     * @return int
233
     */
234 19
    public function getNetworkPrefix()
235
    {
236 19
        return $this->networkPrefix;
237 1
    }
238
239 18
    /**
240 18
     * {@inheritdoc}
241 18
     *
242 10
     * @see \IPLib\Range\RangeInterface::getSubnetMask()
243 10
     */
244
    public function getSubnetMask()
245 18
    {
246 13
        if ($this->getAddressType() !== AddressType::T_IPv4) {
247
            return null;
248 18
        }
249
        $bytes = array();
250 18
        $prefix = $this->getNetworkPrefix();
251
        while ($prefix >= 8) {
252
            $bytes[] = 255;
253
            $prefix -= 8;
254
        }
255
        if ($prefix !== 0) {
256
            $bytes[] = bindec(str_pad(str_repeat('1', $prefix), 8, '0'));
257
        }
258
        $bytes = array_pad($bytes, 4, 0);
259
260
        return IPv4::fromBytes($bytes);
261
    }
262
}
263