| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.api.asn1.util; |
| |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.Queue; |
| |
| import org.apache.directory.api.asn1.DecoderException; |
| import org.apache.directory.api.i18n.I18n; |
| |
| |
| /** |
| * An immutable representation of an object identifier that provides conversion |
| * between their <code>String</code>, and encoded <code>byte[]</code> |
| * representations. |
| * |
| * <p> The encoding of OID values is performed according to |
| * <a href='http://www.itu.int/rec/T-REC-X.690/en'>itu X.690</a> section 8.19. |
| * Specifically:</p> |
| * |
| * <p><b>8.19.2</b> The contents octets shall be an (ordered) list of encodings |
| * of subidentifiers (see 8.19.3 and 8.19.4) concatenated together. Each |
| * subidentifier is represented as a series of (one or more) octets. Bit 8 of |
| * each octet indicates whether it is the last in the series: bit 8 of the last |
| * octet is zero; bit 8 of each preceding octet is one. Bits 7 to 1 of the |
| * octets in the series collectively encode the subidentifier. Conceptually, |
| * these groups of bits are concatenated to form an unsigned binary number whose |
| * most significant bit is bit 7 of the first octet and whose least significant |
| * bit is bit 1 of the last octet. The subidentifier shall be encoded in the |
| * fewest possible octets, that is, the leading octet of the subidentifier shall |
| * not have the value 0x80. </p> |
| * |
| * <p><b>8.19.3</b> The number of subidentifiers (N) shall be one less than the |
| * number of object identifier components in the object identifier value being |
| * encoded.</p> |
| * |
| * <p><b>8.19.4</b> The numerical value of the first subidentifier is derived |
| * from the values of the first two object identifier components in the object |
| * identifier value being encoded, using the formula: |
| * <br /><code>(X*40) + Y</code><br /> |
| * where X is the value of the first object identifier component and Y is the |
| * value of the second object identifier component. <i>NOTE – This packing of |
| * the first two object identifier components recognizes that only three values |
| * are allocated from the root node, and at most 39 subsequent values from nodes |
| * reached by X = 0 and X = 1.</i></p> |
| * |
| * <p>For example, the OID "2.123456.7" would be turned into a list of 2 values: |
| * <code>[((2*80)+123456), 7]</code>. The first of which, |
| * <code>123536</code>, would be encoded as the bytes |
| * <code>[0x87, 0xC5, 0x10]</code>, the second would be <code>[0x07]</code>, |
| * giving the final encoding <code>[0x87, 0xC5, 0x10, 0x07]</code>.</p> |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public final class Oid |
| { |
| /** A byte[] representation of an OID */ |
| private byte[] oidBytes; |
| |
| /** The OID as a String */ |
| private String oidString; |
| |
| |
| /** |
| * Creates a new instance of Oid. |
| * |
| * @param oidString The OID as a String |
| * @param oidBytes The OID as a byte[] |
| */ |
| private Oid( String oidString, byte[] oidBytes ) |
| { |
| this.oidString = oidString; |
| this.oidBytes = new byte[oidBytes.length]; |
| System.arraycopy( oidBytes, 0, this.oidBytes, 0, oidBytes.length ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean equals( Object other ) |
| { |
| return ( other instanceof Oid ) |
| && oidString.equals( ( ( Oid ) other ).oidString ); |
| } |
| |
| |
| /** |
| * Decodes an OID from a <code>byte[]</code>. |
| * |
| * @param oidBytes The encoded<code>byte[]</code> |
| * @return A new Oid |
| * @throws DecoderException When the OID is not valid |
| */ |
| public static Oid fromBytes( byte[] oidBytes ) throws DecoderException |
| { |
| if ( oidBytes == null || oidBytes.length < 1 ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Arrays.toString( oidBytes ) ) ); |
| } |
| |
| StringBuilder builder = null; |
| long value = 0; |
| |
| for ( int i = 0; i < oidBytes.length; i++ ) |
| { |
| value |= oidBytes[i] & 0x7F; |
| |
| if ( oidBytes[i] < 0 ) |
| { |
| // leading 1, so value continues |
| value = value << 7; |
| } |
| else |
| { |
| // value completed |
| if ( builder == null ) |
| { |
| builder = new StringBuilder(); |
| |
| // first value special processing |
| if ( value >= 80 ) |
| { |
| // starts with 2 |
| builder.append( 2 ); |
| value = value - 80; |
| } |
| else |
| { |
| // starts with 0 or 1 |
| long one = value / 40; |
| long two = value % 40; |
| |
| if ( ( one < 0 ) || ( one > 2 ) || ( two < 0 ) || ( ( one < 2 ) && ( two > 39 ) ) ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, |
| Arrays.toString( oidBytes ) ) ); |
| } |
| |
| if ( one < 2 ) |
| { |
| builder.append( one ); |
| value = two; |
| } |
| } |
| } |
| |
| // normal processing |
| builder.append( '.' ).append( value ); |
| value = 0; |
| } |
| } |
| |
| if ( builder == null ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Arrays.toString( oidBytes ) ) ); |
| } |
| |
| return new Oid( builder.toString(), oidBytes ); |
| } |
| |
| |
| /** |
| * Returns an OID object representing <code>oidString</code>. |
| * |
| * @param oidString The string representation of the OID |
| * @return A new Oid |
| * @throws DecoderException When the OID is not valid |
| */ |
| public static Oid fromString( String oidString ) throws DecoderException |
| { |
| if ( ( oidString == null ) || oidString.isEmpty() ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, "" ) ); |
| } |
| |
| Queue<Long> segments = new LinkedList<Long>(); |
| |
| for ( String segment : oidString.split( "\\.", -1 ) ) |
| { |
| try |
| { |
| segments.add( Long.parseLong( segment ) ); |
| } |
| catch ( NumberFormatException nfe ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ), nfe ); |
| } |
| } |
| |
| // first segment special case |
| ByteBuffer buffer = new ByteBuffer(); |
| Long segmentOne = segments.poll(); |
| |
| if ( ( segmentOne == null ) || ( segmentOne < 0 ) || ( segmentOne > 2 ) ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) ); |
| } |
| |
| // second segment special case |
| Long segment = segments.poll(); |
| |
| if ( ( segment == null ) || ( segment < 0 ) || ( ( segmentOne < 2 ) && ( segment > 39 ) ) ) |
| { |
| throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oidString ) ); |
| } |
| |
| buffer.append( ( segmentOne * 40 ) + segment ); |
| |
| // the rest |
| while ( ( segment = segments.poll() ) != null ) |
| { |
| buffer.append( segment ); |
| } |
| |
| return new Oid( oidString, buffer.toByteArray() ); |
| } |
| |
| |
| /** |
| * Returns the length of the encoded <code>byte[]</code> representation. |
| * |
| * @return The length of the byte[] |
| */ |
| public int getEncodedLength() |
| { |
| return oidBytes.length; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int hashCode() |
| { |
| return oidString.hashCode(); |
| } |
| |
| |
| /** |
| * Returns true if <code>oidString</code> is a valid string representation |
| * of an OID. This method simply calls {@link #fromString(String)} and |
| * returns true if no exception was thrown. As such, it should not be used |
| * in an attempt to check if a string is a valid OID before calling |
| * {@link #fromString(String)}. |
| * |
| * @param oidString The string to test |
| * @return True, if <code>oidString</code> is valid |
| */ |
| public static boolean isOid( String oidString ) |
| { |
| try |
| { |
| Oid.fromString( oidString ); |
| |
| return true; |
| } |
| catch ( DecoderException e ) |
| { |
| return false; |
| } |
| } |
| |
| |
| /** |
| * Returns the <code>byte[]</code> representation of the OID. The |
| * <code>byte[]</code> that is returned is <i>copied</i> from the internal |
| * value so as to preserve the immutability of an OID object. If the |
| * output of a call to this method is intended to be written to a stream, |
| * the {@link #writeBytesTo(OutputStream)} should be used instead as it will |
| * avoid creating this copy. |
| * |
| * @return The encoded <code>byte[]</code> representation of the OID. |
| */ |
| public byte[] toBytes() |
| { |
| return Arrays.copyOf( oidBytes, oidBytes.length ); |
| } |
| |
| |
| /** |
| * Returns the string representation of the OID. |
| * |
| * @return The string representation of the OID |
| */ |
| @Override |
| public String toString() |
| { |
| return oidString; |
| } |
| |
| |
| /** |
| * Writes the bytes respresenting this OID to the provided buffer. This |
| * should be used in preference to the {@link #toBytes()} method in order |
| * to prevent the creation of copies of the actual <code>byte[]</code>. |
| * |
| * @param buffer The buffer to write the bytes into |
| * @throws IOException If we can't inject the OID into a ByteBuffer |
| */ |
| public void writeBytesTo( java.nio.ByteBuffer buffer ) |
| { |
| buffer.put( oidBytes ); |
| } |
| |
| |
| /** |
| * Writes the bytes respresenting this OID to the provided stream. This |
| * should be used in preference to the {@link #toBytes()} method in order |
| * to prevent the creation of copies of the actual <code>byte[]</code>. |
| * |
| * @param outputStream The stream to write the bytes to |
| * @throws IOException When we can't write the OID into a Stream |
| */ |
| public void writeBytesTo( OutputStream outputStream ) throws IOException |
| { |
| outputStream.write( oidBytes ); |
| } |
| |
| /** |
| * |
| * Internal helper class for converting a long value to a properly encoded byte[] |
| */ |
| private static final class ByteBuffer |
| { |
| /** The Buffer the OID will be written in */ |
| private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); |
| |
| |
| /** |
| * Writes a Long into a ByteBuffer |
| * |
| * @param value The long value to write |
| * @return A ByteBufffer containing the converted Long |
| */ |
| public ByteBuffer append( long value ) |
| { |
| write( value, false ); |
| |
| return this; |
| } |
| |
| |
| /** |
| * Write a long into the buffe, and a flag indicating that there are more |
| * to write |
| * |
| * @param value The value to write |
| * @param hasMore The flag indicati,ng there is more to write into the buffer |
| */ |
| private void write( long value, boolean hasMore ) |
| { |
| long remaining = value >> 7; |
| |
| if ( remaining > 0 ) |
| { |
| write( remaining, true ); |
| } |
| |
| buffer.write( hasMore |
| ? ( byte ) ( ( 0x7F & value ) | 0x80 ) |
| : ( byte ) ( 0x7F & value ) ); |
| } |
| |
| |
| /** |
| * Convert the Buffer to a byte[] |
| * |
| * @return The byte[] containing the Long |
| */ |
| public byte[] toByteArray() |
| { |
| return buffer.toByteArray(); |
| } |
| } |
| } |