1 module asn1.types.universal.objectidentifier;
2 import asn1.constants;
3 import asn1.types.oidtype;
4 import std.array : appender, Appender;
5 
6 ///
7 public alias OIDException = ObjectIdentifierException;
8 ///
9 public alias ObjectIDException = ObjectIdentifierException;
10 ///
11 public
12 class ObjectIdentifierException : ASN1Exception
13 {
14     import std.exception : basicExceptionCtors;
15     mixin basicExceptionCtors;
16 }
17 
18 ///
19 public alias OID = ObjectIdentifier;
20 ///
21 public alias ObjectID = ObjectIdentifier;
22 /**
23     A class for Object Identifiers that supports object descriptors and various
24     output formatting.
25 */
26 public class ObjectIdentifier
27 {
28     import std.conv : text;
29 
30     ///
31     static public bool showDescriptors = true;
32     ///
33     immutable public OIDNode[] nodes;
34 
35     /// Returns: the number of nodes in the OID.
36     public @property @safe
37     size_t length() const
38     {
39         return this.nodes.length;
40     }
41 
42     @system
43     unittest
44     {
45         const OID oid = new OID(1, 3, 6, 4, 1);
46         assert(oid.length == 5);
47     }
48 
49     /**
50         Constructor for an Object Identifier
51 
52         Params:
53         $(UL
54             $(LI $(D numbers) = an array of unsigned integers representing the Object Identifier)
55         )
56         Throws:
57         $(UL
58             $(LI $(D OIDException) if fewer than two numbers are provided, or if the
59                 first number is not 0, 1, or 2, or if the second number is
60                 greater than 39)
61         )
62     */
63     public @safe
64     this(in size_t[] numbers ...)
65     {
66         if (numbers.length < 2u)
67             throw new OIDException
68             ("At least two nodes must be provided to ObjectIdentifier constructor.");
69 
70         if (numbers[0] == 0u || numbers[0] == 1u)
71         {
72             if (numbers[1] > 39u)
73                 throw new OIDException
74                 (
75                     "Second object identifier node number cannot be greater " ~
76                     "than 39 if the first node number is either 0 or 1."
77                 );
78         }
79         else if (numbers[0] == 2u)
80         {
81             if (numbers[1] > 175u)
82                 throw new OIDException
83                 (
84                     "Second object identifier node number cannot be greater " ~
85                     "than 175 if the first node number is 2."
86                 );
87         }
88         else
89             throw new OIDException
90             ("First object identifier node number can only be 0, 1, or 2.");
91 
92         OIDNode[] nodes = [];
93         foreach (number; numbers)
94         {
95             nodes ~= OIDNode(number);
96         }
97 
98         this.nodes = nodes.idup;
99     }
100 
101 
102     /**
103         Constructor for an Object Identifier
104 
105         Throws:
106         $(UL
107             $(LI $(D OIDException) if fewer than two nodes are provided, or if the
108                 first node is not 0, 1, or 2, or if the second node is greater
109                 than 39)
110         )
111     */
112     public @safe
113     this(in OIDNode[] nodes ...)
114     {
115         if (nodes.length < 2u)
116             throw new OIDException
117             ("At least two nodes must be provided to ObjectIdentifier constructor.");
118 
119         if (nodes[0].number == 0u || nodes[0].number == 1u)
120         {
121             if (nodes[1].number > 39u)
122                 throw new OIDException
123                 (
124                     "Second object identifier node number cannot be greater " ~
125                     "than 39 if the first node number is either 0 or 1."
126                 );
127         }
128         else if (nodes[0].number == 2u)
129         {
130             if (nodes[1].number > 175u)
131                 throw new OIDException
132                 (
133                     "Second object identifier node number cannot be greater " ~
134                     "than 175 if the first node number is 2."
135                 );
136         }
137         else
138             throw new OIDException
139             ("First object identifier node number can only be 0, 1, or 2.");
140 
141         this.nodes = nodes.idup;
142     }
143 
144     /**
145         Constructor for an Object Identifier
146 
147         Params:
148         $(UL
149             $(LI $(D str) = the dot-delimited form of the object identifier)
150         )
151         Throws:
152         $(UL
153             $(LI $(D OIDException) if fewer than two nodes are provided, or if the
154                 first node is not 0, 1, or 2, or if the second node is greater
155                 than 39)
156         )
157     */
158     public @safe
159     this (in string str)
160     {
161         import std.array : split;
162         import std.conv : to;
163         string[] segments = str.split(".");
164 
165         if (segments.length < 2u)
166             throw new OIDException
167             ("At least two nodes must be provided to ObjectIdentifier constructor.");
168 
169         size_t[] numbers;
170         numbers.length = segments.length;
171 
172         // for (size_t i = 0u; i < segments.length; i++)
173         // {
174         //     numbers[i] = segments[i].to!size_t;
175         // }
176         foreach (immutable size_t i, immutable string segment; segments)
177         {
178             numbers[i] = segment.to!size_t;
179         }
180 
181         if (numbers[0] == 0u || numbers[0] == 1u)
182         {
183             if (numbers[1] > 39u)
184                 throw new OIDException
185                 (
186                     "Second object identifier node number cannot be greater " ~
187                     "than 39 if the first node number is either 0 or 1."
188                 );
189         }
190         else if (numbers[0] == 2u)
191         {
192             if (numbers[1] > 175u)
193                 throw new OIDException
194                 (
195                     "Second object identifier node number cannot be greater " ~
196                     "than 175 if the first node number is 2."
197                 );
198         }
199         else
200             throw new OIDException
201             ("First object identifier node number can only be 0, 1, or 2.");
202 
203         OIDNode[] nodes = [];
204         foreach (number; numbers)
205         {
206             nodes ~= OIDNode(number);
207         }
208 
209         this.nodes = nodes.idup;
210     }
211 
212     @system
213     unittest
214     {
215         assert((new OID("0.0.1.127")).numericArray == [ 0, 0, 1, 127 ]);
216         assert((new OID("1.1.256.1")).numericArray == [ 1, 1, 256, 1 ]);
217         assert((new OID("2.174.3.1")).numericArray == [ 2, 174, 3, 1 ]);
218 
219         // Test an invalid first subidentifier
220         assertThrown!OIDException(new OID("3.0.1.1"));
221 
222         // Test an invalid second identifier
223         assertThrown!OIDException(new OID("0.64.1.1"));
224         assertThrown!OIDException(new OID("1.64.1.1"));
225         assertThrown!OIDException(new OID("1.178.1.1"));
226 
227         // Test terminal zero
228         assert((new OID("1.0.1.0")).numericArray == [ 1, 0, 1, 0 ]);
229 
230         // Test terminal large number
231         assert((new OID("1.0.1.65537")).numericArray == [ 1, 0, 1, 65537 ]);
232     }
233 
234     ///
235     override public @system
236     bool opEquals(in Object other) const
237     {
238         const OID that = cast(OID) other;
239         if (that is null) return false;
240         if (this.nodes.length != that.nodes.length) return false;
241         for (size_t i = 0u; i < this.nodes.length; i++)
242         {
243             if (this.nodes[i].number != that.nodes[i].number) return false;
244         }
245         return true;
246     }
247 
248     @system
249     unittest
250     {
251         const OID a = new OID(1, 3, 6, 4, 1, 5);
252         const OID b = new OID(1, 3, 6, 4, 1, 5);
253         assert(a == b);
254         const OID c = new OID(1, 3, 6, 4, 1, 6);
255         assert(a != c);
256         const OID d = new OID(2, 3, 6, 4, 1, 6);
257         assert(c != d);
258     }
259 
260     /**
261         Returns: the $(D OIDNode) at the specified index.
262         Throws:
263             RangeError = if invalid index specified.
264     */
265     public @safe @nogc nothrow
266     OIDNode opIndex(in ptrdiff_t index) const
267     {
268         return this.nodes[index];
269     }
270 
271     @system
272     unittest
273     {
274         const OID oid = new OID(1, 3, 7000);
275         assert((oid[0].number == 1) && (oid[1].number == 3) && (oid[2].number == 7000));
276     }
277 
278     /**
279         Returns: a range of $(D OIDNode)s from the OID.
280         Throws:
281             RangeError = if invalid indices are specified.
282     */
283     public @system @nogc nothrow
284     OIDNode[] opSlice(in ptrdiff_t index1, in ptrdiff_t index2) const
285     {
286         return cast(OIDNode[]) this.nodes[index1 .. index2];
287     }
288 
289     /// Returns the length of the OID.
290     public @safe @nogc nothrow
291     size_t opDollar() const
292     {
293         return this.nodes.length;
294     }
295 
296     /**
297         Returns: The descriptor at the specified index.
298         Throws:
299         $(UL
300             $(LI $(D RangeError) if an invalid index is specified)
301         )
302     */
303     public @safe nothrow
304     string descriptor(in size_t index) const
305     {
306         return this.nodes[index].descriptor;
307     }
308 
309     @system
310     unittest
311     {
312         const OID oid = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
313         assert(oid.descriptor(1) == "registered-org");
314         assertThrown!RangeError(oid.descriptor(6));
315     }
316 
317     ///
318     public alias numbers = numericArray;
319     /**
320         Returns:
321             an array of $(D size_t)s representing the dot-delimited sequence of
322             integers that constitute the numeric OID.
323     */
324     public @property @safe nothrow
325     size_t[] numericArray() const
326     {
327         size_t[] ret;
328         ret.length = this.nodes.length;
329         // for (size_t i = 0u; i < this.nodes.length; i++)
330         // {
331         //     ret[i] = this.nodes[i].number;
332         // }
333         foreach (immutable size_t i, immutable OIDNode node; this.nodes)
334         {
335             ret[i] = node.number;
336         }
337         return ret;
338     }
339 
340     @system
341     unittest
342     {
343         const OID a = new OID(1, 3, 6, 4, 1, 5);
344         assert(a.numericArray == [ 1, 3, 6, 4, 1, 5 ]);
345     }
346 
347     ///
348     public alias asn1Notation = abstractSyntaxNotation1Notation;
349     /// Returns: the OID in ASN.1 Notation
350     public @property @safe nothrow
351     string abstractSyntaxNotation1Notation() const
352     {
353         Appender!string ret = appender!string();
354         ret.put("{");
355         foreach(node; this.nodes)
356         {
357             if (this.showDescriptors && node.descriptor != "")
358             {
359                 ret.put(node.descriptor ~ '(' ~ text(node.number) ~ ") ");
360             }
361             else
362             {
363                 ret.put(text(node.number) ~ ' ');
364             }
365         }
366         return (ret.data[0 .. $-1] ~ '}');
367     }
368 
369     @system
370     unittest
371     {
372         OID a = new OID(1, 3, 6, 4, 1, 5);
373         a.showDescriptors = true;
374         assert(a.asn1Notation == "{1 3 6 4 1 5}");
375 
376         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
377         b.showDescriptors = true;
378         assert(b.asn1Notation == "{iso(1) registered-org(3) dod(4)}");
379     }
380 
381     /**
382         Returns:
383             the OID as a dot-delimited string, where all nodes with descriptors
384             are represented as descriptors instead of numbers
385     */
386     public @property @safe nothrow
387     string dotNotation() const
388     {
389         Appender!string ret = appender!string();
390         foreach (node; this.nodes)
391         {
392             if (this.showDescriptors && node.descriptor != "")
393             {
394                 ret.put(node.descriptor);
395             }
396             else
397             {
398                 ret.put(text(node.number));
399             }
400             ret.put('.');
401         }
402         return ret.data[0 .. $-1];
403     }
404 
405     @system
406     unittest
407     {
408         OID a = new OID(1, 3, 6, 4, 1, 5);
409         a.showDescriptors = true;
410         assert(a.dotNotation == "1.3.6.4.1.5");
411 
412         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
413         b.showDescriptors = true;
414         assert(b.dotNotation == "iso.registered-org.dod");
415     }
416 
417     ///
418     public alias iriNotation = internationalizedResourceIdentifierNotation;
419     ///
420     public alias uriNotation = internationalizedResourceIdentifierNotation;
421     ///
422     public alias uniformResourceIdentifierNotation = internationalizedResourceIdentifierNotation;
423     /**
424         Returns:
425             the OID as a forward-slash-delimited string (as one might expect in
426             a URI / IRI path), where all nodes with descriptors are represented
427             as descriptors instead of numbers
428     */
429     public @property @system
430     string internationalizedResourceIdentifierNotation() const
431     {
432         import std.uri : encodeComponent; // @system
433         Appender!string ret = appender!string();
434         ret.put("/");
435         foreach (node; this.nodes)
436         {
437             if (this.showDescriptors && node.descriptor != "")
438             {
439                 ret.put(encodeComponent(node.descriptor) ~ '/');
440             }
441             else
442             {
443                 ret.put(text(node.number) ~ '/');
444             }
445         }
446         return ret.data[0 .. $-1];
447     }
448 
449     @system
450     unittest
451     {
452         OID a = new OID(1, 3, 6, 4, 1, 5);
453         a.showDescriptors = true;
454         assert(a.uriNotation == "/1/3/6/4/1/5");
455 
456         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
457         b.showDescriptors = true;
458         assert(b.uriNotation == "/iso/registered-org/dod");
459     }
460 
461     ///
462     public alias urnNotation = uniformResourceNameNotation;
463     /**
464         Returns:
465             the OID as a URN, where all nodes of the OID are translated to a
466             segment in the URN path, and where all nodes are represented as
467             numbers regardless of whether or not they have a descriptor
468         See_Also:
469             $(LINK https://www.ietf.org/rfc/rfc3061.txt, RFC 3061)
470     */
471     public @property @safe nothrow
472     string uniformResourceNameNotation() const
473     {
474         Appender!string ret = appender!string();
475         ret.put("urn:oid:");
476         foreach (node; this.nodes)
477         {
478             ret.put(text(node.number) ~ ':');
479         }
480         return ret.data[0 .. $-1];
481     }
482 
483     @system
484     unittest
485     {
486         OID a = new OID(1, 3, 6, 4, 1, 5);
487         a.showDescriptors = true;
488         assert(a.urnNotation == "urn:oid:1:3:6:4:1:5");
489 
490         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
491         b.showDescriptors = true;
492         assert(b.urnNotation == "urn:oid:1:3:4");
493     }
494 
495     ///
496     override public @property
497     string toString()
498     {
499         return this.dotNotation();
500     }
501 
502     /**
503         An override so that associative arrays can use an $(D OIDNode) as a
504         key.
505         Returns: A $(D size_t) that represents a hash of the $(D OIDNode)
506     */
507     override public nothrow @trusted
508     size_t toHash() const
509     {
510         size_t sum;
511         foreach (node; this.nodes)
512         {
513             sum += typeid(node).getHash(cast(const void*) &node);
514         }
515         return sum;
516     }
517 
518 }