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.
Completed
Push — php72 ( 2f43a1...f5fc00 )
by Joni
03:42
created

Element::tagToName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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