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.io.ByteArrayOutputStream;
024import java.io.IOException;
025import java.io.OutputStream;
026import java.util.Arrays;
027import java.util.LinkedList;
028import java.util.Queue;
029
030import org.apache.directory.api.asn1.DecoderException;
031import org.apache.directory.api.i18n.I18n;
032
033
034/**
035 * An immutable representation of an object identifier that provides conversion 
036 * between their <code>String</code>, and encoded <code>byte[]</code> 
037 * representations.
038 * 
039 * <p> The encoding of OID values is performed according to 
040 * <a href='http://www.itu.int/rec/T-REC-X.690/en'>itu X.690</a> section 8.19.
041 * Specifically:</p>
042 * 
043 * <p><b>8.19.2</b> The contents octets shall be an (ordered) list of encodings
044 * of subidentifiers (see 8.19.3 and 8.19.4) concatenated together. Each 
045 * subidentifier is represented as a series of (one or more) octets. Bit 8 of 
046 * each octet indicates whether it is the last in the series: bit 8 of the last 
047 * octet is zero; bit 8 of each preceding octet is one. Bits 7 to 1 of the 
048 * octets in the series collectively encode the subidentifier. Conceptually, 
049 * these groups of bits are concatenated to form an unsigned binary number whose 
050 * most significant bit is bit 7 of the first octet and whose least significant 
051 * bit is bit 1 of the last octet. The subidentifier shall be encoded in the 
052 * fewest possible octets, that is, the leading octet of the subidentifier shall 
053 * not have the value 0x80. </p>
054 * 
055 * <p><b>8.19.3</b> The number of subidentifiers (N) shall be one less than the 
056 * number of object identifier components in the object identifier value being 
057 * encoded.</p>
058 * 
059 * <p><b>8.19.4</b> The numerical value of the first subidentifier is derived 
060 * from the values of the first two object identifier components in the object 
061 * identifier value being encoded, using the formula:
062 * <br /><code>(X*40) + Y</code><br /> 
063 * where X is the value of the first object identifier component and Y is the 
064 * value of the second object identifier component. <i>NOTE – This packing of 
065 * the first two object identifier components recognizes that only three values 
066 * are allocated from the root node, and at most 39 subsequent values from nodes 
067 * reached by X = 0 and X = 1.</i></p>
068 * 
069 * <p>For example, the OID "2.123456.7" would be turned into a list of 2 values:
070 * <code>[((2*80)+123456), 7]</code>.  The first of which, 
071 * <code>123536</code>, would be encoded as the bytes 
072 * <code>[0x87, 0xC5, 0x10]</code>, the second would be <code>[0x07]</code>,
073 * giving the final encoding <code>[0x87, 0xC5, 0x10, 0x07]</code>.</p>
074 * 
075 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
076 */
077public final class Oid
078{
079    /** A byte[] representation of an OID */
080    private byte[] oidBytes;
081    
082    /** The OID as a String */
083    private String oidString;
084
085
086    /**
087     * Creates a new instance of Oid.
088     *
089     * @param oidString The OID as a String
090     * @param oidBytes The OID as a byte[]
091     */
092    private Oid( String oidString, byte[] oidBytes )
093    {
094        this.oidString = oidString;
095        this.oidBytes = new byte[oidBytes.length];
096        System.arraycopy( oidBytes, 0, this.oidBytes, 0, oidBytes.length );
097    }
098
099
100    /**
101     * {@inheritDoc}
102     */
103    @Override
104    public boolean equals( Object other )
105    {
106        return ( other instanceof Oid )
107            && oidString.equals( ( ( Oid ) other ).oidString );
108    }
109
110
111    /**
112     * Decodes an OID from a <code>byte[]</code>.
113     * 
114     * @param oidBytes The encoded<code>byte[]</code>
115     * @return A new Oid
116     * @throws DecoderException When the OID is not valid
117     */
118    public static Oid fromBytes( byte[] oidBytes ) throws DecoderException
119    {
120        if ( oidBytes == null || oidBytes.length < 1 )
121        {
122            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Arrays.toString( oidBytes ) ) );
123        }
124
125        StringBuilder builder = null;
126        long value = 0;
127
128        for ( int i = 0; i < oidBytes.length; i++ )
129        {
130            value |= oidBytes[i] & 0x7F;
131
132            if ( oidBytes[i] < 0 )
133            {
134                // leading 1, so value continues
135                value = value << 7;
136            }
137            else
138            {
139                // value completed
140                if ( builder == null )
141                {
142                    builder = new StringBuilder();
143
144                    // first value special processing
145                    if ( value >= 80 )
146                    {
147                        // starts with 2
148                        builder.append( 2 );
149                        value = value - 80;
150                    }
151                    else
152                    {
153                        // starts with 0 or 1
154                        long one = value / 40;
155                        long two = value % 40;
156
157                        if ( ( one < 0 ) || ( one > 2 ) || ( two < 0 ) || ( ( one < 2 ) && ( two > 39 ) ) )
158                        {
159                            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID,
160                                Arrays.toString( oidBytes ) ) );
161                        }
162
163                        if ( one < 2 )
164                        {
165                            builder.append( one );
166                            value = two;
167                        }
168                    }
169                }
170
171                // normal processing
172                builder.append( '.' ).append( value );
173                value = 0;
174            }
175        }
176
177        if ( builder == null )
178        {
179            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Arrays.toString( oidBytes ) ) );
180        }
181
182        return new Oid( builder.toString(), oidBytes );
183    }
184
185
186    /**
187     * Returns an OID object representing <code>oidString</code>.  
188     *  
189     * @param oidString The string representation of the OID
190     * @return A new Oid
191     * @throws DecoderException  When the OID is not valid
192     */
193    public static Oid fromString( String oidString ) throws DecoderException
194    {
195        if ( ( oidString == null ) || oidString.isEmpty() )
196        {
197            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, "" ) );
198        }
199
200        Queue<Long> segments = new LinkedList<Long>();
201
202        for ( String segment : oidString.split( "\\.", -1 ) )
203        {
204            try
205            {
206                segments.add( Long.parseLong( segment ) );
207            }
208            catch ( NumberFormatException nfe )
209            {
210                throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ), nfe );
211            }
212        }
213
214        // first segment special case
215        ByteBuffer buffer = new ByteBuffer();
216        Long segmentOne = segments.poll();
217
218        if ( ( segmentOne == null ) || ( segmentOne < 0 ) || ( segmentOne > 2 ) )
219        {
220            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) );
221        }
222
223        // second segment special case
224        Long segment = segments.poll();
225
226        if ( ( segment == null ) || ( segment < 0 ) || ( ( segmentOne < 2 ) && ( segment > 39 ) ) )
227        {
228            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) );
229        }
230
231        buffer.append( ( segmentOne * 40 ) + segment );
232
233        // the rest
234        while ( ( segment = segments.poll() ) != null )
235        {
236            buffer.append( segment );
237        }
238
239        return new Oid( oidString, buffer.toByteArray() );
240    }
241
242
243    /**
244     * Returns the length of the encoded <code>byte[]</code> representation.
245     * 
246     * @return The length of the byte[]
247     */
248    public int getEncodedLength()
249    {
250        return oidBytes.length;
251    }
252
253
254    /**
255     * {@inheritDoc}
256     */
257    @Override
258    public int hashCode()
259    {
260        return oidString.hashCode();
261    }
262
263
264    /**
265     * Returns true if <code>oidString</code> is a valid string representation
266     * of an OID.  This method simply calls {@link #fromString(String)} and 
267     * returns true if no exception was thrown.  As such, it should not be used 
268     * in an attempt to check if a string is a valid OID before calling 
269     * {@link #fromString(String)}.
270     * 
271     * @param oidString The string to test
272     * @return True, if <code>oidString</code> is valid
273     */
274    public static boolean isOid( String oidString )
275    {
276        try
277        {
278            Oid.fromString( oidString );
279
280            return true;
281        }
282        catch ( DecoderException e )
283        {
284            return false;
285        }
286    }
287
288
289    /**
290     * Returns the <code>byte[]</code> representation of the OID. The 
291     * <code>byte[]</code> that is returned is <i>copied</i> from the internal
292     * value so as to preserve the immutability of an OID object.  If the 
293     * output of a call to this method is intended to be written to a stream,
294     * the {@link #writeBytesTo(OutputStream)} should be used instead as it will
295     * avoid creating this copy. 
296     * 
297     * @return The encoded <code>byte[]</code> representation of the OID.
298     */
299    public byte[] toBytes()
300    {
301        return Arrays.copyOf( oidBytes, oidBytes.length );
302    }
303
304
305    /**
306     * Returns the string representation of the OID.
307     * 
308     * @return The string representation of the OID
309     */
310    @Override
311    public String toString()
312    {
313        return oidString;
314    }
315
316
317    /**
318     * Writes the bytes respresenting this OID to the provided buffer.  This 
319     * should be used in preference to the {@link #toBytes()} method in order
320     * to prevent the creation of copies of the actual <code>byte[]</code>.
321     * 
322     * @param buffer The buffer to write the bytes into
323     * @throws IOException If we can't inject the OID into a ByteBuffer 
324     */
325    public void writeBytesTo( java.nio.ByteBuffer buffer )
326    {
327        buffer.put( oidBytes );
328    }
329
330
331    /**
332     * Writes the bytes respresenting this OID to the provided stream.  This 
333     * should be used in preference to the {@link #toBytes()} method in order
334     * to prevent the creation of copies of the actual <code>byte[]</code>.
335     * 
336     * @param outputStream The stream to write the bytes to
337     * @throws IOException When we can't write the OID into a Stream
338     */
339    public void writeBytesTo( OutputStream outputStream ) throws IOException
340    {
341        outputStream.write( oidBytes );
342    }
343
344    /**
345     * 
346     * Internal helper class for converting a long value to a properly encoded byte[]
347     */
348    private static final class ByteBuffer
349    {
350        /** The Buffer the OID will be written in */
351        private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
352
353
354        /**
355         * Writes a Long into a ByteBuffer
356         * 
357         * @param value The long value to write
358         * @return A ByteBufffer containing the converted Long
359         */
360        public ByteBuffer append( long value )
361        {
362            write( value, false );
363            
364            return this;
365        }
366
367
368        /**
369         * Write a long into the buffe, and a flag indicating that there are more 
370         * to write
371         *
372         * @param value The value to write
373         * @param hasMore The flag indicati,ng there is more to write into the buffer
374         */
375        private void write( long value, boolean hasMore )
376        {
377            long remaining = value >> 7;
378        
379            if ( remaining > 0 )
380            {
381                write( remaining, true );
382            }
383            
384            buffer.write( hasMore
385                ? ( byte ) ( ( 0x7F & value ) | 0x80 )
386                : ( byte ) ( 0x7F & value ) );
387        }
388
389
390        /**
391         * Convert the Buffer to a byte[]
392         * 
393         * @return The byte[] containing the Long
394         */
395        public byte[] toByteArray()
396        {
397            return buffer.toByteArray();
398        }
399    }
400}