<?php
 declare(strict_types=1); namespace SpomkyLabs\Pki\X501\ASN1; use ArrayIterator; use Countable; use IteratorAggregate; use LogicException; use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence; use SpomkyLabs\Pki\ASN1\Type\Constructed\Set; use SpomkyLabs\Pki\ASN1\Type\UnspecifiedType; use SpomkyLabs\Pki\X501\ASN1\AttributeValue\AttributeValue; use function count; use function sprintf; final class Attribute implements Countable, IteratorAggregate { private readonly AttributeType $type; private readonly array $values; private function __construct(AttributeType $type, AttributeValue ...$values) { foreach ($values as $value) { if ($value->oid() !== $type->oid()) { throw new LogicException('Attribute OID mismatch.'); } } $this->type = $type; $this->values = $values; } public static function create(AttributeType $type, AttributeValue ...$values): self { return new self($type, ...$values); } public static function fromASN1(Sequence $seq): self { $type = AttributeType::fromASN1($seq->at(0)->asObjectIdentifier()); $values = array_map( static fn (UnspecifiedType $el) => AttributeValue::fromASN1ByOID($type->oid(), $el), $seq->at(1) ->asSet() ->elements() ); return self::create($type, ...$values); } public static function fromAttributeValues(AttributeValue ...$values): self { if (count($values) === 0) { throw new LogicException('No values.'); } $oid = reset($values) ->oid(); return self::create(AttributeType::create($oid), ...$values); } public function first(): AttributeValue { if (count($this->values) === 0) { throw new LogicException('Attribute contains no values.'); } return $this->values[0]; } public function values(): array { return $this->values; } public function toASN1(): Sequence { $values = array_map(static fn (AttributeValue $value) => $value->toASN1(), $this->values); $valueset = Set::create(...$values); return Sequence::create($this->type->toASN1(), $valueset->sortedSetOf()); } public function castValues(string $cls): self { if (! is_subclass_of($cls, AttributeValue::class)) { throw new LogicException(sprintf('%s must be derived from %s.', $cls, AttributeValue::class)); } $values = array_map( function (AttributeValue $value) use ($cls) { $value = $cls::fromSelf($value); if ($value->oid() !== $this->oid()) { throw new LogicException('Attribute OID mismatch.'); } return $value; }, $this->values ); return self::fromAttributeValues(...$values); } public function count(): int { return count($this->values); } public function getIterator(): ArrayIterator { return new ArrayIterator($this->values); } public function type(): AttributeType { return $this->type; } public function oid(): string { return $this->type->oid(); } } 