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

Structure   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 399
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 120
dl 0
loc 399
ccs 134
cts 134
cp 1
rs 8.8798
c 0
b 0
f 0
wmc 44

20 Methods

Rating   Name   Duplication   Size   Complexity  
A has() 0 11 4
A __clone() 0 5 1
A isConstructed() 0 3 1
A withInserted() 0 8 3
A withAppended() 0 5 1
A elements() 0 9 2
A withReplaced() 0 9 2
A explodeDER() 0 27 4
A withPrepended() 0 5 1
A __construct() 0 6 1
A withoutElement() 0 9 2
A hasTagged() 0 12 4
A getIterator() 0 3 1
A count() 0 3 1
A getTagged() 0 6 2
A at() 0 7 2
A _decodeDefiniteLength() 0 17 3
A _decodeIndefiniteLength() 0 21 4
A _decodeFromDER() 0 17 3
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 60
    public function __construct(ElementBase ...$elements)
47
    {
48 60
        $this->_elements = array_map(
49
            function (ElementBase $el) {
50 51
                return $el->asElement();
51 60
            }, $elements);
52 60
    }
53
54
    /**
55
     * Clone magic method.
56
     */
57 18
    public function __clone()
58
    {
59
        // clear cache-variables
60 18
        $this->_taggedMap = null;
61 18
        $this->_unspecifiedTypes = null;
62 18
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 19
    public function isConstructed(): bool
68
    {
69 19
        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;
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
     * @param int $idx Index 0..n
242
     *
243
     * @throws \OutOfBoundsException     If element doesn't exists
244
     * @throws \UnexpectedValueException If expectation fails
245
     *
246
     * @return UnspecifiedType
247
     */
248 5
    public function at(int $idx): UnspecifiedType
249
    {
250 5
        if (!isset($this->_elements[$idx])) {
251 1
            throw new \OutOfBoundsException(
252 1
                "Structure doesn't have an element at index {$idx}.");
253
        }
254 4
        return new UnspecifiedType($this->_elements[$idx]);
255
    }
256
257
    /**
258
     * Check whether the structure contains a context specific element with a
259
     * given tag.
260
     *
261
     * @param int $tag Tag number
262
     *
263
     * @return bool
264
     */
265 6
    public function hasTagged(int $tag): bool
266
    {
267
        // lazily build lookup map
268 6
        if (!isset($this->_taggedMap)) {
269 6
            $this->_taggedMap = [];
270 6
            foreach ($this->_elements as $element) {
271 6
                if ($element->isTagged()) {
272 6
                    $this->_taggedMap[$element->tag()] = $element;
273
                }
274
            }
275
        }
276 6
        return isset($this->_taggedMap[$tag]);
277
    }
278
279
    /**
280
     * Get a context specific element tagged with a given tag.
281
     *
282
     * @param int $tag
283
     *
284
     * @throws \LogicException If tag doesn't exists
285
     *
286
     * @return TaggedType
287
     */
288 3
    public function getTagged(int $tag): TaggedType
289
    {
290 3
        if (!$this->hasTagged($tag)) {
291 1
            throw new \LogicException("No tagged element for tag {$tag}.");
292
        }
293 2
        return $this->_taggedMap[$tag];
294
    }
295
296
    /**
297
     * @see \Countable::count()
298
     *
299
     * @return int
300
     */
301 4
    public function count(): int
302
    {
303 4
        return count($this->_elements);
304
    }
305
306
    /**
307
     * Get an iterator for the UnspecifiedElement objects.
308
     *
309
     * @see \IteratorAggregate::getIterator()
310
     *
311
     * @return \ArrayIterator
312
     */
313 1
    public function getIterator(): \ArrayIterator
314
    {
315 1
        return new \ArrayIterator($this->elements());
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321 18
    protected function _encodedContentDER(): string
322
    {
323 18
        $data = '';
324 18
        foreach ($this->_elements as $element) {
325 14
            $data .= $element->toDER();
326
        }
327 18
        return $data;
328
    }
329
330
    /**
331
     * {@inheritdoc}
332
     *
333
     * @return self
334
     */
335 25
    protected static function _decodeFromDER(Identifier $identifier,
336
        string $data, int &$offset): ElementBase
337
    {
338 25
        if (!$identifier->isConstructed()) {
339 2
            throw new DecodeException(
340 2
                'Structured element must have constructed bit set.');
341
        }
342 23
        $idx = $offset;
343 23
        $length = Length::expectFromDER($data, $idx);
344 22
        if ($length->isIndefinite()) {
345 6
            $type = self::_decodeIndefiniteLength($data, $idx);
346
        } else {
347 16
            $type = self::_decodeDefiniteLength($data, $idx,
348 16
                $length->intLength());
349
        }
350 20
        $offset = $idx;
351 20
        return $type;
352
    }
353
354
    /**
355
     * Decode elements for a definite length.
356
     *
357
     * @param string $data   DER data
358
     * @param int    $offset Offset to data
359
     * @param int    $length Number of bytes to decode
360
     *
361
     * @throws DecodeException
362
     *
363
     * @return ElementBase
364
     */
365 16
    private static function _decodeDefiniteLength(string $data, int &$offset,
366
        int $length): ElementBase
367
    {
368 16
        $idx = $offset;
369 16
        $end = $idx + $length;
370 16
        $elements = [];
371 16
        while ($idx < $end) {
372 12
            $elements[] = Element::fromDER($data, $idx);
373
            // check that element didn't overflow length
374 12
            if ($idx > $end) {
375 1
                throw new DecodeException(
376 1
                    "Structure's content overflows length.");
377
            }
378
        }
379 15
        $offset = $idx;
380
        // return instance by static late binding
381 15
        return new static(...$elements);
382
    }
383
384
    /**
385
     * Decode elements for an indefinite length.
386
     *
387
     * @param string $data   DER data
388
     * @param int    $offset Offset to data
389
     *
390
     * @throws DecodeException
391
     *
392
     * @return ElementBase
393
     */
394 6
    private static function _decodeIndefiniteLength(
395
        string $data, int &$offset): ElementBase
396
    {
397 6
        $idx = $offset;
398 6
        $elements = [];
399 6
        $end = strlen($data);
400 6
        while (true) {
401 6
            if ($idx >= $end) {
402 1
                throw new DecodeException(
403 1
                    'Unexpected end of data while decoding indefinite length structure.');
404
            }
405 6
            $el = Element::fromDER($data, $idx);
406 6
            if ($el->isType(self::TYPE_EOC)) {
407 5
                break;
408
            }
409 5
            $elements[] = $el;
410
        }
411 5
        $offset = $idx;
412 5
        $type = new static(...$elements);
413 5
        $type->_indefiniteLength = true;
414 5
        return $type;
415
    }
416
}
417