Completed
Pull Request — master (#62)
by Michele
02:31
created

RangesFromBoundaryCalculator::subnetFromBits()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
namespace IPLib\Service;
4
5
use IPLib\Address\AddressInterface;
6
use IPLib\Factory;
7
use IPLib\Range\Subnet;
8
9
/**
10
 * Helper class to calculate the subnets describing all (and only all) the addresses between two bouundaries.
11
 */
12
class RangesFromBoundaryCalculator
13
{
14
    /**
15
     * The BinaryMath instance to be used to perform bitwise poerations.
16
     *
17
     * @var \IPLib\Service\BinaryMath
18
     */
19
    private $math;
20
21
    /**
22
     * The number of bits used to represent addresses.
23
     *
24
     * @var int
25
     *
26
     * @example 32 for IPv4, 128 for IPv6
27
     */
28
    private $numBits;
29
30
    /**
31
     * The bit masks for every bit index.
32
     *
33
     * @var string[]
34
     */
35
    private $masks;
36
37
    /**
38
     * The bit unmasks for every bit index.
39
     *
40
     * @var string[]
41
     */
42
    private $unmasks;
43
44
    /**
45
     * Initializes the instance.
46
     *
47
     * @param int $numBits the number of bits used to represent addresses (32 for IPv4, 128 for IPv6)
48
     */
49 25
    public function __construct($numBits)
50
    {
51 25
        $this->math = new BinaryMath();
52 25
        $this->setNumBits($numBits);
53 25
    }
54
55
    /**
56
     * Calculate the subnets describing all (and only all) the addresses between two bouundaries.
57
     *
58
     * @param \IPLib\Address\AddressInterface $from
59
     * @param \IPLib\Address\AddressInterface $to
60
     *
61
     * @return \IPLib\Range\Subnet[]|null return NULL if the two addresses have an invalid number of bits (that is, different from the one passed to the constructor of this class)
62
     */
63 43
    public function getRanges(AddressInterface $from, AddressInterface $to)
64
    {
65 43
        if ($from->getNumberOfBits() !== $this->numBits || $to->getNumberOfBits() !== $this->numBits) {
66 6
            return null;
67
        }
68 37
        if ($from->getComparableString() > $to->getComparableString()) {
69 2
            list($from, $to) = array($to, $from);
70
        }
71 37
        $result = array();
72 37
        $this->calculate($this->math->reduce($from->getBits()), $this->math->reduce($to->getBits()), $this->numBits, $result);
73
74 37
        return $result;
75
    }
76
77
    /**
78
     * Set the number of bits used to represent addresses (32 for IPv4, 128 for IPv6).
79
     *
80
     * @param int $numBits
81
     */
82 25
    private function setNumBits($numBits)
83
    {
84 25
        $numBits = (int) $numBits;
85 25
        $masks = array();
86 25
        $unmasks = array();
87 25
        for ($bit = 0; $bit < $numBits; $bit++) {
88 25
            $masks[$bit] = str_repeat('1', $numBits - $bit) . str_repeat('0', $bit);
89 25
            $unmasks[$bit] = $bit === 0 ? '0' : str_repeat('1', $bit);
90
        }
91 25
        $this->numBits = $numBits;
92 25
        $this->masks = $masks;
93 25
        $this->unmasks = $unmasks;
94 25
    }
95
96
    /**
97
     * Calculate the subnets.
98
     *
99
     * @param string $start the start address (represented in reduced bit form)
100
     * @param string $end the end address (represented in reduced bit form)
101
     * @param int $position the number of bits in the mask we are comparing at this cycle
102
     * @param \IPLib\Range\Subnet[] $result found ranges will be added to this variable
103
     */
104 37
    private function calculate($start, $end, $position, array &$result)
105
    {
106 37
        if ($start === $end) {
107 21
            $result[] = $this->subnetFromBits($start, $this->numBits);
108
109 21
            return;
110
        }
111 31
        for ($index = $position - 1; $index >= 0; $index--) {
112 31
            $startMasked = $this->math->andX($start, $this->masks[$index]);
113 31
            $endMasked = $this->math->andX($end, $this->masks[$index]);
114 31
            if ($startMasked !== $endMasked) {
115 31
                $position = $index;
116 31
                break;
117
            }
118
        }
119 31
        if ($startMasked === $start && $this->math->andX($this->math->increment($end), $this->unmasks[$position]) === '0') {
0 ignored issues
show
Bug introduced by
The variable $startMasked does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
120 26
            $result[] = $this->subnetFromBits($start, $this->numBits - 1 - $position);
121
122 26
            return;
123
        }
124 16
        $middleAddress = $this->math->orX($start, $this->unmasks[$position]);
125 16
        $this->calculate($start, $middleAddress, $position, $result);
126 16
        $this->calculate($this->math->increment($middleAddress), $end, $position, $result);
127 16
    }
128
129
    /**
130
     * Create an address instance starting from its bits.
131
     *
132
     * @param string $bits the bits of the address (represented in reduced bit form)
133
     *
134
     * @return \IPLib\Address\AddressInterface
135
     */
136 37
    private function addressFromBits($bits)
137
    {
138 37
        $bits = str_pad($bits, $this->numBits, '0', STR_PAD_LEFT);
139 37
        $bytes = array();
140 37
        foreach (explode("\n", trim(chunk_split($bits, 8, "\n"))) as $byteBits) {
141 37
            $bytes[] = bindec($byteBits);
142
        }
143
144 37
        return Factory::addressFromBytes($bytes);
145
    }
146
147
    /**
148
     * Create an range instance starting from the bits if the address and the length of the network prefix.
149
     *
150
     * @param string $bits the bits of the address (represented in reduced bit form)
151
     * @param int $networkPrefix the length of the network prefix
152
     *
153
     * @return \IPLib\Range\Subnet
154
     */
155 37
    private function subnetFromBits($bits, $networkPrefix)
156
    {
157 37
        $address = $this->addressFromBits($bits);
158
159 37
        return new Subnet($address, $address, $networkPrefix);
0 ignored issues
show
Bug introduced by
It seems like $address defined by $this->addressFromBits($bits) on line 157 can also be of type null; however, IPLib\Range\Subnet::__construct() does only seem to accept object<IPLib\Address\AddressInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
160
    }
161
}
162