1<?php
2/**
3 * This file is part of the FreeDSx ASN1 package.
4 *
5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11namespace FreeDSx\Asn1\Encoder;
12
13use FreeDSx\Asn1\Exception\EncoderException;
14use FreeDSx\Asn1\Type\AbstractStringType;
15use FreeDSx\Asn1\Type\AbstractTimeType;
16use FreeDSx\Asn1\Type\AbstractType;
17use FreeDSx\Asn1\Type\BitStringType;
18use FreeDSx\Asn1\Type\OctetStringType;
19use FreeDSx\Asn1\Type\SetTrait;
20use FreeDSx\Asn1\Type\SetType;
21
22/**
23 * Distinguished Encoding Rules (DER) encoder.
24 *
25 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
26 */
27class DerEncoder extends BerEncoder
28{
29    use CerDerTrait,
30        SetTrait;
31
32    /**
33     * @param array $options
34     */
35    public function __construct(array $options = [])
36    {
37        parent::__construct($options);
38        $this->setOptions([
39            'bitstring_padding' => '0',
40        ]);
41    }
42
43    public function encode(AbstractType $type): string
44    {
45        $this->validate($type);
46
47        return parent::encode($type);
48    }
49
50    /**
51     * {@inheritdoc}
52     */
53    protected function decodeBytes(bool $isRoot = false, $tagType = null, $length = null, $isConstructed = null, $class = null): AbstractType
54    {
55        $type = parent::decodeBytes($isRoot, $tagType, $length, $isConstructed, $class);
56        $this->validate($type);
57
58        return $type;
59    }
60
61    /**
62     * {@inheritdoc}
63     */
64    protected function decodeLongDefiniteLength(int $length): int
65    {
66        $length = parent::decodeLongDefiniteLength($length);
67
68        if ($length < 127) {
69            throw new EncoderException('DER must be encoded using the shortest possible length form, but it is not.');
70        }
71
72        return $length;
73    }
74
75    /**
76     * {@inheritdoc}
77     * @throws EncoderException
78     */
79    protected function encodeSet(SetType $set)
80    {
81        return $this->encodeConstructedType(...$this->canonicalize(...$set->getChildren()));
82    }
83
84    /**
85     * @param AbstractType $type
86     * @throws EncoderException
87     */
88    protected function validate(AbstractType $type): void
89    {
90        if ($type instanceof OctetStringType && $type->getIsConstructed()) {
91            throw new EncoderException('The octet string must be primitive. It cannot be constructed.');
92        }
93        if ($type instanceof BitStringType && $type->getIsConstructed()) {
94            throw new EncoderException('The bit string must be primitive. It cannot be constructed.');
95        }
96        if ($type instanceof AbstractStringType && $type->isCharacterRestricted() && $type->getIsConstructed()) {
97            throw new EncoderException('Character restricted string types must be primitive.');
98        }
99        if ($type instanceof AbstractTimeType) {
100            $this->validateTimeType($type);
101        }
102    }
103}
104