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