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.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.util.Arrays;
27  import java.util.LinkedList;
28  import java.util.Queue;
29  
30  import org.apache.directory.api.asn1.DecoderException;
31  import org.apache.directory.api.i18n.I18n;
32  
33  
34  /**
35   * An immutable representation of an object identifier that provides conversion 
36   * between their <code>String</code>, and encoded <code>byte[]</code> 
37   * representations.
38   * 
39   * <p> The encoding of OID values is performed according to 
40   * <a href='http://www.itu.int/rec/T-REC-X.690/en'>itu X.690</a> section 8.19.
41   * Specifically:</p>
42   * 
43   * <p><b>8.19.2</b> The contents octets shall be an (ordered) list of encodings
44   * of subidentifiers (see 8.19.3 and 8.19.4) concatenated together. Each 
45   * subidentifier is represented as a series of (one or more) octets. Bit 8 of 
46   * each octet indicates whether it is the last in the series: bit 8 of the last 
47   * octet is zero; bit 8 of each preceding octet is one. Bits 7 to 1 of the 
48   * octets in the series collectively encode the subidentifier. Conceptually, 
49   * these groups of bits are concatenated to form an unsigned binary number whose 
50   * most significant bit is bit 7 of the first octet and whose least significant 
51   * bit is bit 1 of the last octet. The subidentifier shall be encoded in the 
52   * fewest possible octets, that is, the leading octet of the subidentifier shall 
53   * not have the value 0x80. </p>
54   * 
55   * <p><b>8.19.3</b> The number of subidentifiers (N) shall be one less than the 
56   * number of object identifier components in the object identifier value being 
57   * encoded.</p>
58   * 
59   * <p><b>8.19.4</b> The numerical value of the first subidentifier is derived 
60   * from the values of the first two object identifier components in the object 
61   * identifier value being encoded, using the formula:
62   * <br /><code>(X*40) + Y</code><br /> 
63   * where X is the value of the first object identifier component and Y is the 
64   * value of the second object identifier component. <i>NOTE – This packing of 
65   * the first two object identifier components recognizes that only three values 
66   * are allocated from the root node, and at most 39 subsequent values from nodes 
67   * reached by X = 0 and X = 1.</i></p>
68   * 
69   * <p>For example, the OID "2.123456.7" would be turned into a list of 2 values:
70   * <code>[((2*80)+123456), 7]</code>.  The first of which, 
71   * <code>123536</code>, would be encoded as the bytes 
72   * <code>[0x87, 0xC5, 0x10]</code>, the second would be <code>[0x07]</code>,
73   * giving the final encoding <code>[0x87, 0xC5, 0x10, 0x07]</code>.</p>
74   * 
75   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
76   */
77  final public class Oid
78  {
79      private byte[] oidBytes;
80      private String oidString;
81  
82  
83      private Oid( String oidString, byte[] oidBytes )
84      {
85          this.oidString = oidString;
86          this.oidBytes = oidBytes;
87      }
88  
89  
90      @Override
91      public boolean equals( Object other )
92      {
93          return ( other instanceof Oid )
94              && oidString.equals( ( ( Oid ) other ).oidString );
95      }
96  
97  
98      /**
99       * 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 }