Issues (37)

src/Service/UnsignedIntegerMath.php (6 issues)

1
<?php
2
3
namespace IPLib\Service;
4
5
/**
6
 * Helper class to work with unsigned integers.
7
 *
8
 * @internal
9
 */
10
class UnsignedIntegerMath
11
{
12
    /**
13
     * Convert a string containing a decimal, octal or hexadecimal number into its bytes.
14
     *
15
     * @param string $value
16
     * @param int $numBytes the wanted number of bytes
17
     * @param bool $onlyDecimal Only parse decimal numbers
18
     *
19
     * @return int[]|null
20
     */
21 2538
    public function getBytes($value, $numBytes, $onlyDecimal = false)
22
    {
23 2538
        $m = null;
24 2538
        if ($onlyDecimal) {
25 948
            if (preg_match('/^0*(\d+)$/', $value, $m)) {
26 948
                return $this->getBytesFromDecimal($m[1], $numBytes);
27
            }
28
        } else {
29 1602
            if (preg_match('/^0[Xx]0*([0-9A-Fa-f]+)$/', $value, $m)) {
30 544
                return $this->getBytesFromHexadecimal($m[1], $numBytes);
31
            }
32 1081
            if (preg_match('/^0+([0-7]*)$/', $value, $m)) {
33 555
                return $this->getBytesFromOctal($m[1], $numBytes);
34
            }
35 552
            if (preg_match('/^[1-9][0-9]*$/', $value)) {
36 547
                return $this->getBytesFromDecimal($value, $numBytes);
37
            }
38
        }
39
40
        // Not a valid number
41 6
        return null;
42
    }
43
44
    /**
45
     * @return int
46
     */
47 1484
    protected function getMaxSignedInt()
48
    {
49 1484
        return PHP_INT_MAX;
50
    }
51
52
    /**
53
     * @param string $value never zero-length, never extra leading zeroes
54
     * @param int $numBytes
55
     *
56
     * @return int[]|null
57
     */
58 2007
    private function getBytesFromBits($value, $numBytes)
59
    {
60 2007
        $valueLength = strlen($value);
61 2007
        if ($valueLength > $numBytes << 3) {
62
            // overflow
63 11
            return null;
64
        }
65 2001
        $remainderBits = $valueLength % 8;
66 2001
        if ($remainderBits !== 0) {
67 1418
            $value = str_pad($value, $valueLength + 8 - $remainderBits, '0', STR_PAD_LEFT);
68
        }
69 2001
        $bytes = array_map('bindec', str_split($value, 8));
0 ignored issues
show
It seems like str_split($value, 8) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

69
        $bytes = array_map('bindec', /** @scrutinizer ignore-type */ str_split($value, 8));
Loading history...
70
71 2001
        return array_pad($bytes, -$numBytes, 0);
72
    }
73
74
    /**
75
     * @param string $value may be zero-length, never extra leading zeroes
76
     * @param int $numBytes
77
     *
78
     * @return int[]|null
79
     */
80 555
    private function getBytesFromOctal($value, $numBytes)
81
    {
82 555
        if ($value === '') {
83 25
            return array_fill(0, $numBytes, 0);
84
        }
85 540
        $bits = implode(
86 540
            '',
87 540
            array_map(
88
                function ($octalDigit) {
89 540
                    return str_pad(decbin(octdec($octalDigit)), 3, '0', STR_PAD_LEFT);
0 ignored issues
show
It seems like octdec($octalDigit) can also be of type double; however, parameter $num of decbin() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

89
                    return str_pad(decbin(/** @scrutinizer ignore-type */ octdec($octalDigit)), 3, '0', STR_PAD_LEFT);
Loading history...
90 540
                },
91 540
                str_split($value, 1)
0 ignored issues
show
It seems like str_split($value, 1) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

91
                /** @scrutinizer ignore-type */ str_split($value, 1)
Loading history...
92
            )
93
        );
94 540
        $bits = ltrim($bits, '0');
95
96 540
        return $bits === '' ? array_fill(0, $numBytes, 0) : static::getBytesFromBits($bits, $numBytes);
0 ignored issues
show
Bug Best Practice introduced by
The method IPLib\Service\UnsignedIn...ath::getBytesFromBits() is not static, but was called statically. ( Ignorable by Annotation )

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

96
        return $bits === '' ? array_fill(0, $numBytes, 0) : static::/** @scrutinizer ignore-call */ getBytesFromBits($bits, $numBytes);
Loading history...
97
    }
98
99
    /**
100
     * @param string $value never zero-length, never extra leading zeroes
101
     * @param int $numBytes
102
     *
103
     * @return int[]|null
104
     */
105 1484
    private function getBytesFromDecimal($value, $numBytes)
106
    {
107 1484
        $valueLength = strlen($value);
108 1484
        $maxSignedIntLength = strlen((string) $this->getMaxSignedInt());
109 1484
        if ($valueLength < $maxSignedIntLength) {
110 1484
            return $this->getBytesFromBits(decbin((int) $value), $numBytes);
111
        }
112
        // Divide by two, so that we have 1 less bit
113 4
        $carry = 0;
114 4
        $halfValue = ltrim(
115 4
            implode(
116 4
                '',
117 4
                array_map(
118
                    function ($digit) use (&$carry) {
119 4
                        $number = $carry + (int) $digit;
120 4
                        $carry = ($number % 2) * 10;
121
122 4
                        return (string) $number >> 1;
123 4
                    },
124 4
                    str_split($value, 1)
0 ignored issues
show
It seems like str_split($value, 1) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

124
                    /** @scrutinizer ignore-type */ str_split($value, 1)
Loading history...
125
                )
126
            ),
127 4
            '0'
128
        );
129 4
        $halfValueBytes = $this->getBytesFromDecimal($halfValue, $numBytes);
130 4
        if ($halfValueBytes === null) {
131 1
            return null;
132
        }
133 3
        $carry = $carry === 0 ? 0 : 1;
134 3
        $result = array_fill(0, $numBytes, 0);
135 3
        for ($index = $numBytes - 1; $index >= 0; $index--) {
136 3
            $byte = $carry + ($halfValueBytes[$index] << 1);
137 3
            if ($byte <= 0xFF) {
138 3
                $carry = 0;
139
            } else {
140 3
                $carry = ($byte & ~0xFF) >> 8;
141 3
                $byte -= 0x100;
142
            }
143 3
            $result[$index] = $byte;
144
        }
145 3
        if ($carry !== 0) {
146
            // Overflow
147 1
            return null;
148
        }
149
150 3
        return $result;
151
    }
152
153
    /**
154
     * @param string $value never zero-length, never extra leading zeroes
155
     * @param int $numBytes
156
     *
157
     * @return int[]|null
158
     */
159 544
    private function getBytesFromHexadecimal($value, $numBytes)
160
    {
161 544
        $valueLength = strlen($value);
162 544
        if ($valueLength > $numBytes << 1) {
163
            // overflow
164 1
            return null;
165
        }
166 543
        $value = str_pad($value, $valueLength + $valueLength % 2, '0', STR_PAD_LEFT);
167 543
        $bytes = array_map('hexdec', str_split($value, 2));
0 ignored issues
show
It seems like str_split($value, 2) can also be of type true; however, parameter $array of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

167
        $bytes = array_map('hexdec', /** @scrutinizer ignore-type */ str_split($value, 2));
Loading history...
168
169 543
        return array_pad($bytes, -$numBytes, 0);
170
    }
171
}
172