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 }