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
Branch indefinite (96169e)
by Joni
03:55
created

Element::hasIndefiniteLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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