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
Bug
introduced
by
![]() |
|||||
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
![]() |
|||||
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
![]() |
|||||
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
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
![]() |
|||||
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
![]() |
|||||
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
![]() |
|||||
168 | |||||
169 | 543 | return array_pad($bytes, -$numBytes, 0); |
|||
170 | } |
||||
171 | } |
||||
172 |