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

Structure::withPrepended()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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