blob: 7856323549625ab045f1a10e1fc6fc553915d49f [file] [log] [blame]
/*
* 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.shared.asn1.util;
import java.io.Serializable;
import java.util.Arrays;
import org.apache.directory.shared.asn1.DecoderException;
import org.apache.directory.shared.i18n.I18n;
/**
* This class implement an OID (Object Identifier).<br/>
* <br/>
* An OID is encoded as a list of bytes representing integers.<br/>
* <br/>
* An OID has a numeric representation where number are separated with dots :<br/>
* SPNEGO Oid = 1.3.6.1.5.5.2<br/>
* <br/>
* Translating from a byte list to a dot separated list of number follows the rules :<br/>
* <ul>
* <li>the first number is in [0..2]</li>
* <li>the second number is in [0..39] if the first number is 0 or 1</li>
* <li>the first byte has a value equal to : number 1 * 40 + number two</li>
* <li>the upper bit of a byte is set if the next byte is a part of the number</li>
* </ul>
* <br/>
* For instance, the SPNEGO Oid (1.3.6.1.5.5.2) will be encoded :<br/>
* <pre>
* 1.3 -> 0x2B (1*40 + 3 = 43 = 0x2B)
* .6 -> 0x06
* .1 -> 0x01
* .5 -> 0x05
* .5 -> 0x05
* .2 -> 0x02
* </pre>
* <br/>
* The Kerberos V5 Oid (1.2.840.48018.1.2.2) will be encoded :<br/>
* <pre>
* 1.2 -> 0x2A (1*40 + 2 = 42 = 0x2A)
* 840 -> 0x86 0x48 (840 = 6 * 128 + 72 = (0x06 | 0x80) 0x48 = 0x86 0x48
* 48018 -> 0x82 0xF7 0x12 (2 * 128 * 128 + 119 * 128 + 18 = (0x02 | 0x80) (0x77 | 0x80) 0x12
* .1 -> 0x01
* .2 -> 0x02
* .2 -> 0x02
* </pre>
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class OID implements Serializable
{
/** Internal Serial UUID version */
private static final long serialVersionUID = 1L;
/** The OID as a array of int */
private long[] oidValues;
/** The hashcode, computed only once */
private int hash;
/**
* Creates a new OID object.
*/
public OID()
{
// We should not create this kind of object directly, it must
// be created through the factory.
hash = 0;
}
/**
* Create a new OID object from a byte array
*
* @param oid the byte array containing the OID
* @throws org.apache.directory.shared.asn1.DecoderException if the byte array does not contain a
* valid OID
*/
public OID( byte[] oid ) throws DecoderException
{
setOID( oid );
hash = computeHashCode();
}
/**
* Create a new OID object from a String
*
* @param oid The String which is supposed to be an OID
* @throws DecoderException if the byte array does not contain a
* valid OID
*/
public OID( String oid ) throws DecoderException
{
setOID( oid );
hash = computeHashCode();
}
/**
* Set the OID. It will be translated from a byte array to an internal
* representation.
*
* @param oid The bytes containing the OID
* @throws org.apache.directory.shared.asn1.DecoderException if the byte array does not contains a valid OID
*/
public void setOID( byte[] oid ) throws DecoderException
{
if ( oid == null )
{
throw new DecoderException( I18n.err( I18n.ERR_00032_NULL_OID ) );
}
if ( oid.length < 1 )
{
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, Asn1StringUtils.dumpBytes( oid ) ) );
}
// First, we have to calculate the number of int to allocate
int nbValues = 1;
int pos = 0;
while ( pos < oid.length )
{
if ( oid[pos] >= 0 )
{
nbValues++;
}
pos++;
}
oidValues = new long[nbValues];
nbValues = 0;
pos = 0;
int accumulator = 0;
if ( ( oid[0] < 0 ) || ( oid[0] >= 80 ) )
{
oidValues[nbValues++] = 2;
while ( pos < oid.length )
{
if ( oid[pos] >= 0 )
{
oidValues[nbValues++] = ( ( accumulator << 7 ) + oid[pos] ) - 80;
accumulator = 0;
pos++;
break;
}
else
{
accumulator = ( accumulator << 7 ) + ( oid[pos] & 0x007F );
}
pos++;
}
}
else if ( oid[0] < 40 )
{
oidValues[nbValues++] = 0;
oidValues[nbValues++] = oid[pos++]; // itu-t
}
else
// oid[0] is < 80
{
oidValues[nbValues++] = 1;
oidValues[nbValues++] = oid[pos++] - 40; // iso
}
while ( pos < oid.length )
{
if ( oid[pos] >= 0 )
{
oidValues[nbValues++] = ( accumulator << 7 ) + oid[pos];
accumulator = 0;
}
else
{
accumulator = ( accumulator << 7 ) + ( oid[pos] & 0x007F );
}
pos++;
}
hash = computeHashCode();
}
/**
* Set the OID. It will be translated from a String to an internal
* representation.
*
* The syntax will be controled in respect with this rule :
* OID = ( [ '0' | '1' ] '.' [ 0 .. 39 ] | '2' '.' int) ( '.' int )*
*
* @param oid The String containing the OID
* @throws org.apache.directory.shared.asn1.DecoderException if the byte array does not contains a valid OID
*/
public void setOID( String oid ) throws DecoderException
{
if ( ( oid == null ) || ( oid.length() == 0 ) )
{
throw new DecoderException( I18n.err( I18n.ERR_00032_NULL_OID ) );
}
int nbValues = 1;
char[] chars = oid.toCharArray();
boolean dotSeen = false;
// Count the number of int to allocate.
for ( char c : chars )
{
if ( c == '.' )
{
if ( dotSeen )
{
// Two dots, that's an error !
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
nbValues++;
dotSeen = true;
}
else
{
dotSeen = false;
}
}
// We must have at least 2 ints
if ( nbValues < 2 )
{
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
oidValues = new long[nbValues];
int pos = 0;
int intPos = 0;
// This flag is used to forbid a second value above 39 if the
// first value is 0 or 1 (itu_t or iso arcs)
boolean ituOrIso = false;
// The first value
switch ( chars[pos] )
{
case '0': // itu-t
case '1': // iso
case '2': // joint-iso-itu-t
ituOrIso = true;
oidValues[intPos++] = chars[pos++] - '0';
break;
default: // error, this value is not allowed
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
// We must have a dot
if ( chars[pos++] != '.' )
{
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
dotSeen = true;
int value = 0;
for ( int i = pos; i < chars.length; i++ )
{
if ( chars[i] == '.' )
{
if ( dotSeen )
{
// Two dots, that's an error !
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
if ( ituOrIso && ( value > 39 ) )
{
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
else
{
ituOrIso = false;
}
nbValues++;
dotSeen = true;
oidValues[intPos++] = value;
value = 0;
}
else if ( ( chars[i] >= 0x30 ) && ( chars[i] <= 0x39 ) )
{
dotSeen = false;
value = ( ( value * 10 ) + chars[i] ) - '0';
}
else
{
// We don't have a number, this is an error
throw new DecoderException( I18n.err( I18n.ERR_00033_INVALID_OID, oid ) );
}
}
oidValues[intPos] = value;
hash = computeHashCode();
}
/**
* Get an array of long from the OID
*
* @return An array of long representing the OID
*/
public long[] getOIDValues()
{
long[] copy = new long[oidValues.length];
System.arraycopy( oidValues, 0, copy, 0, oidValues.length );
return copy;
}
/**
* Get the number of bytes necessary to store the OID
*
* @return An int representing the length of the OID
*/
public int getOIDLength()
{
long value = oidValues[0] * 40 + oidValues[1];
int nbBytes = 0;
if ( value < 128 )
{
nbBytes = 1;
}
else if ( value < 16384 )
{
nbBytes = 2;
}
else if ( value < 2097152 )
{
nbBytes = 3;
}
else if ( value < 268435456 )
{
nbBytes = 4;
}
else
{
nbBytes = 5;
}
for ( int i = 2; i < oidValues.length; i++ )
{
value = oidValues[i];
if ( value < 128 )
{
nbBytes += 1;
}
else if ( value < 16384 )
{
nbBytes += 2;
}
else if ( value < 2097152 )
{
nbBytes += 3;
}
else if ( value < 268435456 )
{
nbBytes += 4;
}
else
{
nbBytes += 5;
}
}
return nbBytes;
}
/**
* Get an array of bytes from the OID
*
* @return An array of int representing the OID
*/
public byte[] getOID()
{
long value = oidValues[0] * 40 + oidValues[1];
long firstValues = value;
byte[] bytes = new byte[getOIDLength()];
int pos = 0;
if ( oidValues[0] < 2 )
{
bytes[pos++] = ( byte ) ( oidValues[0] * 40 + oidValues[1] );
}
else
{
if ( firstValues < 128 )
{
bytes[pos++] = ( byte ) ( firstValues );
}
else if ( firstValues < 16384 )
{
bytes[pos++] = ( byte ) ( ( firstValues >> 7 ) | 0x0080 );
bytes[pos++] = ( byte ) ( firstValues & 0x007F );
}
else if ( value < 2097152 )
{
bytes[pos++] = ( byte ) ( ( firstValues >> 14 ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( firstValues >> 7 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( firstValues & 0x007F );
}
else if ( value < 268435456 )
{
bytes[pos++] = ( byte ) ( ( firstValues >> 21 ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( firstValues >> 14 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( firstValues >> 7 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( firstValues & 0x007F );
}
else
{
bytes[pos++] = ( byte ) ( ( firstValues >> 28 ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( firstValues >> 21 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( firstValues >> 14 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( firstValues >> 7 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( firstValues & 0x007F );
}
}
for ( int i = 2; i < oidValues.length; i++ )
{
value = oidValues[i];
if ( value < 128 )
{
bytes[pos++] = ( byte ) ( value );
}
else if ( value < 16384 )
{
bytes[pos++] = ( byte ) ( ( value >> 7 ) | 0x0080 );
bytes[pos++] = ( byte ) ( value & 0x007F );
}
else if ( value < 2097152 )
{
bytes[pos++] = ( byte ) ( ( value >> 14 ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( value >> 7 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( value & 0x007F );
}
else if ( value < 268435456 )
{
bytes[pos++] = ( byte ) ( ( value >> 21 ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( value >> 14 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( value >> 7 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( value & 0x007F );
}
else
{
bytes[pos++] = ( byte ) ( ( value >> 28 ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( value >> 21 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( value >> 14 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( ( ( value >> 7 ) & 0x007F ) | 0x0080 );
bytes[pos++] = ( byte ) ( value & 0x007F );
}
}
return bytes;
}
/**
* Compute the hash code for this object. No need to compute
* it live when calling the hashCode() method, as an OID
* never change.
*
* @return the OID's hash code
*/
private int computeHashCode()
{
int h = 37;
for ( long val : oidValues )
{
int low = ( int ) ( val & 0x0000FFFFL );
int high = ( int ) ( val >> 32 );
h = h * 17 + high;
h = h * 17 + low;
}
return h;
}
/**
* Check that an OID is valid
* @param oid The oid to be checked
* @return <code>true</code> if the OID is valid
*/
public static boolean isOID( String oid )
{
if ( ( oid == null ) || ( oid.length() == 0 ) )
{
return false;
}
int nbValues = 1;
byte[] bytes = oid.getBytes();
boolean dotSeen = false;
// Count the number of int to allocate.
for ( byte b : bytes )
{
if ( b == '.' )
{
if ( dotSeen )
{
// Two dots, that's an error !
return false;
}
nbValues++;
dotSeen = true;
}
else
{
dotSeen = false;
}
}
// We must have at least 2 ints
if ( nbValues < 2 )
{
return false;
}
int pos = 0;
// This flag is used to forbid a second value above 39 if the
// first value is 0 or 1 (itu_t or iso arcs)
boolean ituOrIso = false;
// The first value
switch ( bytes[pos++] )
{
case '0': // itu-t
case '1': // iso
case '2': // joint-iso-itu-t
ituOrIso = true;
break;
default: // error, this value is not allowed
return false;
}
// We must have a dot
if ( bytes[pos++] != '.' )
{
return false;
}
dotSeen = true;
long value = 0;
for ( int i = pos; i < bytes.length; i++ )
{
if ( bytes[i] == '.' )
{
if ( dotSeen )
{
// Two dots, that's an error !
return false;
}
if ( ituOrIso && ( value > 39 ) )
{
return false;
}
else
{
ituOrIso = false;
}
nbValues++;
dotSeen = true;
value = 0;
}
else if ( ( bytes[i] >= 0x30 ) && ( bytes[i] <= 0x39 ) )
{
dotSeen = false;
value = ( ( value * 10 ) + bytes[i] ) - '0';
}
else
{
// We don't have a number, this is an error
return false;
}
}
return !dotSeen;
}
/**
* Get the OID as a String
*
* @return A String representing the OID
*/
@Override
public String toString()
{
StringBuffer sb = new StringBuffer();
if ( oidValues != null )
{
sb.append( oidValues[0] );
for ( int i = 1; i < oidValues.length; i++ )
{
sb.append( '.' ).append( oidValues[i] );
}
}
return sb.toString();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode()
{
return hash;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals( Object oid )
{
if ( this == oid )
{
return true;
}
if ( oid == null )
{
return false;
}
if ( oid.getClass() != this.getClass() )
{
return false;
}
OID instance = ( OID ) oid;
if ( instance.hash != hash )
{
return false;
}
else
{
return Arrays.equals( instance.oidValues, oidValues );
}
}
}