Address::previous()   A
last analyzed

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 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * Copyright (c) 2018 Mattheu Racine
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace mracine\IPTools\IPv4;
10
11
use OutOfBoundsException;
12
use InvalidArgumentException;
13
use DomainException;
14
use RuntimeException;
15
16
use mracine\IPTools\IPVersion;
17
18
/**
19
 * Represents an IPv4 address
20
 *
21
 * Used to represents and manipulate IPv4 addresses  
22
 * 
23
 * IMMUTABLE : once instancied, cannot be modified.
24
 * New instances are created and returned when results implies differents values. 
25
 *
26
 * @package IPTools
27
 */
28
class Address implements IPVersion
29
{
30
    /**
31
     * Classes of an address
32
     *
33
     * Obsolete for pure routing but still significant for multicat addresses (class D)
34
     * 
35
     * @see https://en.wikipedia.org/wiki/Classful_network 
36
     */ 
37
    const CLASS_A = "A",
38
          CLASS_B = "B",
39
          CLASS_C = "C",
40
          CLASS_D = "D",
41
          CLASS_E = "E";
42
43
    /**
44
     * IPv4 addresses are stored as a 32 bits integer
45
     *
46
     * @var int
47
     */
48
    protected $address = 0;
49
50
    // Implemnents version(); 
51
    use IPv4;
52
    
53
    /**
54
     * Creates an instance of Adress from an integer (0x00000000 to 0xffffffff)
55
     *
56
     * @see Address::fromInteger
57
     * @see Address::fromString
58
     * @see Address::fromArray
59
     * @param int $address 32 bits integer reprsenting the adress
60
     * @throws OutOfBoundsException when the value is negative or greater than 32 bits value
61
     * @return self
62
     */
63 9
    public function __construct(int $address)
64
    {
65 9
        if ($address<0 || $address>0xffffffff)
66
        {
67 4
            throw new OutOfBoundsException(sprintf("Cannot convert 0x%x to an IPv4 address", $address));            
68
        } 
69
70 5
        $this->address =  $address;
71 5
    }
72
73
    /**
74
     * Creates an instance of Adress from an integer (0x00000000 to 0xffffffff)
75
     *
76
     * Maintained to avoid BC-break
77
     *
78
     * @param int $address 32 bits integer reprsenting the adress
79
     * @throws OutOfBoundsException when the value is negative or greater than 32 bits value
80
     * @return self
81
     */
82
83 3
    public static function fromInteger(int $address)
84
    {
85 3
        return new static($address);
86
    }
87
88
89
    /**
90
     * Creates an instance of Address from an array.
91
     *
92
     * The array must contains excly 4 elements (strings or integer).
93
     * Each elemnt represents a digit (byte) of the dotted quad notation ([192,168,1,10])
94
     *
95
     * @param (int|string)[] $address
96
     * @throws InvalidArgumentException when the array doesn not contains four elements
97
     * @throws InvalidArgumentException when a string element is empty 
98
     * @throws InvalidArgumentException when a string element is not a pure integer representation 
99
     * @throws InvalidArgumentException when an element is not a string nor an integer 
100
     * @throws OutOfBoundsException when a string element represent a negative value
101
     * @throws OutOfBoundsException when an element is negative or greater than 32 bits integer
102
     * @return self
103
     */
104
105 22
    public static function fromArray(Array $address)
106
    {
107 22
        if( count($address) != 4)
108
        {
109 5
            throw new InvalidArgumentException(sprintf("Array must contain 4 digits, %d found", count($address)));
110
        }
111
112 17
        $buffer = 0;
113 17
        foreach($address as $digit)
114
        {
115 17
            if (is_string($digit) )
116
            {        
117 6
                $digit = trim($digit);
118 6
                if (strlen($digit)==0)
119
                {
120 1
                    throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , string found with empty value"));
121
                }
122 5
                if ( $digit[0] == '-' && ctype_digit(ltrim($digit, '-')) )
123
                {
124
                    // string type : "-123"
125 1
                    throw new OutOfBoundsException(sprintf("Cannot convert %d to Ipv4 addresss digit", $digit));
126
                }
127 4
                if (!ctype_digit($digit))
128
                {
129 1
                    throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , string found with bad value %s", $digit));
130
                }
131
                // Here the string contains only numbers, can be casted to int safely
132 3
                $digit = (int)$digit;
133
            }
134
            
135 17
            if (!is_integer($digit) )
136
            {
137 1
                throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , %s found", gettype($digit)));
138
            }
139
140 17
            if($digit<0 || $digit>0xff)
141
            {
142 5
                throw new OutOfBoundsException(sprintf("Cannot convert %d to Ipv4 addresss digit", $digit));
143
144
            }
145
            // shift adress left from one byte (8 bits), then add digit (the last byte)
146 16
            $buffer = ($buffer << 8) | $digit;
147
        }
148 8
        return static::fromInteger($buffer);
149
    }
150
151
    /**
152
     * Creates an instance of Address from a dotted quad formated string ("#.#.#.#"")
153
     *
154
     * @param string $address
155
     * @throws InvalidFormatException
156
     * @return self
157
     */
158 22
    public static function fromString(string $address)
159
    {
160 22
        return static::fromArray(explode('.', $address));
161
    }
162
163
    /**
164
     * Get the default 'dotted-quad' representation of the IPv4 address ("#.#.#.#")
165
     *
166
     * @return string
167
     */
168 2
    public function asDotQuad()
169
    {
170 2
        return sprintf(
171 2
            "%u.%u.%u.%u",
172 2
            (($this->address & (0xff << 24)) >> 24),
173 2
            (($this->address & (0xff << 16)) >> 16),
174 2
            (($this->address & (0xff << 8)) >> 8),
175 2
            ($this->address & 0xff)
176
        );
177
    }
178
179
    /**
180
     * Get the integer value of the IPv4 address between 0 to 0xffffffff
181
     *
182
     * @return int
183
     */
184
185 5
    public function asInteger()
186
    {
187 5
        return $this->address;
188
    }
189
190
    /**
191
     * Get the integer value of the IPv4 address between 0 to 0xffffffff
192
     *
193
     * Just a helper/short way to call asInteger()
194
     *
195
     * @see Address::asInteger()
196
     * @return int
197
     */
198
199 5
    public function int()
200
    {
201 5
        return $this->asInteger();
202
    }
203
204
    /**
205
     * Get a string representation of address
206
     *
207
     * Automagically called when PHP needs to convert an address to a string.
208
     * I choose to return the default 'dotted-quad' representation of the IPv4 address ("#.#.#.#")
209
     *
210
     * @return string
211
     */
212 2
    public function __toString()
213
    {
214 2
        return $this->asDotQuad();
215
    }
216
217
    /**
218
     * Tests if two addresses are equivalent (same value)
219
     *
220
     * @param self $address
221
     * @return bool
222
     */
223 7
     public function match(Address $address)
224
    {
225 7
        return $this->int() == $address->int();
226
    }
227
228
    /**
229
     * Tells if the address is contained in a range
230
     *
231
     * Return true if the provided address is between the boundaries of the range.  
232
     *
233
     * @param Range $container 
234
     * @return bool
235
     */
236 3
    public function isIn(Range $container)
237
    {
238 3
        return ($container->getLowerBound()->int()<=$this->int()) && ($this->int()<=$container->getUpperBound()->int());
239
    }
240
241
242
    /**
243
     * Get the class of the IP address (for obsolete classfull routing)
244
     *
245
     * @return string a constant CLASS_[A-E]
246
     */
247 13
    public function getClass()
248
    {
249 13
        $higherOctet = $this->address >> 24;
250
251 13
        if (($higherOctet & 0x80) == 0) {
252 5
            return self::CLASS_A;
253
        }
254
255 8
        if (($higherOctet & 0xC0) == 0x80) {
256 2
            return self::CLASS_B;
257
        }
258
259 6
        if (($higherOctet & 0xE0) == 0xC0) {
260 2
            return self::CLASS_C;
261
        }
262
263 4
        if (($higherOctet & 0xF0) == 0xE0) {
264 2
            return self::CLASS_D;
265
        }
266
267 2
        if (($higherOctet & 0xF0) == 0xF0) {
268 2
            return self::CLASS_E;
269
        }
270
271
        // Should never be triggered
272
        // @codeCoverageIgnoreStart
273
        throw new RuntimeException();
274
        // @codeCoverageIgnoreEnd
275
    }
276
277
    /**
278
     * Returns whether the address is part of multicast address range
279
     * 
280
     * @return bool 
281
     */
282 8
    public function isMulticast()
283
    {
284 8
        return $this->getClass() === self::CLASS_D;
285
    }
286
287
    /**
288
     * Returns whether the address is part of the subnets defined in RFC 1918
289
     *
290
     * RFC1918 defines privates, non Internet routables subnets for end users's privates network
291
     * This subnets are :
292
     *  - 10.0.0.0/8
293
     *  - 172.16.0.0/12
294
     *  - 192.168.0.0/16
295
     * This subnets should be used by end users to build their privates networks.
296
     *
297
     * @see https://tools.ietf.org/html/rfc1918
298
     * @return bool
299
     */
300 14
    public function isRFC1918()
301
    {
302
        /** @var $subnets Subnet[] */
303
        $subnets = array(
304 14
            Subnet::fromCidr(Address::fromString("10.0.0.0"), 8),
305 14
            Subnet::fromCidr(Address::fromString("172.16.0.0"), 12),
306 14
            Subnet::fromCidr(Address::fromString("192.168.0.0"), 16),
307
        );
308 14
        foreach($subnets as $subnet) {
309 14
            if($this->isIn($subnet)) {
310 3
                return true;
311
            }
312
        }
313 11
        return false;
314
    }
315
316
    /**
317
     * Returns whether the address is part of the subnets defined in RFC 6598
318
     *
319
     * RFC6598 defines a private, non Internet routable subnet for Carrir Grade Networks, mainly used by FAI or non end users network providers 
320
     * This subnet is 100.64.0.0/10
321
     * This subnet should not be used by end users
322
     *
323
     * @see https://tools.ietf.org/html/rfc6598
324
     * @return bool
325
     */
326 14
    public function isRFC6598()
327
    {
328 14
        $subnet = Subnet::fromCidr(Address::fromString("100.64.0.0"), 10);
329 14
        return $this->isIn($subnet);
330
    }
331
332
    /**
333
     * Returns whether the address is part of non Internet routable subnets
334
     * 
335
     * @todo improve: multicast, other on routables, ... ?
336
     * @return bool 
337
     */
338 14
    public function isPrivate()
339
    {
340 14
        return ($this->isRFC1918()  || $this->isRFC6598());
341
    }
342
343
    /**
344
     * Returns an address shifted by an amount of units 
345
     *
346
     * NOTICE : A new instance of address is instanciated, does not shitf the instance used.
347
     *
348
     * @param int $offset 
349
     * @throws OutOfBoundsException when the resulting address is out of the bounds (negative or greater than 32 bits value)
350
     * @return self
351
     */
352 14
    public function shift(int $offset)
353
    {
354 14
        return new static($this->address+$offset);
355
    }
356
357
    /**
358
     * Returns the address immediately following this address 
359
     *
360
     * Examples :
361
     *   - "1.2.3.4" => "1.2.3.5"
362
     *   - "1.2.3.255" => "1.2.4.0"
363
     *
364
     *  NOTICE : A new instance of address is instanciated, does not modify the instance used.
365
     *
366
     * @throws OutOfBoundsException when the resulting address is out of the bounds (more than 32 bits value). Happend only if try to get next of "255.255.255.255"
367
     * @return self
368
     */
369 11
    public function next()
370
    {
371 11
        return $this->shift(1);
372
    }
373
374
    /**
375
     * Returns the address immediately preceding this address 
376
     *
377
     * Examples :
378
     *   - "1.2.3.5" => "1.2.3.4"
379
     *   - "1.2.4.0" => "1.2.3.255"
380
     *
381
     * NOTICE : A new instance of address is instanciated, does not modify the instance used.
382
     *
383
     * @throws OutOfBoundsException when the resulting address is out of the bounds (less than 0). Happend only if try to get previous of "0.0.0.0"
384
     * @return self
385
     */
386 11
    public function previous()
387
    {
388 11
        return $this->shift(-1);
389
    }
390
}