001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.shared.asn1.primitives; 021 022 023 import java.io.Serializable; 024 import java.util.Arrays; 025 026 import org.apache.directory.shared.asn1.codec.DecoderException; 027 import org.apache.directory.shared.asn1.util.Asn1StringUtils; 028 import org.apache.directory.shared.i18n.I18n; 029 030 031 /** 032 * This class implement an OID (Object Identifier). 033 * 034 * An OID is encoded as a list of bytes representing integers. 035 * 036 * An OID has a numeric representation where number are separated with dots : 037 * SPNEGO Oid = 1.3.6.1.5.5.2 038 * 039 * Translating from a byte list to a dot separated list of number follows the rules : 040 * - the first number is in [0..2] 041 * - the second number is in [0..39] if the first number is 0 or 1 042 * - the first byte has a value equal to : number 1 * 40 + number two 043 * - the upper bit of a byte is set if the next byte is a part of the number 044 * 045 * For instance, the SPNEGO Oid (1.3.6.1.5.5.2) will be encoded : 046 * 1.3 -> 0x2B (1*40 + 3 = 43 = 0x2B) 047 * .6 -> 0x06 048 * .1 -> 0x01 049 * .5 -> 0x05 050 * .5 -> 0x05 051 * .2 -> 0x02 052 * 053 * The Kerberos V5 Oid (1.2.840.48018.1.2.2) will be encoded : 054 * 1.2 -> 0x2A (1*40 + 2 = 42 = 0x2A) 055 * 840 -> 0x86 0x48 (840 = 6 * 128 + 72 = (0x06 | 0x80) 0x48 = 0x86 0x48 056 * 48018 -> 0x82 0xF7 0x12 (2 * 128 * 128 + 119 * 128 + 18 = (0x02 | 0x80) (0x77 | 0x80) 0x12 057 * .1 -> 0x01 058 * .2 -> 0x02 059 * .2 -> 0x02 060 * 061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 062 * @version $Rev: 912399 $, $Date: 2010-02-21 21:52:31 +0100 (Sun, 21 Feb 2010) $ 063 */ 064 public class OID implements Serializable 065 { 066 private static final long serialVersionUID = 1L; 067 068 // ~ Instance fields 069 // ---------------------------------------------------------------------------- 070 071 /** The OID as a array of int */ 072 private long[] oidValues; 073 074 /** Th hashcode, computed only once */ 075 private int hash; 076 077 078 // ~ Constructors 079 // ------------------------------------------------------------------------------- 080 081 /** 082 * Creates a new OID object. 083 */ 084 public OID() 085 { 086 // We should not create this kind of object directly, it must 087 // be created through the factory. 088 hash = 0; 089 } 090 091 092 /** 093 * Create a new OID object from a byte array 094 * 095 * @param oid the byte array containing the OID 096 * @throws DecoderException if the byte array does not contain a 097 * valid OID 098 */ 099 public OID( byte[] oid ) throws DecoderException 100 { 101 setOID( oid ); 102 hash = computeHashCode(); 103 } 104 105 106 /** 107 * Create a new OID object from a String 108 * 109 * @param oid The String which is supposed to be an OID 110 * @throws DecoderException if the byte array does not contain a 111 * valid OID 112 */ 113 public OID( String oid ) throws DecoderException 114 { 115 setOID( oid ); 116 hash = computeHashCode(); 117 } 118 119 120 // ~ Methods 121 // ------------------------------------------------------------------------------------ 122 /** 123 * Set the OID. It will be translated from a byte array to an internal 124 * representation. 125 * 126 * @param oid The bytes containing the OID 127 * @throws DecoderException if the byte array does not contains a valid OID 128 */ 129 public void setOID( byte[] oid ) throws DecoderException 130 { 131 132 if ( oid == null ) 133 { 134 throw new DecoderException( I18n.err( I18n.ERR_00032 ) ); 135 } 136 137 if ( oid.length < 1 ) 138 { 139 throw new DecoderException( I18n.err( I18n.ERR_00033, Asn1StringUtils.dumpBytes( oid ) ) ); 140 } 141 142 // First, we have to calculate the number of int to allocate 143 int nbValues = 1; 144 145 int pos = 0; 146 147 while ( pos < oid.length ) 148 { 149 150 if ( oid[pos] >= 0 ) 151 { 152 nbValues++; 153 } 154 155 pos++; 156 } 157 158 oidValues = new long[nbValues]; 159 160 nbValues = 0; 161 pos = 0; 162 163 int accumulator = 0; 164 165 if ( ( oid[0] < 0 ) || ( oid[0] >= 80 ) ) 166 { 167 oidValues[nbValues++] = 2; 168 169 while ( pos < oid.length ) 170 { 171 172 if ( oid[pos] >= 0 ) 173 { 174 oidValues[nbValues++] = ( ( accumulator << 7 ) + oid[pos] ) - 80; 175 accumulator = 0; 176 pos++; 177 break; 178 } 179 else 180 { 181 accumulator = ( accumulator << 7 ) + ( oid[pos] & 0x007F ); 182 } 183 184 pos++; 185 } 186 } 187 else if ( oid[0] < 40 ) 188 { 189 oidValues[nbValues++] = 0; 190 oidValues[nbValues++] = oid[pos++]; // itu-t 191 } 192 else 193 // oid[0] is < 80 194 { 195 oidValues[nbValues++] = 1; 196 oidValues[nbValues++] = oid[pos++] - 40; // iso 197 } 198 199 while ( pos < oid.length ) 200 { 201 202 if ( oid[pos] >= 0 ) 203 { 204 oidValues[nbValues++] = ( accumulator << 7 ) + oid[pos]; 205 accumulator = 0; 206 } 207 else 208 { 209 accumulator = ( accumulator << 7 ) + ( oid[pos] & 0x007F ); 210 } 211 212 pos++; 213 } 214 215 hash = computeHashCode(); 216 } 217 218 219 /** 220 * Set the OID. It will be translated from a String to an internal 221 * representation. 222 * 223 * The syntax will be controled in respect with this rule : 224 * OID = ( [ '0' | '1' ] '.' [ 0 .. 39 ] | '2' '.' int) ( '.' int )* 225 * 226 * @param oid The String containing the OID 227 * @throws DecoderException if the byte array does not contains a valid OID 228 */ 229 public void setOID( String oid ) throws DecoderException 230 { 231 232 if ( ( oid == null ) || ( oid.length() == 0 ) ) 233 { 234 throw new DecoderException( I18n.err( I18n.ERR_00032 ) ); 235 } 236 237 int nbValues = 1; 238 char[] chars = oid.toCharArray(); 239 boolean dotSeen = false; 240 241 // Count the number of int to allocate. 242 for ( char c:chars ) 243 { 244 245 if ( c == '.' ) 246 { 247 248 if ( dotSeen ) 249 { 250 251 // Two dots, that's an error ! 252 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 253 } 254 255 nbValues++; 256 dotSeen = true; 257 } 258 else 259 { 260 dotSeen = false; 261 } 262 } 263 264 // We must have at least 2 ints 265 if ( nbValues < 2 ) 266 { 267 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 268 } 269 270 oidValues = new long[nbValues]; 271 272 int pos = 0; 273 int intPos = 0; 274 275 // This flag is used to forbid a second value above 39 if the 276 // first value is 0 or 1 (itu_t or iso arcs) 277 boolean ituOrIso = false; 278 279 // The first value 280 switch ( chars[pos] ) 281 { 282 283 case '0': // itu-t 284 case '1': // iso 285 ituOrIso = true; 286 // fallthrough 287 288 case '2': // joint-iso-itu-t 289 oidValues[intPos++] = chars[pos++] - '0'; 290 break; 291 292 default: // error, this value is not allowed 293 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 294 } 295 296 // We must have a dot 297 if ( chars[pos++] != '.' ) 298 { 299 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 300 } 301 302 dotSeen = true; 303 304 int value = 0; 305 306 for ( int i = pos; i < chars.length; i++ ) 307 { 308 309 if ( chars[i] == '.' ) 310 { 311 312 if ( dotSeen ) 313 { 314 315 // Two dots, that's an error ! 316 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 317 } 318 319 if ( ituOrIso && value > 39 ) 320 { 321 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 322 } 323 else 324 { 325 ituOrIso = false; 326 } 327 328 nbValues++; 329 dotSeen = true; 330 oidValues[intPos++] = value; 331 value = 0; 332 } 333 else if ( ( chars[i] >= 0x30 ) && ( chars[i] <= 0x39 ) ) 334 { 335 dotSeen = false; 336 value = ( ( value * 10 ) + chars[i] ) - '0'; 337 338 } 339 else 340 { 341 342 // We don't have a number, this is an error 343 throw new DecoderException( I18n.err( I18n.ERR_00033, oid ) ); 344 } 345 } 346 347 oidValues[intPos] = value; 348 349 hash = computeHashCode(); 350 } 351 352 353 /** 354 * Get an array of long from the OID 355 * 356 * @return An array of long representing the OID 357 */ 358 public long[] getOIDValues() 359 { 360 return oidValues; 361 } 362 363 364 /** 365 * Get the number of bytes necessary to store the OID 366 * 367 * @return An int representing the length of the OID 368 */ 369 public int getOIDLength() 370 { 371 long value = oidValues[0] * 40 + oidValues[1]; 372 int nbBytes = 0; 373 374 if ( value < 128 ) 375 { 376 nbBytes = 1; 377 } 378 else if ( value < 16384 ) 379 { 380 nbBytes = 2; 381 } 382 else if ( value < 2097152 ) 383 { 384 nbBytes = 3; 385 } 386 else if ( value < 268435456 ) 387 { 388 nbBytes = 4; 389 } 390 else 391 { 392 nbBytes = 5; 393 } 394 395 for ( int i = 2; i < oidValues.length; i++ ) 396 { 397 value = oidValues[i]; 398 399 if ( value < 128 ) 400 { 401 nbBytes += 1; 402 } 403 else if ( value < 16384 ) 404 { 405 nbBytes += 2; 406 } 407 else if ( value < 2097152 ) 408 { 409 nbBytes += 3; 410 } 411 else if ( value < 268435456 ) 412 { 413 nbBytes += 4; 414 } 415 else 416 { 417 nbBytes += 5; 418 } 419 } 420 421 return nbBytes; 422 } 423 424 425 /** 426 * Get an array of bytes from the OID 427 * 428 * @return An array of int representing the OID 429 */ 430 public byte[] getOID() 431 { 432 long value = oidValues[0] * 40 + oidValues[1]; 433 long firstValues = value; 434 435 byte[] bytes = new byte[getOIDLength()]; 436 int pos = 0; 437 438 if ( oidValues[0] < 2 ) 439 { 440 bytes[pos++] = ( byte ) ( oidValues[0] * 40 + oidValues[1] ); 441 } 442 else 443 { 444 if ( firstValues < 128 ) 445 { 446 bytes[pos++] = ( byte ) ( firstValues ); 447 } 448 else if ( firstValues < 16384 ) 449 { 450 bytes[pos++] = ( byte ) ( ( firstValues >> 7 ) | 0x0080 ); 451 bytes[pos++] = ( byte ) ( firstValues & 0x007F ); 452 } 453 else if ( value < 2097152 ) 454 { 455 bytes[pos++] = ( byte ) ( ( firstValues >> 14 ) | 0x0080 ); 456 bytes[pos++] = ( byte ) ( ( ( firstValues >> 7 ) & 0x007F ) | 0x0080 ); 457 bytes[pos++] = ( byte ) ( firstValues & 0x007F ); 458 } 459 else if ( value < 268435456 ) 460 { 461 bytes[pos++] = ( byte ) ( ( firstValues >> 21 ) | 0x0080 ); 462 bytes[pos++] = ( byte ) ( ( ( firstValues >> 14 ) & 0x007F ) | 0x0080 ); 463 bytes[pos++] = ( byte ) ( ( ( firstValues >> 7 ) & 0x007F ) | 0x0080 ); 464 bytes[pos++] = ( byte ) ( firstValues & 0x007F ); 465 } 466 else 467 { 468 bytes[pos++] = ( byte ) ( ( firstValues >> 28 ) | 0x0080 ); 469 bytes[pos++] = ( byte ) ( ( ( firstValues >> 21 ) & 0x007F ) | 0x0080 ); 470 bytes[pos++] = ( byte ) ( ( ( firstValues >> 14 ) & 0x007F ) | 0x0080 ); 471 bytes[pos++] = ( byte ) ( ( ( firstValues >> 7 ) & 0x007F ) | 0x0080 ); 472 bytes[pos++] = ( byte ) ( firstValues & 0x007F ); 473 } 474 } 475 476 for ( int i = 2; i < oidValues.length; i++ ) 477 { 478 value = oidValues[i]; 479 480 if ( value < 128 ) 481 { 482 bytes[pos++] = ( byte ) ( value ); 483 } 484 else if ( value < 16384 ) 485 { 486 bytes[pos++] = ( byte ) ( ( value >> 7 ) | 0x0080 ); 487 bytes[pos++] = ( byte ) ( value & 0x007F ); 488 } 489 else if ( value < 2097152 ) 490 { 491 bytes[pos++] = ( byte ) ( ( value >> 14 ) | 0x0080 ); 492 bytes[pos++] = ( byte ) ( ( ( value >> 7 ) & 0x007F ) | 0x0080 ); 493 bytes[pos++] = ( byte ) ( value & 0x007F ); 494 } 495 else if ( value < 268435456 ) 496 { 497 bytes[pos++] = ( byte ) ( ( value >> 21 ) | 0x0080 ); 498 bytes[pos++] = ( byte ) ( ( ( value >> 14 ) & 0x007F ) | 0x0080 ); 499 bytes[pos++] = ( byte ) ( ( ( value >> 7 ) & 0x007F ) | 0x0080 ); 500 bytes[pos++] = ( byte ) ( value & 0x007F ); 501 } 502 else 503 { 504 bytes[pos++] = ( byte ) ( ( value >> 28 ) | 0x0080 ); 505 bytes[pos++] = ( byte ) ( ( ( value >> 21 ) & 0x007F ) | 0x0080 ); 506 bytes[pos++] = ( byte ) ( ( ( value >> 14 ) & 0x007F ) | 0x0080 ); 507 bytes[pos++] = ( byte ) ( ( ( value >> 7 ) & 0x007F ) | 0x0080 ); 508 bytes[pos++] = ( byte ) ( value & 0x007F ); 509 } 510 } 511 512 return bytes; 513 } 514 515 516 /** 517 * Compute the hash code for this object. No need to compute 518 * it live when calling the hashCode() method, as an OID 519 * never change. 520 * 521 * @return the OID's hash code 522 */ 523 private int computeHashCode() 524 { 525 int h = 37; 526 527 for ( long val:oidValues ) 528 { 529 int low = (int)(val & 0x0000FFFFL); 530 int high = (int)(val >> 32); 531 h = h*17 + high; 532 h = h*17 + low; 533 } 534 535 return h; 536 } 537 538 /** 539 * Check that an OID is valid 540 * @param oid The oid to be checked 541 * @return <code>true</code> if the OID is valid 542 */ 543 public static boolean isOID( String oid ) 544 { 545 if ( ( oid == null ) || ( oid.length() == 0 ) ) 546 { 547 return false; 548 } 549 550 int nbValues = 1; 551 byte[] bytes = oid.getBytes(); 552 boolean dotSeen = false; 553 554 // Count the number of int to allocate. 555 for ( byte b:bytes ) 556 { 557 558 if ( b == '.' ) 559 { 560 561 if ( dotSeen ) 562 { 563 564 // Two dots, that's an error ! 565 return false; 566 } 567 568 nbValues++; 569 dotSeen = true; 570 } 571 else 572 { 573 dotSeen = false; 574 } 575 } 576 577 // We must have at least 2 ints 578 if ( nbValues < 2 ) 579 { 580 return false; 581 } 582 583 int pos = 0; 584 585 // This flag is used to forbid a second value above 39 if the 586 // first value is 0 or 1 (itu_t or iso arcs) 587 boolean ituOrIso = false; 588 589 // The first value 590 switch ( bytes[pos++] ) 591 { 592 593 case '0': // itu-t 594 // fallthrough 595 case '1': // iso 596 ituOrIso = true; 597 // fallthrough 598 599 case '2': // joint-iso-itu-t 600 break; 601 602 default: // error, this value is not allowed 603 return false; 604 } 605 606 // We must have a dot 607 if ( bytes[pos++] != '.' ) 608 { 609 return false; 610 } 611 612 dotSeen = true; 613 614 long value = 0; 615 616 for ( int i = pos; i < bytes.length; i++ ) 617 { 618 619 if ( bytes[i] == '.' ) 620 { 621 622 if ( dotSeen ) 623 { 624 // Two dots, that's an error ! 625 return false; 626 } 627 628 if ( ituOrIso && value > 39 ) 629 { 630 return false; 631 } 632 else 633 { 634 ituOrIso = false; 635 } 636 637 nbValues++; 638 dotSeen = true; 639 value = 0; 640 } 641 else if ( ( bytes[i] >= 0x30 ) && ( bytes[i] <= 0x39 ) ) 642 { 643 dotSeen = false; 644 645 value = ( ( value * 10 ) + bytes[i] ) - '0'; 646 } 647 else 648 { 649 // We don't have a number, this is an error 650 return false; 651 } 652 } 653 654 return !dotSeen; 655 } 656 657 /** 658 * Get the OID as a String 659 * 660 * @return A String representing the OID 661 */ 662 public String toString() 663 { 664 665 StringBuffer sb = new StringBuffer(); 666 667 if ( oidValues != null ) 668 { 669 sb.append( oidValues[0] ); 670 671 for ( int i = 1; i < oidValues.length; i++ ) 672 { 673 sb.append( '.' ).append( oidValues[i] ); 674 } 675 } 676 677 return sb.toString(); 678 } 679 680 681 public int hashCode() 682 { 683 return hash; 684 } 685 686 687 public boolean equals( Object oid ) 688 { 689 if ( this == oid ) 690 { 691 return true; 692 } 693 694 if ( oid == null ) 695 { 696 return false; 697 } 698 699 if ( oid.getClass() != this.getClass() ) 700 { 701 return false; 702 } 703 704 OID instance = (OID)oid; 705 706 if ( instance.hash != hash ) 707 { 708 return false; 709 } 710 else 711 { 712 return Arrays.equals( instance.oidValues, oidValues ); 713 } 714 } 715 716 }