| 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 |