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 22:52:31 +0200 (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 }