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
Push — master ( 95c197...564e5e )
by Joni
04:32
created

Element::_determineImplClass()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

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