<?php
 declare(strict_types=1); namespace SpomkyLabs\Pki\ASN1\Type\Constructed; use LogicException; use SpomkyLabs\Pki\ASN1\Component\Identifier; use SpomkyLabs\Pki\ASN1\Component\Length; use SpomkyLabs\Pki\ASN1\Element; use SpomkyLabs\Pki\ASN1\Exception\DecodeException; use SpomkyLabs\Pki\ASN1\Type\StringType; use SpomkyLabs\Pki\ASN1\Type\Structure; use Stringable; use function count; final class ConstructedString extends Structure implements StringType, Stringable { public function __toString(): string { return $this->string(); } public static function create(StringType ...$elements): self { if (count($elements) === 0) { throw new LogicException('No elements, unable to determine type tag.'); } $tag = $elements[0]->tag(); return self::createWithTag($tag, ...$elements); } public static function createWithTag(int $tag, StringType ...$elements): self { foreach ($elements as $el) { if ($el->tag() !== $tag) { throw new LogicException('All elements in constructed string must have the same type.'); } } return new self($tag, ...$elements); } public function strings(): array { return array_map(static fn (Element $el): string => $el->string(), $this->elements); } public function string(): string { return implode('', $this->strings()); } protected static function decodeFromDER(Identifier $identifier, string $data, int &$offset): self { if (! $identifier->isConstructed()) { throw new DecodeException('Structured element must have constructed bit set.'); } $idx = $offset; $length = Length::expectFromDER($data, $idx); if ($length->isIndefinite()) { $type = self::decodeIndefiniteLength($identifier->intTag(), $data, $idx); } else { $type = self::decodeDefiniteLength($identifier->intTag(), $data, $idx, $length->intLength()); } $offset = $idx; return $type; } protected static function decodeDefiniteLength(int $typeTag, string $data, int &$offset, int $length): self { $idx = $offset; $end = $idx + $length; $elements = []; while ($idx < $end) { $elements[] = Element::fromDER($data, $idx); if ($idx > $end) { throw new DecodeException("Structure's content overflows length."); } } $offset = $idx; return self::createWithTag($typeTag, ...$elements); } protected static function decodeIndefiniteLength(int $typeTag, string $data, int &$offset): self { $idx = $offset; $elements = []; $end = mb_strlen($data, '8bit'); while (true) { if ($idx >= $end) { throw new DecodeException('Unexpected end of data while decoding indefinite length structure.'); } $el = Element::fromDER($data, $idx); if ($el->isType(self::TYPE_EOC)) { break; } $elements[] = $el; } $offset = $idx; $type = self::createWithTag($typeTag, ...$elements); $type->indefiniteLength = true; return $type; } } 