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 }