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

Structure   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 413
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 123
dl 0
loc 413
ccs 137
cts 137
cp 1
rs 8.8
c 0
b 0
f 0
wmc 45

20 Methods

Rating   Name   Duplication   Size   Complexity  
A _decodeDefiniteLength() 0 17 3
A has() 0 11 4
A hasTagged() 0 12 4
A getIterator() 0 3 1
A __clone() 0 5 1
A isConstructed() 0 3 1
A withInserted() 0 8 3
A count() 0 3 1
A withAppended() 0 5 1
A elements() 0 9 2
A withReplaced() 0 9 2
A _decodeIndefiniteLength() 0 21 4
A _decodeFromDER() 0 17 3
A explodeDER() 0 27 4
A withPrepended() 0 5 1
A getTagged() 0 6 2
A at() 0 11 3
A __construct() 0 6 1
A withoutElement() 0 9 2
A _encodedContentDER() 0 7 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\ASN1\Type;
6
7
use Sop\ASN1\Component\Identifier;
8
use Sop\ASN1\Component\Length;
9
use Sop\ASN1\Element;
10
use Sop\ASN1\Exception\DecodeException;
11
use Sop\ASN1\Feature\ElementBase;
12
13
/**
14
 * Base class for the constructed types.
15
 */
16
abstract class Structure extends Element implements \Countable, \IteratorAggregate
17
{
18
    use UniversalClass;
19
20
    /**
21
     * Array of elements in the structure.
22
     *
23
     * @var Element[]
24
     */
25
    protected $_elements;
26
27
    /**
28
     * Lookup table for the tagged elements.
29
     *
30
     * @var null|TaggedType[]
31
     */
32
    private $_taggedMap;
33
34
    /**
35
     * Cache variable of elements wrapped into UnspecifiedType objects.
36
     *
37
     * @var null|UnspecifiedType[]
38
     */
39
    private $_unspecifiedTypes;
40
41
    /**
42
     * Constructor.
43
     *
44
     * @param ElementBase ...$elements Any number of elements
45
     */
46 52
    public function __construct(ElementBase ...$elements)
47
    {
48 52
        $this->_elements = array_map(
49
            function (ElementBase $el) {
50 48
                return $el->asElement();
51 52
            }, $elements);
52 52
    }
53
54
    /**
55
     * Clone magic method.
56
     */
57 16
    public function __clone()
58
    {
59
        // clear cache-variables
60 16
        $this->_taggedMap = null;
61 16
        $this->_unspecifiedTypes = null;
62 16
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 16
    public function isConstructed(): bool
68
    {
69 16
        return true;
70
    }
71
72
    /**
73
     * Explode DER structure to DER encoded components that it contains.
74
     *
75
     * @param string $data
76
     *
77
     * @throws DecodeException
78
     *
79
     * @return string[]
80
     */
81 3
    public static function explodeDER(string $data): array
82
    {
83 3
        $offset = 0;
84 3
        $identifier = Identifier::fromDER($data, $offset);
85 3
        if (!$identifier->isConstructed()) {
86 1
            throw new DecodeException('Element is not constructed.');
87
        }
88 2
        $length = Length::expectFromDER($data, $offset);
89 2
        if ($length->isIndefinite()) {
90 1
            throw new DecodeException(
91 1
                'Explode not implemented for indefinite length encoding.');
92
        }
93 1
        $end = $offset + $length->intLength();
94 1
        $parts = [];
95 1
        while ($offset < $end) {
96
            // start of the element
97 1
            $idx = $offset;
98
            // skip identifier
99 1
            Identifier::fromDER($data, $offset);
100
            // decode element length
101 1
            $length = Length::expectFromDER($data, $offset)->intLength();
102
            // extract der encoding of the element
103 1
            $parts[] = substr($data, $idx, $offset - $idx + $length);
104
            // update offset over content
105 1
            $offset += $length;
106
        }
107 1
        return $parts;
108
    }
109
110
    /**
111
     * Get self with an element at the given index replaced by another.
112
     *
113
     * @param int     $idx Element index
114
     * @param Element $el  New element to insert into the structure
115
     *
116
     * @throws \OutOfBoundsException
117
     *
118
     * @return self
119
     */
120 2
    public function withReplaced(int $idx, Element $el): self
121
    {
122 2
        if (!isset($this->_elements[$idx])) {
123 1
            throw new \OutOfBoundsException(
124 1
                "Structure doesn't have element at index ${idx}.");
125
        }
126 1
        $obj = clone $this;
127 1
        $obj->_elements[$idx] = $el;
128 1
        return $obj;
129
    }
130
131
    /**
132
     * Get self with an element inserted before the given index.
133
     *
134
     * @param int     $idx Element index
135
     * @param Element $el  New element to insert into the structure
136
     *
137
     * @throws \OutOfBoundsException
138
     *
139
     * @return self
140
     */
141 4
    public function withInserted(int $idx, Element $el): self
142
    {
143 4
        if (count($this->_elements) < $idx || $idx < 0) {
144 1
            throw new \OutOfBoundsException("Index ${idx} is out of bounds.");
145
        }
146 3
        $obj = clone $this;
147 3
        array_splice($obj->_elements, $idx, 0, [$el]);
148 3
        return $obj;
149
    }
150
151
    /**
152
     * Get self with an element appended to the end.
153
     *
154
     * @param Element $el Element to insert into the structure
155
     *
156
     * @return self
157
     */
158 2
    public function withAppended(Element $el): self
159
    {
160 2
        $obj = clone $this;
161 2
        array_push($obj->_elements, $el);
162 2
        return $obj;
163
    }
164
165
    /**
166
     * Get self with an element prepended in the beginning.
167
     *
168
     * @param Element $el Element to insert into the structure
169
     *
170
     * @return self
171
     */
172 1
    public function withPrepended(Element $el): self
173
    {
174 1
        $obj = clone $this;
175 1
        array_unshift($obj->_elements, $el);
176 1
        return $obj;
177
    }
178
179
    /**
180
     * Get self with an element at the given index removed.
181
     *
182
     * @param int $idx Element index
183
     *
184
     * @throws \OutOfBoundsException
185
     *
186
     * @return self
187
     */
188 4
    public function withoutElement(int $idx): self
189
    {
190 4
        if (!isset($this->_elements[$idx])) {
191 1
            throw new \OutOfBoundsException(
192 1
                "Structure doesn't have element at index ${idx}.");
193
        }
194 3
        $obj = clone $this;
195 3
        array_splice($obj->_elements, $idx, 1);
196 3
        return $obj;
197
    }
198
199
    /**
200
     * Get elements in the structure.
201
     *
202
     * @return UnspecifiedType[]
203
     */
204 2
    public function elements(): array
205
    {
206 2
        if (!isset($this->_unspecifiedTypes)) {
207 1
            $this->_unspecifiedTypes = array_map(
208
                function (Element $el) {
209 1
                    return new UnspecifiedType($el);
210 1
                }, $this->_elements);
211
        }
212 2
        return $this->_unspecifiedTypes;
1 ignored issue
show
Bug Best Practice introduced by
The expression return $this->_unspecifiedTypes could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
213
    }
214
215
    /**
216
     * Check whether the structure has an element at the given index, optionally
217
     * satisfying given tag expectation.
218
     *
219
     * @param int      $idx         Index 0..n
220
     * @param null|int $expectedTag Optional type tag expectation
221
     *
222
     * @return bool
223
     */
224 8
    public function has(int $idx, ?int $expectedTag = null): bool
225
    {
226 8
        if (!isset($this->_elements[$idx])) {
227 2
            return false;
228
        }
229 6
        if (isset($expectedTag)) {
230 3
            if (!$this->_elements[$idx]->isType($expectedTag)) {
231 1
                return false;
232
            }
233
        }
234 5
        return true;
235
    }
236
237
    /**
238
     * Get the element at the given index, optionally checking that the element
239
     * has a given tag.
240
     *
241
     * <strong>NOTE!</strong> Expectation checking is deprecated and should be
242
     * done with <code>UnspecifiedType</code>.
243
     *
244
     * @todo Remove
245
     *
246
     * @param int      $idx         Index 0..n
247
     * @param null|int $expectedTag Optional type tag expectation
248
     *
249
     * @throws \OutOfBoundsException     If element doesn't exists
250
     * @throws \UnexpectedValueException If expectation fails
251
     *
252
     * @return UnspecifiedType
253
     */
254 6
    public function at(int $idx, ?int $expectedTag = null): UnspecifiedType
255
    {
256 6
        if (!isset($this->_elements[$idx])) {
257 1
            throw new \OutOfBoundsException(
258 1
                "Structure doesn't have an element at index ${idx}.");
259
        }
260 5
        $element = $this->_elements[$idx];
261 5
        if (isset($expectedTag)) {
262 2
            $element->expectType($expectedTag);
263
        }
264 4
        return new UnspecifiedType($element);
265
    }
266
267
    /**
268
     * Check whether the structure contains a context specific element with a
269
     * given tag.
270
     *
271
     * @param int $tag Tag number
272
     *
273
     * @return bool
274
     */
275 6
    public function hasTagged(int $tag): bool
276
    {
277
        // lazily build lookup map
278 6
        if (!isset($this->_taggedMap)) {
279 6
            $this->_taggedMap = [];
280 6
            foreach ($this->_elements as $element) {
281 6
                if ($element->isTagged()) {
282 6
                    $this->_taggedMap[$element->tag()] = $element;
283
                }
284
            }
285
        }
286 6
        return isset($this->_taggedMap[$tag]);
287
    }
288
289
    /**
290
     * Get a context specific element tagged with a given tag.
291
     *
292
     * @param int $tag
293
     *
294
     * @throws \LogicException If tag doesn't exists
295
     *
296
     * @return TaggedType
297
     */
298 3
    public function getTagged(int $tag): TaggedType
299
    {
300 3
        if (!$this->hasTagged($tag)) {
301 1
            throw new \LogicException("No tagged element for tag ${tag}.");
302
        }
303 2
        return $this->_taggedMap[$tag];
304
    }
305
306
    /**
307
     * @see \Countable::count()
308
     *
309
     * @return int
310
     */
311 4
    public function count(): int
312
    {
313 4
        return count($this->_elements);
314
    }
315
316
    /**
317
     * Get an iterator for the UnspecifiedElement objects.
318
     *
319
     * @see \IteratorAggregate::getIterator()
320
     *
321
     * @return \ArrayIterator
322
     */
323 1
    public function getIterator(): \ArrayIterator
324
    {
325 1
        return new \ArrayIterator($this->elements());
326
    }
327
328
    /**
329
     * @see \Sop\ASN1\Element::_encodedContentDER()
330
     *
331
     * @return string
332
     */
333 15
    protected function _encodedContentDER(): string
334
    {
335 15
        $data = '';
336 15
        foreach ($this->_elements as $element) {
337 13
            $data .= $element->toDER();
338
        }
339 15
        return $data;
340
    }
341
342
    /**
343
     * {@inheritdoc}
344
     *
345
     * @see \Sop\ASN1\Element::_decodeFromDER()
346
     *
347
     * @return self
348
     */
349 21
    protected static function _decodeFromDER(Identifier $identifier,
350
        string $data, int &$offset): ElementBase
351
    {
352 21
        if (!$identifier->isConstructed()) {
353 2
            throw new DecodeException(
354 2
                'Structured element must have constructed bit set.');
355
        }
356 19
        $idx = $offset;
357 19
        $length = Length::expectFromDER($data, $idx);
358 18
        if ($length->isIndefinite()) {
359 4
            $type = self::_decodeIndefiniteLength($data, $idx);
360
        } else {
361 14
            $type = self::_decodeDefiniteLength($data, $idx,
362 14
                $length->intLength());
363
        }
364 16
        $offset = $idx;
365 16
        return $type;
366
    }
367
368
    /**
369
     * Decode elements for a definite length.
370
     *
371
     * @param string $data   DER data
372
     * @param int    $offset Offset to data
373
     * @param int    $length Number of bytes to decode
374
     *
375
     * @throws DecodeException
376
     *
377
     * @return ElementBase
378
     */
379 14
    private static function _decodeDefiniteLength(string $data, int &$offset,
380
        int $length): ElementBase
381
    {
382 14
        $idx = $offset;
383 14
        $end = $idx + $length;
384 14
        $elements = [];
385 14
        while ($idx < $end) {
386 12
            $elements[] = Element::fromDER($data, $idx);
387
            // check that element didn't overflow length
388 12
            if ($idx > $end) {
389 1
                throw new DecodeException(
390 1
                    "Structure's content overflows length.");
391
            }
392
        }
393 13
        $offset = $idx;
394
        // return instance by static late binding
395 13
        return new static(...$elements);
396
    }
397
398
    /**
399
     * Decode elements for an indefinite length.
400
     *
401
     * @param string $data   DER data
402
     * @param int    $offset Offset to data
403
     *
404
     * @throws DecodeException
405
     *
406
     * @return ElementBase
407
     */
408 4
    private static function _decodeIndefiniteLength(
409
        string $data, int &$offset): ElementBase
410
    {
411 4
        $idx = $offset;
412 4
        $elements = [];
413 4
        $end = strlen($data);
414 4
        while (true) {
415 4
            if ($idx >= $end) {
416 1
                throw new DecodeException(
417 1
                    'Unexpected end of data while decoding indefinite length structure.');
418
            }
419 4
            $el = Element::fromDER($data, $idx);
420 4
            if ($el->isType(self::TYPE_EOC)) {
421 3
                break;
422
            }
423 4
            $elements[] = $el;
424
        }
425 3
        $offset = $idx;
426 3
        $type = new static(...$elements);
427 3
        $type->_indefiniteLength = true;
428 3
        return $type;
429
    }
430
}
431