elements = array_map(static fn (ElementBase $el) => $el->asElement(), $elements); } /** * Clone magic method. */ public function __clone() { // clear cache-variables $this->taggedMap = null; $this->unspecifiedTypes = null; } public function isConstructed(): bool { return true; } /** * Explode DER structure to DER encoded components that it contains. * * @return string[] */ public static function explodeDER(string $data): array { $offset = 0; $identifier = Identifier::fromDER($data, $offset); if (! $identifier->isConstructed()) { throw new DecodeException('Element is not constructed.'); } $length = Length::expectFromDER($data, $offset); if ($length->isIndefinite()) { throw new DecodeException('Explode not implemented for indefinite length encoding.'); } $end = $offset + $length->intLength(); $parts = []; while ($offset < $end) { // start of the element $idx = $offset; // skip identifier Identifier::fromDER($data, $offset); // decode element length $length = Length::expectFromDER($data, $offset)->intLength(); // extract der encoding of the element $parts[] = mb_substr($data, $idx, $offset - $idx + $length, '8bit'); // update offset over content $offset += $length; } return $parts; } /** * Get self with an element at the given index replaced by another. * * @param int $idx Element index * @param Element $el New element to insert into the structure */ public function withReplaced(int $idx, Element $el): self { if (! isset($this->elements[$idx])) { throw new OutOfBoundsException("Structure doesn't have element at index {$idx}."); } $obj = clone $this; $obj->elements[$idx] = $el; return $obj; } /** * Get self with an element inserted before the given index. * * @param int $idx Element index * @param Element $el New element to insert into the structure */ public function withInserted(int $idx, Element $el): self { if (count($this->elements) < $idx || $idx < 0) { throw new OutOfBoundsException("Index {$idx} is out of bounds."); } $obj = clone $this; array_splice($obj->elements, $idx, 0, [$el]); return $obj; } /** * Get self with an element appended to the end. * * @param Element $el Element to insert into the structure */ public function withAppended(Element $el): self { $obj = clone $this; array_push($obj->elements, $el); return $obj; } /** * Get self with an element prepended in the beginning. * * @param Element $el Element to insert into the structure */ public function withPrepended(Element $el): self { $obj = clone $this; array_unshift($obj->elements, $el); return $obj; } /** * Get self with an element at the given index removed. * * @param int $idx Element index */ public function withoutElement(int $idx): self { if (! isset($this->elements[$idx])) { throw new OutOfBoundsException("Structure doesn't have element at index {$idx}."); } $obj = clone $this; array_splice($obj->elements, $idx, 1); return $obj; } /** * Get elements in the structure. * * @return UnspecifiedType[] */ public function elements(): array { if (! isset($this->unspecifiedTypes)) { $this->unspecifiedTypes = array_map( static fn (Element $el) => UnspecifiedType::create($el), $this->elements ); } return $this->unspecifiedTypes; } /** * Check whether the structure has an element at the given index, optionally satisfying given tag expectation. * * @param int $idx Index 0..n * @param null|int $expectedTag Optional type tag expectation */ public function has(int $idx, ?int $expectedTag = null): bool { if (! isset($this->elements[$idx])) { return false; } if (isset($expectedTag)) { if (! $this->elements[$idx]->isType($expectedTag)) { return false; } } return true; } /** * Get the element at the given index, optionally checking that the element has a given tag. * * @param int $idx Index 0..n */ public function at(int $idx): UnspecifiedType { if (! isset($this->elements[$idx])) { throw new OutOfBoundsException("Structure doesn't have an element at index {$idx}."); } return UnspecifiedType::create($this->elements[$idx]); } /** * Check whether the structure contains a context specific element with a given tag. * * @param int $tag Tag number */ public function hasTagged(int $tag): bool { // lazily build lookup map if (! isset($this->taggedMap)) { $this->taggedMap = []; foreach ($this->elements as $element) { if ($element->isTagged()) { $this->taggedMap[$element->tag()] = $element; } } } return isset($this->taggedMap[$tag]); } /** * Get a context specific element tagged with a given tag. */ public function getTagged(int $tag): TaggedType { if (! $this->hasTagged($tag)) { throw new LogicException("No tagged element for tag {$tag}."); } return $this->taggedMap[$tag]; } /** * @see \Countable::count() */ public function count(): int { return count($this->elements); } /** * Get an iterator for the `UnspecifiedElement` objects. * * @see \IteratorAggregate::getIterator() */ public function getIterator(): ArrayIterator { return new ArrayIterator($this->elements()); } protected function encodedAsDER(): string { $data = ''; foreach ($this->elements as $element) { $data .= $element->toDER(); } return $data; } }