Passed
Push — master ( 4f3302...913bce )
by Matthieu
01:55
created

Address::__toString()   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 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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::fromCidr
59
     * @see Address::fromArray
60
     * @param int $address 32 bits integer reprsenting the adress
61
     * @throws OutOfBoundsException when the value is negative or greater than 32 bits value
62
     * @return self
63
     */
64 9
    public function __construct(int $address)
65
    {
66 9
        if ($address<0 || $address>0xffffffff)
67
        {
68 4
            throw new OutOfBoundsException(sprintf("Cannot convert 0x%x to an IPv4 address", $address));            
69
        } 
70
71 5
        $this->address =  $address;
72 5
    }
73
74
    /**
75
     * Creates an instance of Adress from an integer (0x00000000 to 0xffffffff)
76
     *
77
     * Maintained to avoid BC-break
78
     *
79
     * @param int $address 32 bits integer reprsenting the adress
80
     * @throws OutOfBoundsException when the value is negative or greater than 32 bits value
81
     * @return self
82
     */
83
84 3
    public static function fromInteger(int $address)
85
    {
86 3
        return new static($address);
87
    }
88
89
90
    /**
91
     * Creates an instance of Address from an array.
92
     *
93
     * The array must contains excly 4 elements (strings or integer).
94
     * Each elemnt represents a digit (byte) of the dotted quad notation ([192,168,1,10])
95
     *
96
     * @param (int|string)[] $address
97
     * @throws InvalidArgumentException when the array doesn not contains four elements
98
     * @throws InvalidArgumentException when a string element is empty 
99
     * @throws InvalidArgumentException when a string element is not a pure integer representation 
100
     * @throws InvalidArgumentException when an element is not a string nor an integer 
101
     * @throws OutOfBoundsException when a string element represent a negative value
102
     * @throws OutOfBoundsException when an element is negative or greater than 32 bits integer
103
     * @return self
104
     */
105
106 22
    public static function fromArray(Array $address)
107
    {
108 22
        if( count($address) != 4)
109
        {
110 5
            throw new InvalidArgumentException(sprintf("Array must contain 4 digits, %d found", count($address)));
111
        }
112
113 17
        $buffer = 0;
114 17
        foreach($address as $digit)
115
        {
116 17
            if (is_string($digit) )
117
            {        
118 6
                $digit = trim($digit);
119 6
                if (strlen($digit)==0)
120
                {
121 1
                    throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , string found with empty value"));
122
                }
123 5
                if ( $digit[0] == '-' && ctype_digit(ltrim($digit, '-')) )
124
                {
125
                    // string type : "-123"
126 1
                    throw new OutOfBoundsException(sprintf("Cannot convert %d to Ipv4 addresss digit", $digit));
127
                }
128 4
                if (!ctype_digit($digit))
129
                {
130 1
                    throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , string found with bad value %s", $digit));
131
                }
132
                // Here the string contains only numbers, can be casted to int safely
133 3
                $digit = (int)$digit;
134
            }
135
            
136 17
            if (!is_integer($digit) )
137
            {
138 1
                throw new InvalidArgumentException(sprintf("The array must contains only integers or strings with integer content , %s found", gettype($digit)));
139
            }
140
141 17
            if($digit<0 || $digit>0xff)
142
            {
143 5
                throw new OutOfBoundsException(sprintf("Cannot convert %d to Ipv4 addresss digit", $digit));
144
145
            }
146
            // shift adress left from one byte (8 bits), then add digit (the last byte)
147 16
            $buffer = ($buffer << 8) | $digit;
148
        }
149 8
        return static::fromInteger($buffer);
150
    }
151
152
    /**
153
     * Creates an instance of Address from a dotted quad formated string ("#.#.#.#"")
154
     *
155
     * @param string $address
156
     * @throws InvalidFormatException
157
     * @return self
158
     */
159 22
    public static function fromString(string $address)
160
    {
161 22
        return static::fromArray(explode('.', $address));
162
    }
163
164
    /**
165
     * Get the default 'dotted-quad' representation of the IPv4 address ("#.#.#.#")
166
     *
167
     * @return string
168
     */
169 2
    public function asDotQuad()
170
    {
171 2
        return sprintf(
172 2
            "%u.%u.%u.%u",
173 2
            (($this->address & (0xff << 24)) >> 24),
174 2
            (($this->address & (0xff << 16)) >> 16),
175 2
            (($this->address & (0xff << 8)) >> 8),
176 2
            ($this->address & 0xff)
177
        );
178
    }
179
180
    /**
181
     * Get the integer value of the IPv4 address between 0 to 0xffffffff
182
     *
183
     * @return int
184
     */
185
186 5
    public function asInteger()
187
    {
188 5
        return $this->address;
189
    }
190
191
    /**
192
     * Get the integer value of the IPv4 address between 0 to 0xffffffff
193
     *
194
     * Just a helper/short way to call asInteger()
195
     *
196
     * @see Address::asInteger()
197
     * @return int
198
     */
199
200 5
    public function int()
201
    {
202 5
        return $this->asInteger();
203
    }
204
205
    /**
206
     * Get the CIDR integer value (0-32) from the adress if possible (eg : "255.255.255.0" returns 24)
207
     *
208
     * @see Address::fromCidr()
209
     * @throws DomainException when the address cannot be converted to CIDR
210
     * @return integer A value beteween 0 to 32  
211
     */
212
    // public function asCidr()
213
    // {
214
    //     // Pas très élégant.... 
215
    //     for ($cidr=32 ; $cidr>=0 ; $cidr--)
216
    //     {
217
    //         $n = (0xffffffff << (32 - $cidr)) & 0xffffffff;
218
    //         if( $n == $this->address )
219
    //         {
220
    //             return $cidr;
221
    //         }
222
    //     }
223
    //     throw new DomainException(sprintf("Cannot convert address %s to CIDR, not a netmask", (string)$this));
224
    // }
225
226
227
    /**
228
     * Get a string representation of address
229
     *
230
     * Automagically called when PHP needs to convert an address to a string.
231
     * I choose to return the default 'dotted-quad' representation of the IPv4 address ("#.#.#.#")
232
     *
233
     * @return string
234
     */
235 2
    public function __toString()
236
    {
237 2
        return $this->asDotQuad();
238
    }
239
240
    /**
241
     * Tests if two addresses are equivalent (same value)
242
     *
243
     * @param self $address
244
     * @return bool
245
     */
246 7
     public function match(Address $address)
247
    {
248 7
        return $this->int() == $address->int();
249
    }
250
251
    /**
252
     * Tells if the address is contained in a range
253
     *
254
     * Return true if the provided address is between the boundaries of the range.  
255
     *
256
     * @param Range $container 
257
     * @return bool
258
     */
259 3
    public function isIn(Range $container)
260
    {
261 3
        return ($container->getLowerBound()->int()<=$this->int()) && ($this->int()<=$container->getUpperBound()->int());
262
    }
263
264
265
    /**
266
     * Get the class of the IP address (for obsolete classfull routing)
267
     *
268
     * @return string a constant CLASS_[A-E]
269
     */
270 13
    public function getClass()
271
    {
272 13
        $higherOctet = $this->address >> 24;
273
274 13
        if (($higherOctet & 0x80) == 0) {
275 5
            return self::CLASS_A;
276
        }
277
278 8
        if (($higherOctet & 0xC0) == 0x80) {
279 2
            return self::CLASS_B;
280
        }
281
282 6
        if (($higherOctet & 0xE0) == 0xC0) {
283 2
            return self::CLASS_C;
284
        }
285
286 4
        if (($higherOctet & 0xF0) == 0xE0) {
287 2
            return self::CLASS_D;
288
        }
289
290 2
        if (($higherOctet & 0xF0) == 0xF0) {
291 2
            return self::CLASS_E;
292
        }
293
294
        // Should never be triggered
295
        // @codeCoverageIgnoreStart
296
        throw new RuntimeException();
297
        // @codeCoverageIgnoreEnd
298
    }
299
300
    /**
301
     * Returns whether the address is part of multicast address range
302
     * 
303
     * @return bool 
304
     */
305 8
    public function isMulticast()
306
    {
307 8
        return $this->getClass() === self::CLASS_D;
308
    }
309
310
    /**
311
     * Returns whether the address is part of the subnets defined in RFC 1918
312
     *
313
     * RFC1918 defines privates, non Internet routables subnets for end users's privates network
314
     * This subnets are :
315
     *  - 10.0.0.0/8
316
     *  - 172.16.0.0/12
317
     *  - 192.168.0.0/16
318
     * This subnets should be used by end users to build their privates networks.
319
     *
320
     * @see https://tools.ietf.org/html/rfc1918
321
     * @return bool
322
     */
323 14
    public function isRFC1918()
324
    {
325
        /** @var $subnets Subnet[] */
326
        $subnets = array(
327 14
            Subnet::fromCidr(Address::fromString("10.0.0.0"), 8),
328 14
            Subnet::fromCidr(Address::fromString("172.16.0.0"), 12),
329 14
            Subnet::fromCidr(Address::fromString("192.168.0.0"), 16),
330
        );
331 14
        foreach($subnets as $subnet) {
332 14
            if($subnet->contains($this)) {
0 ignored issues
show
Deprecated Code introduced by
The function mracine\IPTools\IPv4\Range::contains() has been deprecated: Use Address::isIn ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

332
            if(/** @scrutinizer ignore-deprecated */ $subnet->contains($this)) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
333 3
                return true;
334
            }
335
        }
336 11
        return false;
337
    }
338
339
    /**
340
     * Returns whether the address is part of the subnets defined in RFC 6598
341
     *
342
     * RFC6598 defines a private, non Internet routable subnet for Carrir Grade Networks, mainly used by FAI or non end users network providers 
343
     * This subnet is 100.64.0.0/10
344
     * This subnet should not be used by end users
345
     *
346
     * @see https://tools.ietf.org/html/rfc6598
347
     * @return bool
348
     */
349 14
    public function isRFC6598()
350
    {
351 14
        $subnet = Subnet::fromCidr(Address::fromString("100.64.0.0"), 10);
352 14
        return $subnet->contains($this) ? true : false;
0 ignored issues
show
Deprecated Code introduced by
The function mracine\IPTools\IPv4\Range::contains() has been deprecated: Use Address::isIn ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

352
        return /** @scrutinizer ignore-deprecated */ $subnet->contains($this) ? true : false;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
353
    }
354
355
    /**
356
     * Returns whether the address is part of non Internet routable subnets
357
     * 
358
     * @todo improve: multicast, other on routables, ... ?
359
     * @return bool 
360
     */
361 14
    public function isPrivate()
362
    {
363 14
        return ($this->isRFC1918()  || $this->isRFC6598());
364
    }
365
366
    /**
367
     * Returns an address shifted by an amount of units 
368
     *
369
     * NOTICE : A new instance of address is instanciated, does not shitf the instance used.
370
     *
371
     * @param int $offset 
372
     * @throws OutOfBoundsException when the resulting address is out of the bounds (negative or greater than 32 bits value)
373
     * @return self
374
     */
375 14
    public function shift(int $offset)
376
    {
377 14
        return new static($this->address+$offset);
378
    }
379
380
    /**
381
     * Returns the address immediately following this address 
382
     *
383
     * Examples :
384
     *   - "1.2.3.4" => "1.2.3.5"
385
     *   - "1.2.3.255" => "1.2.4.0"
386
     *
387
     *  NOTICE : A new instance of address is instanciated, does not modify the instance used.
388
     *
389
     * @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"
390
     * @return self
391
     */
392 11
    public function next()
393
    {
394 11
        return $this->shift(1);
395
    }
396
397
    /**
398
     * Returns the address immediately preceding this address 
399
     *
400
     * Examples :
401
     *   - "1.2.3.5" => "1.2.3.4"
402
     *   - "1.2.4.0" => "1.2.3.255"
403
     *
404
     * NOTICE : A new instance of address is instanciated, does not modify the instance used.
405
     *
406
     * @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"
407
     * @return self
408
     */
409 11
    public function previous()
410
    {
411 11
        return $this->shift(-1);
412
    }
413
}