Name   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 205
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 21
eloc 58
dl 0
loc 205
ccs 64
cts 64
cp 1
rs 10
c 1
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getIterator() 0 3 1
A toASN1() 0 7 1
A count() 0 3 1
A equals() 0 14 4
A __construct() 0 3 1
A firstValueOf() 0 13 4
A __toString() 0 3 1
A all() 0 3 1
A fromASN1() 0 7 1
A toString() 0 7 1
A fromString() 0 20 4
A countOfType() 0 8 1
1
<?php
2
3
declare(strict_types = 1);
4
5
namespace Sop\X501\ASN1;
6
7
use Sop\ASN1\Element;
8
use Sop\ASN1\Type\Constructed\Sequence;
9
use Sop\ASN1\Type\UnspecifiedType;
10
use Sop\X501\ASN1\AttributeValue\AttributeValue;
11
use Sop\X501\DN\DNParser;
12
13
/**
14
 * Implements *Name* ASN.1 type.
15
 *
16
 * Since *Name* is a CHOICE only supporting *RDNSequence* type,
17
 * this class implements *RDNSequence* semantics as well.
18
 *
19
 * @see https://www.itu.int/ITU-T/formal-language/itu-t/x/x501/2012/InformationFramework.html#InformationFramework.Name
20
 */
21
class Name implements \Countable, \IteratorAggregate
22
{
23
    /**
24
     * Relative distinguished name components.
25
     *
26
     * @var RDN[]
27
     */
28
    protected $_rdns;
29
30
    /**
31
     * Constructor.
32
     *
33
     * @param RDN ...$rdns RDN components
34
     */
35 37
    public function __construct(RDN ...$rdns)
36
    {
37 37
        $this->_rdns = $rdns;
38 37
    }
39
40
    /**
41
     * @return string
42
     */
43 1
    public function __toString()
44
    {
45 1
        return $this->toString();
46
    }
47
48
    /**
49
     * Initialize from ASN.1.
50
     *
51
     * @param Sequence $seq
52
     *
53
     * @return self
54
     */
55 1
    public static function fromASN1(Sequence $seq): self
56
    {
57 1
        $rdns = array_map(
58
            function (UnspecifiedType $el) {
59 1
                return RDN::fromASN1($el->asSet());
60 1
            }, $seq->elements());
61 1
        return new self(...$rdns);
62
    }
63
64
    /**
65
     * Initialize from distinguished name string.
66
     *
67
     * @see https://tools.ietf.org/html/rfc1779
68
     *
69
     * @param string $str
70
     *
71
     * @return self
72
     */
73 36
    public static function fromString(string $str): self
74
    {
75 36
        $rdns = [];
76 36
        foreach (DNParser::parseString($str) as $nameComponent) {
77 36
            $attribs = [];
78 36
            foreach ($nameComponent as [$name, $val]) {
79 36
                $type = AttributeType::fromName($name);
80
                // hexstrings are parsed to ASN.1 elements
81 36
                if ($val instanceof Element) {
82 4
                    $el = $val;
83
                } else {
84 32
                    $el = AttributeType::asn1StringForType($type->oid(), $val);
85
                }
86 36
                $value = AttributeValue::fromASN1ByOID($type->oid(),
87 36
                    $el->asUnspecified());
88 36
                $attribs[] = new AttributeTypeAndValue($type, $value);
89
            }
90 36
            $rdns[] = new RDN(...$attribs);
91
        }
92 36
        return new self(...$rdns);
93
    }
94
95
    /**
96
     * Generate ASN.1 structure.
97
     *
98
     * @return Sequence
99
     */
100 1
    public function toASN1(): Sequence
101
    {
102 1
        $elements = array_map(
103
            function (RDN $rdn) {
104 1
                return $rdn->toASN1();
105 1
            }, $this->_rdns);
106 1
        return new Sequence(...$elements);
107
    }
108
109
    /**
110
     * Get distinguised name string conforming to RFC 2253.
111
     *
112
     * @see https://tools.ietf.org/html/rfc2253#section-2.1
113
     *
114
     * @return string
115
     */
116 13
    public function toString(): string
117
    {
118 13
        $parts = array_map(
119
            function (RDN $rdn) {
120 13
                return $rdn->toString();
121 13
            }, array_reverse($this->_rdns));
122 13
        return implode(',', $parts);
123
    }
124
125
    /**
126
     * Whether name is semantically equal to other.
127
     *
128
     * Comparison conforms to RFC 4518 string preparation algorithm.
129
     *
130
     * @see https://tools.ietf.org/html/rfc4518
131
     *
132
     * @param Name $other Object to compare to
133
     *
134
     * @return bool
135
     */
136 23
    public function equals(Name $other): bool
137
    {
138
        // if RDN count doesn't match
139 23
        if (count($this) !== count($other)) {
140 1
            return false;
141
        }
142 22
        for ($i = count($this) - 1; $i >= 0; --$i) {
143 22
            $rdn1 = $this->_rdns[$i];
144 22
            $rdn2 = $other->_rdns[$i];
145 22
            if (!$rdn1->equals($rdn2)) {
146 9
                return false;
147
            }
148
        }
149 13
        return true;
150
    }
151
152
    /**
153
     * Get all RDN objects.
154
     *
155
     * @return RDN[]
156
     */
157 1
    public function all(): array
158
    {
159 1
        return $this->_rdns;
160
    }
161
162
    /**
163
     * Get the first AttributeValue of given type.
164
     *
165
     * Relative name components shall be traversed in encoding order, which is
166
     * reversed in regards to the string representation.
167
     * Multi-valued RDN with multiple attributes of the requested type is
168
     * ambiguous and shall throw an exception.
169
     *
170
     * @param string $name Attribute OID or name
171
     *
172
     * @throws \RuntimeException If attribute cannot be resolved
173
     *
174
     * @return AttributeValue
175
     */
176 3
    public function firstValueOf(string $name): AttributeValue
177
    {
178 3
        $oid = AttributeType::attrNameToOID($name);
179 3
        foreach ($this->_rdns as $rdn) {
180 3
            $tvs = $rdn->allOf($oid);
181 3
            if (count($tvs) > 1) {
182 1
                throw new \RangeException("RDN with multiple {$name} attributes.");
183
            }
184 2
            if (1 === count($tvs)) {
185 2
                return $tvs[0]->value();
186
            }
187
        }
188 1
        throw new \RangeException("Attribute {$name} not found.");
189
    }
190
191
    /**
192
     * @see \Countable::count()
193
     *
194
     * @return int
195
     */
196 24
    public function count(): int
197
    {
198 24
        return count($this->_rdns);
199
    }
200
201
    /**
202
     * Get the number of attributes of given type.
203
     *
204
     * @param string $name Attribute OID or name
205
     *
206
     * @return int
207
     */
208 2
    public function countOfType(string $name): int
209
    {
210 2
        $oid = AttributeType::attrNameToOID($name);
211 2
        return (int) array_sum(
212 2
            array_map(
213
                function (RDN $rdn) use ($oid): int {
214 2
                    return count($rdn->allOf($oid));
215 2
                }, $this->_rdns));
216
    }
217
218
    /**
219
     * @see \IteratorAggregate::getIterator()
220
     *
221
     * @return \ArrayIterator
222
     */
223 1
    public function getIterator(): \ArrayIterator
224
    {
225 1
        return new \ArrayIterator($this->_rdns);
226
    }
227
}
228