Total Complexity | 169 |
Total Lines | 538 |
Duplicated Lines | 0 % |
Changes | 5 | ||
Bugs | 2 | Features | 2 |
Complex classes like Decoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Decoder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
11 | class Decoder |
||
12 | { |
||
13 | protected $reader; |
||
14 | |||
15 | /** |
||
16 | * Create an instance by passing in an instantiated reader. |
||
17 | * |
||
18 | * @param Reader $reader |
||
19 | */ |
||
20 | public function __construct(Reader $reader) |
||
21 | { |
||
22 | $this->reader = $reader; |
||
23 | } |
||
24 | /** |
||
25 | * Create a new instance from an ASN1 string. |
||
26 | * |
||
27 | * @param string $data the ASN1 data |
||
28 | * @return Decoder |
||
29 | */ |
||
30 | public static function fromString($data) |
||
31 | { |
||
32 | return new static(Reader::fromString($data)); |
||
33 | } |
||
34 | /** |
||
35 | * Create a new instance from a file. |
||
36 | * |
||
37 | * @param string $path the path to the file to parse |
||
38 | * @return Decoder |
||
39 | */ |
||
40 | public static function fromFile($path) |
||
41 | { |
||
42 | return new static(Reader::fromFile($path)); |
||
43 | } |
||
44 | |||
45 | public function getReader() |
||
48 | } |
||
49 | |||
50 | protected function header() |
||
104 | ]; |
||
105 | } |
||
106 | protected function decode($header) |
||
107 | { |
||
115 | } |
||
116 | protected function decodeContents($tag, $constructed, $contents) |
||
117 | { |
||
118 | switch ($tag) { |
||
119 | case ASN1::TYPE_BOOLEAN: |
||
120 | return (bool)ord($contents[0]); |
||
121 | case ASN1::TYPE_INTEGER: |
||
122 | return ASN1::fromBase256($contents); |
||
123 | case ASN1::TYPE_ENUMERATED: |
||
124 | return (int)base_convert(ASN1::fromBase256($contents), 2, 10); |
||
125 | case ASN1::TYPE_REAL: |
||
126 | // TODO: read the specs |
||
127 | return false; |
||
128 | case ASN1::TYPE_BIT_STRING: |
||
129 | if ($constructed) { |
||
130 | $temp = static::fromString($contents)->values(); |
||
131 | $real = ''; |
||
132 | for ($i = 0; $i < count($temp) - 1; $i++) { |
||
133 | $real .= $temp['value']; |
||
134 | } |
||
135 | return $temp[count($temp) - 1]['value'][0] . $real . substr($temp[$i]['value'], 1); |
||
136 | } |
||
137 | return $contents; |
||
138 | case ASN1::TYPE_OCTET_STRING: |
||
139 | if ($constructed) { |
||
140 | return implode('', array_map(function ($v) { |
||
141 | if (!is_array($v)) { |
||
142 | return $v; |
||
143 | } |
||
144 | return $v['value']; |
||
145 | }, static::fromString($contents)->values())); |
||
146 | } |
||
147 | return $contents; |
||
148 | case ASN1::TYPE_BMP_STRING: |
||
149 | return extension_loaded("iconv") ? iconv('UCS-2BE', 'UTF-8', $contents) : $contents; |
||
150 | case ASN1::TYPE_UNIVERSAL_STRING: |
||
151 | return extension_loaded("iconv") ? iconv('UCS-4BE', 'UTF-8', $contents) : $contents; |
||
152 | case ASN1::TYPE_NULL: |
||
153 | return null; |
||
154 | case ASN1::TYPE_UTC_TIME: |
||
155 | $format = 'YmdHis'; |
||
156 | $matches = []; |
||
157 | if (preg_match('#^(\d{10})(Z|[+-]\d{4})$#', $contents, $matches)) { |
||
158 | $contents = $matches[1] . '00' . $matches[2]; |
||
159 | } |
||
160 | $prefix = substr($contents, 0, 2) >= 50 ? '19' : '20'; |
||
161 | $contents = $prefix . $contents; |
||
162 | if ($contents[strlen($contents) - 1] == 'Z') { |
||
163 | $contents = substr($contents, 0, -1) . '+0000'; |
||
164 | } |
||
165 | if (strpos($contents, '-') !== false || strpos($contents, '+') !== false) { |
||
166 | $format .= 'O'; |
||
167 | } |
||
168 | $result = @DateTime::createFromFormat($format, $contents); |
||
169 | return $result ? $result->getTimestamp() : false; |
||
170 | case ASN1::TYPE_GENERALIZED_TIME: |
||
171 | $format = 'YmdHis'; |
||
172 | if (strpos($contents, '.') !== false) { |
||
173 | $format .= '.v'; |
||
174 | } |
||
175 | if ($contents[strlen($contents) - 1] == 'Z') { |
||
176 | $contents = substr($contents, 0, -1) . '+0000'; |
||
177 | } |
||
178 | if (strpos($contents, '-') !== false || strpos($contents, '+') !== false) { |
||
179 | $format .= 'O'; |
||
180 | } |
||
181 | $result = @DateTime::createFromFormat($format, $contents); |
||
182 | return $result ? $result->format(strpos($contents, '.') !== false ? 'U.v' : 'U') : false; |
||
183 | case ASN1::TYPE_OBJECT_IDENTIFIER: |
||
184 | $temp = ord($contents[0]); |
||
185 | $real = sprintf('%d.%d', floor($temp / 40), $temp % 40); |
||
186 | $obid = 0; |
||
187 | // process septets |
||
188 | for ($i = 1; $i < strlen($contents); $i++) { |
||
189 | $temp = ord($contents[$i]); |
||
190 | $obid <<= 7; |
||
191 | $obid |= $temp & 0x7F; |
||
192 | if (~$temp & 0x80) { |
||
193 | $real .= '.' . $obid; |
||
194 | $obid = 0; |
||
195 | } |
||
196 | } |
||
197 | return $real; |
||
198 | default: |
||
199 | return $contents; |
||
200 | } |
||
201 | } |
||
202 | /** |
||
203 | * Dump the parsed structure of the ASN1 data. |
||
204 | * |
||
205 | * @param mixed $max internal - do not use |
||
206 | * @return mixed in most cases this is an array, as all complex structures are either a sequence or a set |
||
207 | */ |
||
208 | public function structure($max = null) |
||
209 | { |
||
210 | $skeleton = []; |
||
211 | while (!$this->reader->eof() && ($max === null || $this->reader->pos() < $max)) { |
||
212 | $header = $this->header(); |
||
213 | if ($header['class'] === 0 && $header['tag'] === 0) { |
||
214 | if ($max === null) { |
||
215 | break; |
||
216 | } else { |
||
217 | continue; |
||
218 | } |
||
219 | } |
||
220 | if ($header['class'] !== ASN1::CLASS_UNIVERSAL && $header['constructed']) { |
||
221 | $header['children'] = $this->structure( |
||
222 | $header['length'] ? $header['start'] + $header['length'] - 1 : null |
||
223 | ); |
||
224 | if ($header['length'] === null) { |
||
225 | $this->reader->byte(); |
||
226 | $header['length'] = $this->reader->pos() - $header['start']; |
||
227 | $header['content_length'] = $this->reader->pos() - $header['content_start']; |
||
228 | } |
||
229 | $skeleton[] = $header; |
||
230 | } else { |
||
231 | if ($header['class'] === ASN1::CLASS_UNIVERSAL && |
||
232 | in_array($header['tag'], [ASN1::TYPE_SET, ASN1::TYPE_SEQUENCE]) |
||
233 | ) { |
||
234 | $header['children'] = $this->structure( |
||
235 | $header['length'] ? $header['start'] + $header['length'] - 1 : null |
||
236 | ); |
||
237 | if ($header['length'] === null) { |
||
238 | $this->reader->byte(); |
||
239 | $header['length'] = $this->reader->pos() - $header['start']; |
||
240 | $header['content_length'] = $this->reader->pos() - $header['content_start']; |
||
241 | } |
||
242 | } else { |
||
243 | if ($header['length'] === null) { |
||
244 | $this->reader->readUntil(chr(0).chr(0)); |
||
245 | $header['length'] = $this->reader->pos() - $header['start']; |
||
246 | $header['content_length'] = $this->reader->pos() - $header['content_start']; |
||
247 | } else { |
||
248 | if ($header['content_length'] > 0) { |
||
249 | $this->reader->bytes($header['content_length']); |
||
250 | } |
||
251 | } |
||
252 | } |
||
253 | if (!isset($header['children'])) { |
||
254 | $pos = $this->reader->pos(); |
||
255 | $header['value'] = $this->decode($header); |
||
256 | $this->reader->seek($pos); |
||
257 | } |
||
258 | $skeleton[] = $header; |
||
259 | } |
||
260 | } |
||
261 | return $skeleton; |
||
262 | } |
||
263 | /** |
||
264 | * Dump the parsed values only. |
||
265 | * |
||
266 | * @param mixed $skeleton internal - do not use |
||
267 | * @return mixed in most cases this is an array, as all complex structures are either a sequence or a set |
||
268 | */ |
||
269 | public function values($skeleton = null) |
||
270 | { |
||
271 | $skeleton = $skeleton ?? $this->structure(); |
||
272 | foreach ($skeleton as $k => $v) { |
||
273 | if (isset($v['children'])) { |
||
274 | $skeleton[$k] = $this->values($v['children']); |
||
275 | } else { |
||
276 | $skeleton[$k] = $v['value'] ?? null; |
||
277 | } |
||
278 | } |
||
279 | return $skeleton; |
||
280 | } |
||
281 | /** |
||
282 | * Map the parsed data to a map |
||
283 | * |
||
284 | * @param array $map the map to use - look in the structure classes for example map arrays |
||
285 | * @param mixed $skeleton internal - do not use |
||
286 | * @return mixed in most cases this is an array, as all complex structures are either a sequence or a set |
||
287 | */ |
||
288 | public function map($map, $skeleton = null) |
||
549 | } |
||
550 | } |
||
551 | } |
||
552 | } |
||
553 |