GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Branch php72 (57c34e)
by Joni
05:01
created

Element   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 188
dl 0
loc 464
ccs 106
cts 106
cp 1
rs 9.0399
c 0
b 0
f 0
wmc 42

18 Methods

Rating   Name   Duplication   Size   Complexity  
A expectTagged() 0 12 4
A expectType() 0 8 2
A toDER() 0 14 3
A fromDER() 0 31 5
A hasIndefiniteLength() 0 3 1
A _typeDescriptorString() 0 7 2
A _isConcreteType() 0 14 4
A asUnspecified() 0 3 1
A isType() 0 11 3
A _isPseudoType() 0 9 3
A _determineUniversalImplClass() 0 7 2
A withIndefiniteLength() 0 5 1
A tagToName() 0 6 2
A isTagged() 0 3 1
A asElement() 0 3 1
A _determineImplClass() 0 16 5
A _decodeFromDER() 0 5 1
A tag() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Element 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 Element, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\ASN1;
6
7
use Sop\ASN1\Component\Identifier;
8
use Sop\ASN1\Component\Length;
9
use Sop\ASN1\Exception\DecodeException;
10
use Sop\ASN1\Feature\ElementBase;
11
use Sop\ASN1\Type\Constructed;
12
use Sop\ASN1\Type\Primitive;
13
use Sop\ASN1\Type\StringType;
14
use Sop\ASN1\Type\Tagged\ApplicationType;
15
use Sop\ASN1\Type\Tagged\ContextSpecificType;
16
use Sop\ASN1\Type\Tagged\PrivateType;
17
use Sop\ASN1\Type\TaggedType;
18
use Sop\ASN1\Type\TimeType;
19
use Sop\ASN1\Type\UnspecifiedType;
20
21
/**
22
 * Base class for all ASN.1 type elements.
23
 */
24
abstract class Element implements ElementBase
25
{
26
    // Universal type tags
27
    const TYPE_EOC = 0x00;
28
    const TYPE_BOOLEAN = 0x01;
29
    const TYPE_INTEGER = 0x02;
30
    const TYPE_BIT_STRING = 0x03;
31
    const TYPE_OCTET_STRING = 0x04;
32
    const TYPE_NULL = 0x05;
33
    const TYPE_OBJECT_IDENTIFIER = 0x06;
34
    const TYPE_OBJECT_DESCRIPTOR = 0x07;
35
    const TYPE_EXTERNAL = 0x08;
36
    const TYPE_REAL = 0x09;
37
    const TYPE_ENUMERATED = 0x0a;
38
    const TYPE_EMBEDDED_PDV = 0x0b;
39
    const TYPE_UTF8_STRING = 0x0c;
40
    const TYPE_RELATIVE_OID = 0x0d;
41
    const TYPE_SEQUENCE = 0x10;
42
    const TYPE_SET = 0x11;
43
    const TYPE_NUMERIC_STRING = 0x12;
44
    const TYPE_PRINTABLE_STRING = 0x13;
45
    const TYPE_T61_STRING = 0x14;
46
    const TYPE_VIDEOTEX_STRING = 0x15;
47
    const TYPE_IA5_STRING = 0x16;
48
    const TYPE_UTC_TIME = 0x17;
49
    const TYPE_GENERALIZED_TIME = 0x18;
50
    const TYPE_GRAPHIC_STRING = 0x19;
51
    const TYPE_VISIBLE_STRING = 0x1a;
52
    const TYPE_GENERAL_STRING = 0x1b;
53
    const TYPE_UNIVERSAL_STRING = 0x1c;
54
    const TYPE_CHARACTER_STRING = 0x1d;
55
    const TYPE_BMP_STRING = 0x1e;
56
57
    /**
58
     * Mapping from universal type tag to implementation class name.
59
     *
60
     * @internal
61
     *
62
     * @var array
63
     */
64
    const MAP_TAG_TO_CLASS = [
65
        self::TYPE_EOC => Primitive\EOC::class,
66
        self::TYPE_BOOLEAN => Primitive\Boolean::class,
67
        self::TYPE_INTEGER => Primitive\Integer::class,
68
        self::TYPE_BIT_STRING => Primitive\BitString::class,
69
        self::TYPE_OCTET_STRING => Primitive\OctetString::class,
70
        self::TYPE_NULL => Primitive\NullType::class,
71
        self::TYPE_OBJECT_IDENTIFIER => Primitive\ObjectIdentifier::class,
72
        self::TYPE_OBJECT_DESCRIPTOR => Primitive\ObjectDescriptor::class,
73
        self::TYPE_REAL => Primitive\Real::class,
74
        self::TYPE_ENUMERATED => Primitive\Enumerated::class,
75
        self::TYPE_UTF8_STRING => Primitive\UTF8String::class,
76
        self::TYPE_RELATIVE_OID => Primitive\RelativeOID::class,
77
        self::TYPE_SEQUENCE => Constructed\Sequence::class,
78
        self::TYPE_SET => Constructed\Set::class,
79
        self::TYPE_NUMERIC_STRING => Primitive\NumericString::class,
80
        self::TYPE_PRINTABLE_STRING => Primitive\PrintableString::class,
81
        self::TYPE_T61_STRING => Primitive\T61String::class,
82
        self::TYPE_VIDEOTEX_STRING => Primitive\VideotexString::class,
83
        self::TYPE_IA5_STRING => Primitive\IA5String::class,
84
        self::TYPE_UTC_TIME => Primitive\UTCTime::class,
85
        self::TYPE_GENERALIZED_TIME => Primitive\GeneralizedTime::class,
86
        self::TYPE_GRAPHIC_STRING => Primitive\GraphicString::class,
87
        self::TYPE_VISIBLE_STRING => Primitive\VisibleString::class,
88
        self::TYPE_GENERAL_STRING => Primitive\GeneralString::class,
89
        self::TYPE_UNIVERSAL_STRING => Primitive\UniversalString::class,
90
        self::TYPE_CHARACTER_STRING => Primitive\CharacterString::class,
91
        self::TYPE_BMP_STRING => Primitive\BMPString::class,
92
    ];
93
94
    /**
95
     * Pseudotype for all string types.
96
     *
97
     * May be used as an expectation parameter.
98
     *
99
     * @var int
100
     */
101
    const TYPE_STRING = -1;
102
103
    /**
104
     * Pseudotype for all time types.
105
     *
106
     * May be used as an expectation parameter.
107
     *
108
     * @var int
109
     */
110
    const TYPE_TIME = -2;
111
112
    /**
113
     * Mapping from universal type tag to human readable name.
114
     *
115
     * @internal
116
     *
117
     * @var array
118
     */
119
    const MAP_TYPE_TO_NAME = [
120
        self::TYPE_EOC => 'EOC',
121
        self::TYPE_BOOLEAN => 'BOOLEAN',
122
        self::TYPE_INTEGER => 'INTEGER',
123
        self::TYPE_BIT_STRING => 'BIT STRING',
124
        self::TYPE_OCTET_STRING => 'OCTET STRING',
125
        self::TYPE_NULL => 'NULL',
126
        self::TYPE_OBJECT_IDENTIFIER => 'OBJECT IDENTIFIER',
127
        self::TYPE_OBJECT_DESCRIPTOR => 'ObjectDescriptor',
128
        self::TYPE_EXTERNAL => 'EXTERNAL',
129
        self::TYPE_REAL => 'REAL',
130
        self::TYPE_ENUMERATED => 'ENUMERATED',
131
        self::TYPE_EMBEDDED_PDV => 'EMBEDDED PDV',
132
        self::TYPE_UTF8_STRING => 'UTF8String',
133
        self::TYPE_RELATIVE_OID => 'RELATIVE-OID',
134
        self::TYPE_SEQUENCE => 'SEQUENCE',
135
        self::TYPE_SET => 'SET',
136
        self::TYPE_NUMERIC_STRING => 'NumericString',
137
        self::TYPE_PRINTABLE_STRING => 'PrintableString',
138
        self::TYPE_T61_STRING => 'T61String',
139
        self::TYPE_VIDEOTEX_STRING => 'VideotexString',
140
        self::TYPE_IA5_STRING => 'IA5String',
141
        self::TYPE_UTC_TIME => 'UTCTime',
142
        self::TYPE_GENERALIZED_TIME => 'GeneralizedTime',
143
        self::TYPE_GRAPHIC_STRING => 'GraphicString',
144
        self::TYPE_VISIBLE_STRING => 'VisibleString',
145
        self::TYPE_GENERAL_STRING => 'GeneralString',
146
        self::TYPE_UNIVERSAL_STRING => 'UniversalString',
147
        self::TYPE_CHARACTER_STRING => 'CHARACTER STRING',
148
        self::TYPE_BMP_STRING => 'BMPString',
149
        self::TYPE_STRING => 'Any String',
150
        self::TYPE_TIME => 'Any Time',
151
    ];
152
153
    /**
154
     * Element's type tag.
155
     *
156
     * @var int
157
     */
158
    protected $_typeTag;
159
160
    /**
161
     * Whether type shall be encoded with indefinite length.
162
     *
163
     * @var bool
164
     */
165
    protected $_indefiniteLength = false;
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    abstract public function typeClass(): int;
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    abstract public function isConstructed(): bool;
176
177
    /**
178
     * Decode element from DER data.
179
     *
180
     * @param string   $data   DER encoded data
181
     * @param null|int $offset Reference to the variable that contains offset
182
     *                         into the data where to start parsing.
183
     *                         Variable is updated to the offset next to the
184
     *                         parsed element. If null, start from offset 0.
185
     *
186
     * @throws DecodeException           If decoding fails
187
     * @throws \UnexpectedValueException If called in the context of an expected
188
     *                                   type, but decoding yields another type
189
     *
190
     * @return ElementBase
191
     */
192 227
    public static function fromDER(string $data, int &$offset = null): ElementBase
193
    {
194 227
        $idx = $offset ?? 0;
195
        // decode identifier
196 227
        $identifier = Identifier::fromDER($data, $idx);
197
        // determine class that implements type specific decoding
198 227
        $cls = self::_determineImplClass($identifier);
199
        try {
200
            // decode remaining element
201 226
            $element = $cls::_decodeFromDER($identifier, $data, $idx);
202 41
        } catch (\LogicException $e) {
203
            // rethrow as a RuntimeException for unified exception handling
204 1
            throw new DecodeException(
205 1
                sprintf('Error while decoding %s.',
206 1
                    self::tagToName($identifier->intTag())), 0, $e);
207
        }
208
        // if called in the context of a concrete class, check
209
        // that decoded type matches the type of a calling class
210 187
        $called_class = get_called_class();
211 187
        if (self::class != $called_class) {
212 158
            if (!$element instanceof $called_class) {
213 1
                throw new \UnexpectedValueException(
214 1
                    sprintf('%s expected, got %s.', $called_class,
215 1
                        get_class($element)));
216
            }
217
        }
218
        // update offset for the caller
219 186
        if (isset($offset)) {
220 23
            $offset = $idx;
221
        }
222 186
        return $element;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228 131
    public function toDER(): string
229
    {
230 131
        $identifier = new Identifier($this->typeClass(),
231 131
            $this->isConstructed() ? Identifier::CONSTRUCTED : Identifier::PRIMITIVE,
232 131
            $this->_typeTag);
233 131
        $content = $this->_encodedContentDER();
234 131
        if ($this->_indefiniteLength) {
235 2
            $length = new Length(0, true);
236 2
            $eoc = new Primitive\EOC();
237 2
            return $identifier->toDER() . $length->toDER() . $content .
238 2
                $eoc->toDER();
239
        }
240 131
        $length = new Length(strlen($content));
241 131
        return $identifier->toDER() . $length->toDER() . $content;
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     */
247 109
    public function tag(): int
248
    {
249 109
        return $this->_typeTag;
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 27
    public function isType(int $tag): bool
256
    {
257
        // if element is context specific
258 27
        if (Identifier::CLASS_CONTEXT_SPECIFIC === $this->typeClass()) {
259 2
            return false;
260
        }
261
        // negative tags identify an abstract pseudotype
262 25
        if ($tag < 0) {
263 4
            return $this->_isPseudoType($tag);
264
        }
265 21
        return $this->_isConcreteType($tag);
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271 16
    public function expectType(int $tag): ElementBase
272
    {
273 16
        if (!$this->isType($tag)) {
274 6
            throw new \UnexpectedValueException(
275 6
                sprintf('%s expected, got %s.', self::tagToName($tag),
276 6
                    $this->_typeDescriptorString()));
277
        }
278 10
        return $this;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 20
    public function isTagged(): bool
285
    {
286 20
        return $this instanceof TaggedType;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 13
    public function expectTagged(?int $tag = null): TaggedType
293
    {
294 13
        if (!$this->isTagged()) {
295 1
            throw new \UnexpectedValueException(
296 1
                sprintf('Context specific element expected, got %s.',
297 1
                    Identifier::classToName($this->typeClass())));
298
        }
299 12
        if (isset($tag) && $this->tag() !== $tag) {
300 5
            throw new \UnexpectedValueException(
301 5
                sprintf('Tag %d expected, got %d.', $tag, $this->tag()));
302
        }
303 7
        return $this;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this returns the type Sop\ASN1\Element which includes types incompatible with the type-hinted return Sop\ASN1\Type\TaggedType.
Loading history...
304
    }
305
306
    /**
307
     * Whether element has indefinite length.
308
     *
309
     * @return bool
310
     */
311 1
    public function hasIndefiniteLength(): bool
312
    {
313 1
        return $this->_indefiniteLength;
314
    }
315
316
    /**
317
     * Get self with indefinite length encoding set.
318
     *
319
     * @param bool $indefinite True for indefinite length, false for definite
320
     *                         length
321
     *
322
     * @return self
323
     */
324 2
    public function withIndefiniteLength(bool $indefinite = true): self
325
    {
326 2
        $obj = clone $this;
327 2
        $obj->_indefiniteLength = $indefinite;
328 2
        return $obj;
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334 50
    final public function asElement(): Element
335
    {
336 50
        return $this;
337
    }
338
339
    /**
340
     * Get element decorated with UnspecifiedType object.
341
     *
342
     * @return UnspecifiedType
343
     */
344 36
    public function asUnspecified(): UnspecifiedType
345
    {
346 36
        return new UnspecifiedType($this);
347
    }
348
349
    /**
350
     * Get human readable name for an universal tag.
351
     *
352
     * @param int $tag
353
     *
354
     * @return string
355
     */
356 48
    public static function tagToName(int $tag): string
357
    {
358 48
        if (!array_key_exists($tag, self::MAP_TYPE_TO_NAME)) {
359 1
            return "TAG ${tag}";
360
        }
361 47
        return self::MAP_TYPE_TO_NAME[$tag];
362
    }
363
364
    /**
365
     * Get the content encoded in DER.
366
     *
367
     * Returns the DER encoded content without identifier and length header
368
     * octets.
369
     *
370
     * @return string
371
     */
372
    abstract protected function _encodedContentDER(): string;
373
374
    /**
375
     * Decode type-specific element from DER.
376
     *
377
     * @param Identifier $identifier Pre-parsed identifier
378
     * @param string     $data       DER data
379
     * @param int        $offset     Offset in data to the next byte after identifier
380
     *
381
     * @throws DecodeException If decoding fails
382
     *
383
     * @return ElementBase
384
     */
385 1
    protected static function _decodeFromDER(Identifier $identifier,
386
        string $data, int &$offset): ElementBase
387
    {
388 1
        throw new \BadMethodCallException(
389 1
            __METHOD__ . ' must be implemented in derived class.');
390
    }
391
392
    /**
393
     * Determine the class that implements the type.
394
     *
395
     * @param Identifier $identifier
396
     *
397
     * @return string Class name
398
     */
399 230
    protected static function _determineImplClass(Identifier $identifier): string
400
    {
401 230
        switch ($identifier->typeClass()) {
402
            case Identifier::CLASS_UNIVERSAL:
403 212
                return self::_determineUniversalImplClass($identifier->intTag());
404
            case Identifier::CLASS_CONTEXT_SPECIFIC:
405 27
                return ContextSpecificType::class;
406
            case Identifier::CLASS_APPLICATION:
407 3
                return ApplicationType::class;
408
            case Identifier::CLASS_PRIVATE:
409 3
                return PrivateType::class;
410
        }
411 1
        throw new \UnexpectedValueException(
412 1
            sprintf('%s %d not implemented.',
413 1
                Identifier::classToName($identifier->typeClass()),
414 1
                $identifier->tag()));
415
    }
416
417
    /**
418
     * Determine the class that implements an universal type of the given tag.
419
     *
420
     * @param int $tag
421
     *
422
     * @throws \UnexpectedValueException
423
     *
424
     * @return string Class name
425
     */
426 223
    protected static function _determineUniversalImplClass(int $tag): string
427
    {
428 223
        if (!array_key_exists($tag, self::MAP_TAG_TO_CLASS)) {
429 1
            throw new \UnexpectedValueException(
430 1
                "Universal tag ${tag} not implemented.");
431
        }
432 222
        return self::MAP_TAG_TO_CLASS[$tag];
433
    }
434
435
    /**
436
     * Get textual description of the type for debugging purposes.
437
     *
438
     * @return string
439
     */
440 6
    protected function _typeDescriptorString(): string
441
    {
442 6
        if (Identifier::CLASS_UNIVERSAL == $this->typeClass()) {
443 4
            return self::tagToName($this->_typeTag);
444
        }
445 2
        return sprintf('%s TAG %d', Identifier::classToName($this->typeClass()),
446 2
            $this->_typeTag);
447
    }
448
449
    /**
450
     * Check whether the element is a concrete type of a given tag.
451
     *
452
     * @param int $tag
453
     *
454
     * @return bool
455
     */
456 21
    private function _isConcreteType(int $tag): bool
457
    {
458
        // if tag doesn't match
459 21
        if ($this->tag() != $tag) {
460 9
            return false;
461
        }
462
        // if type is universal check that instance is of a correct class
463 16
        if (Identifier::CLASS_UNIVERSAL == $this->typeClass()) {
464 16
            $cls = self::_determineUniversalImplClass($tag);
465 16
            if (!$this instanceof $cls) {
466 1
                return false;
467
            }
468
        }
469 15
        return true;
470
    }
471
472
    /**
473
     * Check whether the element is a pseudotype.
474
     *
475
     * @param int $tag
476
     *
477
     * @return bool
478
     */
479 4
    private function _isPseudoType(int $tag): bool
480
    {
481
        switch ($tag) {
482 4
            case self::TYPE_STRING:
483 1
                return $this instanceof StringType;
484 3
            case self::TYPE_TIME:
485 2
                return $this instanceof TimeType;
486
        }
487 1
        return false;
488
    }
489
}
490