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 */
077final public class Oid
078{
079    private byte[] oidBytes;
080    private String oidString;
081
082
083    private Oid( String oidString, byte[] oidBytes )
084    {
085        this.oidString = oidString;
086        this.oidBytes = oidBytes;
087    }
088
089
090    @Override
091    public boolean equals( Object other )
092    {
093        return ( other instanceof Oid )
094            && oidString.equals( ( ( Oid ) other ).oidString );
095    }
096
097
098    /**
099     * Decodes an OID from a <code>byte[]</code>.
100     * 
101     * @param oidBytes The encoded<code>byte[]</code>
102     * @return A new Oid
103     * @throws DecoderException
104     */
105    public static Oid fromBytes( byte[] oidBytes ) throws DecoderException
106    {
107        if ( oidBytes == null || oidBytes.length < 1 )
108        {
109            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Arrays.toString( oidBytes ) ) );
110        }
111
112        StringBuilder builder = null;
113        long value = 0;
114        for ( int i = 0; i < oidBytes.length; i++ )
115        {
116            value |= oidBytes[i] & 0x7F;
117            if ( oidBytes[i] < 0 )
118            {
119                // leading 1, so value continues
120                value = value << 7;
121            }
122            else
123            {
124                // value completed
125                if ( builder == null )
126                {
127                    builder = new StringBuilder();
128                    // first value special processing
129                    if ( value >= 80 )
130                    {
131                        // starts with 2
132                        builder.append( 2 );
133                        value = value - 80;
134                    }
135                    else
136                    {
137                        // starts with 0 or 1
138                        long one = value / 40;
139                        long two = value % 40;
140                        if ( one < 0 || one > 2 || two < 0 || ( one < 2 && two > 39 ) )
141                        {
142                            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID,
143                                Arrays.toString( oidBytes ) ) );
144                        }
145                        if ( one < 2 )
146                        {
147                            builder.append( one );
148                            value = two;
149                        }
150                    }
151                }
152
153                // normal processing
154                builder.append( '.' ).append( value );
155                value = 0;
156            }
157        }
158        if ( builder == null )
159        {
160            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Arrays.toString( oidBytes ) ) );
161        }
162
163        return new Oid( builder.toString(), oidBytes );
164    }
165
166
167    /**
168     * Returns an OID object representing <code>oidString</code>.  
169     *  
170     * @param oidString The string representation of the OID
171     * @return A new Oid
172     * @throws DecoderException 
173     */
174    public static Oid fromString( String oidString ) throws DecoderException
175    {
176        if ( oidString == null || oidString.isEmpty() )
177        {
178            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, "" ) );
179        }
180
181        Queue<Long> segments = new LinkedList<Long>();
182        for ( String segment : oidString.split( "\\.", -1 ) )
183        {
184            try
185            {
186                segments.add( Long.parseLong( segment ) );
187            }
188            catch ( NumberFormatException e )
189            {
190                throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) );
191            }
192        }
193
194        // first segment special case
195        ByteBuffer buffer = new ByteBuffer();
196        Long segmentOne = segments.poll();
197        if ( segmentOne == null || segmentOne < 0 || segmentOne > 2 )
198        {
199            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) );
200        }
201
202        // second segment special case
203        Long segment = segments.poll();
204        if ( segment == null || segment < 0 || ( segmentOne < 2 && segment > 39 ) )
205        {
206            throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) );
207        }
208
209        buffer.append( ( segmentOne * 40 ) + segment );
210
211        // the rest
212        while ( ( segment = segments.poll() ) != null )
213        {
214            buffer.append( segment );
215        }
216
217        return new Oid( oidString, buffer.toByteArray() );
218    }
219
220
221    /**
222     * Returns the length of the encoded <code>byte[]</code> representation.
223     * 
224     * @return The length of the byte[]
225     */
226    public int getEncodedLength()
227    {
228        return oidBytes.length;
229    }
230
231
232    @Override
233    public int hashCode()
234    {
235        return oidString.hashCode();
236    }
237
238
239    /**
240     * Returns true if <code>oidString</code> is a valid string representation
241     * of an OID.  This method simply calls {@link #fromString(String)} and 
242     * returns true if no exception was thrown.  As such, it should not be used 
243     * in an attempt to check if a string is a valid OID before calling 
244     * {@link #fromString(String)}.
245     * 
246     * @param oidString The string to test
247     * @return True, if <code>oidString</code> is valid
248     */
249    public static boolean isOid( String oidString )
250    {
251        try
252        {
253            return Oid.fromString( oidString ) != null;
254        }
255        catch ( DecoderException e )
256        {
257            return false;
258        }
259    }
260
261
262    /**
263     * Returns the <code>byte[]</code> representation of the OID. The 
264     * <code>byte[]</code> that is returned is <i>copied</i> from the internal
265     * value so as to preserve the immutability of an OID object.  If the 
266     * output of a call to this method is intended to be written to a stream,
267     * the {@link #writeBytesTo(OutputStream)} should be used instead as it will
268     * avoid creating this copy. 
269     * 
270     * @return The encoded <code>byte[]</code> representation of the OID.
271     */
272    public byte[] toBytes()
273    {
274        return Arrays.copyOf( oidBytes, oidBytes.length );
275    }
276
277
278    /**
279     * Returns the string representation of the OID.
280     * 
281     * @return The string representation of the OID
282     */
283    @Override
284    public String toString()
285    {
286        return oidString;
287    }
288
289
290    /**
291     * Writes the bytes respresenting this OID to the provided buffer.  This 
292     * should be used in preference to the {@link #toBytes()} method in order
293     * to prevent the creation of copies of the actual <code>byte[]</code>.
294     * 
295     * @param buffer The buffer to write the bytes to
296     * @throws IOException
297     */
298    public void writeBytesTo( java.nio.ByteBuffer buffer )
299    {
300        buffer.put( oidBytes );
301    }
302
303
304    /**
305     * Writes the bytes respresenting this OID to the provided stream.  This 
306     * should be used in preference to the {@link #toBytes()} method in order
307     * to prevent the creation of copies of the actual <code>byte[]</code>.
308     * 
309     * @param outputStream The stream to write the bytes to
310     * @throws IOException
311     */
312    public void writeBytesTo( OutputStream outputStream ) throws IOException
313    {
314        outputStream.write( oidBytes );
315    }
316
317    // Internal helper class for converting a long value to a properly encoded
318    // byte[]
319    final private static class ByteBuffer
320    {
321        private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
322
323
324        public ByteBuffer append( long value )
325        {
326            write( value, false );
327            return this;
328        }
329
330
331        private void write( long value, boolean hasMore )
332        {
333            long remaining = value >> 7;
334            if ( remaining > 0 )
335            {
336                write( remaining, true );
337            }
338            buffer.write( hasMore
339                ? ( byte ) ( ( 0x7F & value ) | 0x80 )
340                : ( byte ) ( 0x7F & value ) );
341        }
342
343
344        public byte[] toByteArray()
345        {
346            return buffer.toByteArray();
347        }
348    }
349}