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 }