View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.api.asn1.util;
21  
22  
23  import java.util.Arrays;
24  
25  import org.apache.directory.api.asn1.DecoderException;
26  import org.apache.directory.api.i18n.I18n;
27  
28  
29  /**
30   * This class implement an Oid (Object Identifier).<br/>
31   * <br/>
32   * An Oid is encoded as a list of bytes representing integers.<br/>
33   * <br/>
34   * An Oid has a numeric representation where number are separated with dots :<br/>
35   * SPNEGO Oid = 1.3.6.1.5.5.2<br/>
36   * <br/>
37   * Translating from a byte list to a dot separated list of number follows the rules :<br/>
38   * <ul>
39   * <li>the first number is in [0..2]</li>
40   * <li>the second number is in [0..39] if the first number is 0 or 1</li>
41   * <li>the first byte has a value equal to : number 1 * 40 + number two</li>
42   * <li>the upper bit of a byte is set if the next byte is a part of the number</li>
43   * </ul>
44   * <br/>
45   * For instance, the SPNEGO Oid (1.3.6.1.5.5.2) will be encoded :<br/>
46   * <pre>
47   * 1.3 -> 0x2B (1*40 + 3 = 43 = 0x2B)
48   * .6  -> 0x06
49   * .1  -> 0x01
50   * .5  -> 0x05
51   * .5  -> 0x05
52   * .2  -> 0x02
53   * </pre>
54   * <br/>
55   * The Kerberos V5 Oid (1.2.840.48018.1.2.2)  will be encoded :<br/>
56   * <pre>
57   * 1.2   -> 0x2A (1*40 + 2 = 42 = 0x2A)
58   * 840   -> 0x86 0x48 (840 = 6 * 128 + 72 = (0x06 | 0x80) 0x48 = 0x86 0x48
59   * 48018 -> 0x82 0xF7 0x12 (2 * 128 * 128 + 119 * 128 + 18 = (0x02 | 0x80) (0x77 | 0x80) 0x12
60   * .1    -> 0x01
61   * .2    -> 0x02
62   * .2    -> 0x02
63   * </pre>
64   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
65   */
66  public class Oid
67  {
68      /** The Oid as a array of int */
69      private long[] oidValues;
70  
71      /** The hashcode, computed only once */
72      private int hash;
73  
74  
75      /**
76       * Creates a new Oid object.
77       */
78      public Oid()
79      {
80          // We should not create this kind of object directly, it must
81          // be created through the factory.
82          hash = 0;
83      }
84  
85  
86      /**
87       * Create a new Oid object from a byte array
88       *
89       * @param oid the byte array containing the Oid
90       * @throws org.apache.directory.api.asn1.DecoderException if the byte array does not contain a
91       * valid Oid
92       */
93      public Oid( byte[] oid ) throws DecoderException
94      {
95          setOid( oid );
96          hash = computeHashCode();
97      }
98  
99  
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 }