1 module types.universal.objectidentifier;
2 import asn1;
3 import 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 ObjectIdenifier 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(OIDNode[] nodes ...)
114     {
115         if (nodes.length < 2u)
116             throw new OIDException
117             ("At least two nodes must be provided to ObjectIdenifier 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 ObjectIdenifier 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 
177         if (numbers[0] == 0u || numbers[0] == 1u)
178         {
179             if (numbers[1] > 39u)
180                 throw new OIDException
181                 (
182                     "Second object identifier node number cannot be greater " ~
183                     "than 39 if the first node number is either 0 or 1."
184                 );
185         }
186         else if (numbers[0] == 2u)
187         {
188             if (numbers[1] > 175u)
189                 throw new OIDException
190                 (
191                     "Second object identifier node number cannot be greater " ~
192                     "than 175 if the first node number is 2."
193                 );
194         }
195         else
196             throw new OIDException
197             ("First object identifier node number can only be 0, 1, or 2.");
198 
199         OIDNode[] nodes = [];
200         foreach (number; numbers)
201         {
202             nodes ~= OIDNode(number);
203         }
204 
205         this.nodes = nodes.idup;
206     }
207 
208     @system
209     unittest
210     {
211         assert((new OID("0.0.1.127")).numericArray == [ 0, 0, 1, 127 ]);
212         assert((new OID("1.1.256.1")).numericArray == [ 1, 1, 256, 1 ]);
213         assert((new OID("2.174.3.1")).numericArray == [ 2, 174, 3, 1 ]);
214 
215         // Test an invalid first subidentifier
216         assertThrown!OIDException(new OID("3.0.1.1"));
217 
218         // Test an invalid second identifier
219         assertThrown!OIDException(new OID("0.64.1.1"));
220         assertThrown!OIDException(new OID("1.64.1.1"));
221         assertThrown!OIDException(new OID("1.178.1.1"));
222 
223         // Test terminal zero
224         assert((new OID("1.0.1.0")).numericArray == [ 1, 0, 1, 0 ]);
225 
226         // Test terminal large number
227         assert((new OID("1.0.1.65537")).numericArray == [ 1, 0, 1, 65537 ]);
228     }
229 
230     ///
231     override public @system
232     bool opEquals(in Object other) const
233     {
234         const OID that = cast(OID) other;
235         if (that is null) return false;
236         if (this.nodes.length != that.nodes.length) return false;
237         for (ptrdiff_t i = 0; i < this.nodes.length; i++)
238         {
239             if (this.nodes[i].number != that.nodes[i].number) return false;
240         }
241         return true;
242     }
243 
244     @system
245     unittest
246     {
247         const OID a = new OID(1, 3, 6, 4, 1, 5);
248         const OID b = new OID(1, 3, 6, 4, 1, 5);
249         assert(a == b);
250         const OID c = new OID(1, 3, 6, 4, 1, 6);
251         assert(a != c);
252         const OID d = new OID(2, 3, 6, 4, 1, 6);
253         assert(c != d);
254     }
255 
256     /**
257         Returns: the $(D OIDNode) at the specified index.
258         Throws:
259             RangeError = if invalid index specified.
260     */
261     public @safe @nogc nothrow
262     OIDNode opIndex(in ptrdiff_t index) const
263     {
264         return this.nodes[index];
265     }
266 
267     @system
268     unittest
269     {
270         const OID oid = new OID(1, 3, 7000);
271         assert((oid[0].number == 1) && (oid[1].number == 3) && (oid[2].number == 7000));
272     }
273 
274     /**
275         Returns: a range of $(D OIDNode)s from the OID.
276         Throws:
277             RangeError = if invalid indices are specified.
278     */
279     public @system @nogc nothrow
280     OIDNode[] opSlice(in ptrdiff_t index1, in ptrdiff_t index2) const
281     {
282         return cast(OIDNode[]) this.nodes[index1 .. index2];
283     }
284 
285     /// Returns the length of the OID.
286     public @safe @nogc nothrow
287     size_t opDollar() const
288     {
289         return this.nodes.length;
290     }
291 
292     /**
293         Returns: The descriptor at the specified index.
294         Throws:
295         $(UL
296             $(LI $(D RangeError) if an invalid index is specified)
297         )
298     */
299     public @safe @nogc nothrow
300     string descriptor(in size_t index) const
301     {
302         return this.nodes[index].descriptor;
303     }
304 
305     @system
306     unittest
307     {
308         const OID oid = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
309         assert(oid.descriptor(1) == "registered-org");
310         assertThrown!RangeError(oid.descriptor(6));
311     }
312 
313     ///
314     public alias numbers = numericArray;
315     /**
316         Returns:
317             an array of $(D size_t)s representing the dot-delimited sequence of
318             integers that constitute the numeric OID.
319     */
320     public @property @safe nothrow
321     size_t[] numericArray() const
322     {
323         size_t[] ret;
324         ret.length = this.nodes.length;
325         for (ptrdiff_t i = 0; i < this.nodes.length; i++)
326         {
327             ret[i] = this.nodes[i].number;
328         }
329         return ret;
330     }
331 
332     @system
333     unittest
334     {
335         const OID a = new OID(1, 3, 6, 4, 1, 5);
336         assert(a.numericArray == [ 1, 3, 6, 4, 1, 5 ]);
337     }
338 
339     ///
340     public alias asn1Notation = abstractSyntaxNotation1Notation;
341     /// Returns: the OID in ASN.1 Notation
342     public @property @safe nothrow
343     string abstractSyntaxNotation1Notation() const
344     {
345         Appender!string ret = appender!string();
346         ret.put("{");
347         foreach(node; this.nodes)
348         {
349             if (this.showDescriptors && node.descriptor != "")
350             {
351                 ret.put(node.descriptor ~ '(' ~ text(node.number) ~ ") ");
352             }
353             else
354             {
355                 ret.put(text(node.number) ~ ' ');
356             }
357         }
358         return (ret.data[0 .. $-1] ~ '}');
359     }
360 
361     @system
362     unittest
363     {
364         OID a = new OID(1, 3, 6, 4, 1, 5);
365         a.showDescriptors = true;
366         assert(a.asn1Notation == "{1 3 6 4 1 5}");
367 
368         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
369         b.showDescriptors = true;
370         assert(b.asn1Notation == "{iso(1) registered-org(3) dod(4)}");
371     }
372 
373     /**
374         Returns:
375             the OID as a dot-delimited string, where all nodes with descriptors
376             are represented as descriptors instead of numbers
377     */
378     public @property @safe nothrow
379     string dotNotation() const
380     {
381         Appender!string ret = appender!string();
382         foreach (node; this.nodes)
383         {
384             if (this.showDescriptors && node.descriptor != "")
385             {
386                 ret.put(node.descriptor);
387             }
388             else
389             {
390                 ret.put(text(node.number));
391             }
392             ret.put('.');
393         }
394         return ret.data[0 .. $-1];
395     }
396 
397     @system
398     unittest
399     {
400         OID a = new OID(1, 3, 6, 4, 1, 5);
401         a.showDescriptors = true;
402         assert(a.dotNotation == "1.3.6.4.1.5");
403 
404         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
405         b.showDescriptors = true;
406         assert(b.dotNotation == "iso.registered-org.dod");
407     }
408 
409     ///
410     public alias iriNotation = internationalizedResourceIdentifierNotation;
411     ///
412     public alias uriNotation = internationalizedResourceIdentifierNotation;
413     ///
414     public alias uniformResourceIdentifierNotation = internationalizedResourceIdentifierNotation;
415     /**
416         Returns:
417             the OID as a forward-slash-delimited string (as one might expect in
418             a URI / IRI path), where all nodes with descriptors are represented
419             as descriptors instead of numbers
420     */
421     public @property @system
422     string internationalizedResourceIdentifierNotation() const
423     {
424         import std.uri : encodeComponent; // @system
425         Appender!string ret = appender!string();
426         ret.put("/");
427         foreach (node; this.nodes)
428         {
429             if (this.showDescriptors && node.descriptor != "")
430             {
431                 ret.put(encodeComponent(node.descriptor) ~ '/');
432             }
433             else
434             {
435                 ret.put(text(node.number) ~ '/');
436             }
437         }
438         return ret.data[0 .. $-1];
439     }
440 
441     @system
442     unittest
443     {
444         OID a = new OID(1, 3, 6, 4, 1, 5);
445         a.showDescriptors = true;
446         assert(a.uriNotation == "/1/3/6/4/1/5");
447 
448         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
449         b.showDescriptors = true;
450         assert(b.uriNotation == "/iso/registered-org/dod");
451     }
452 
453     ///
454     public alias urnNotation = uniformResourceNameNotation;
455     /**
456         Returns:
457             the OID as a URN, where all nodes of the OID are translated to a
458             segment in the URN path, and where all nodes are represented as
459             numbers regardless of whether or not they have a descriptor
460         See_Also:
461             $(LINK https://www.ietf.org/rfc/rfc3061.txt, RFC 3061)
462     */
463     public @property @safe nothrow
464     string uniformResourceNameNotation() const
465     {
466         Appender!string ret = appender!string();
467         ret.put("urn:oid:");
468         foreach (node; this.nodes)
469         {
470             ret.put(text(node.number) ~ ':');
471         }
472         return ret.data[0 .. $-1];
473     }
474 
475     @system
476     unittest
477     {
478         OID a = new OID(1, 3, 6, 4, 1, 5);
479         a.showDescriptors = true;
480         assert(a.urnNotation == "urn:oid:1:3:6:4:1:5");
481 
482         OID b = new OID(OIDNode(1, "iso"), OIDNode(3, "registered-org"), OIDNode(4, "dod"));
483         b.showDescriptors = true;
484         assert(b.urnNotation == "urn:oid:1:3:4");
485     }
486 
487     ///
488     override public @property
489     string toString()
490     {
491         return this.dotNotation();
492     }
493 
494     /**
495         An override so that associative arrays can use an $(D OIDNode) as a
496         key.
497         Returns: A $(D size_t) that represents a hash of the $(D OIDNode)
498     */
499     override public nothrow @trusted
500     size_t toHash() const
501     {
502         size_t sum;
503         foreach (node; this.nodes)
504         {
505             sum += typeid(node).getHash(cast(const void*) &node);
506         }
507         return sum;
508     }
509 
510 }