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}