/*
* Copyright 2007-2013 UnboundID Corp.
* All Rights Reserved.
*/
/*
* Copyright (C) 2008-2013 UnboundID Corp.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*/
package com.hwlcn.ldap.asn1;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Arrays;
import com.hwlcn.ldap.util.ByteStringBuffer;
import com.hwlcn.core.annotation.NotExtensible;
import com.hwlcn.core.annotation.NotMutable;
import com.hwlcn.core.annotation.ThreadSafety;
import com.hwlcn.ldap.util.ThreadSafetyLevel;
import static com.hwlcn.ldap.asn1.ASN1Constants.*;
import static com.hwlcn.ldap.asn1.ASN1Messages.*;
import static com.hwlcn.ldap.util.Debug.*;
import static com.hwlcn.ldap.util.StaticUtils.*;
/**
* This class defines a generic ASN.1 BER element, which has a type and value.
* It provides a framework for encoding and decoding BER elements, both as
* generic elements and more specific subtypes.
*/
@NotExtensible()
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public class ASN1Element
implements Serializable
{
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = -1871166128693521335L;
// The BER type for this element.
private final byte type;
// The encoded value for this element.
private final byte[] value;
// The cached hashCode for this element.
private int hashCode = -1;
// The number of bytes contained in the value.
private final int valueLength;
// The offset within the value array at which the value begins.
private final int valueOffset;
/**
* Creates a new ASN.1 BER element with the specified type and no value.
*
* @param type The BER type for this element.
*/
public ASN1Element(final byte type)
{
this.type = type;
value = NO_VALUE;
valueOffset = 0;
valueLength = 0;
}
/**
* Creates a new ASN1 BER element with the specified type and value.
*
* @param type The BER type for this element.
* @param value The encoded value for this element.
*/
public ASN1Element(final byte type, final byte[] value)
{
this.type = type;
if (value == null)
{
this.value = NO_VALUE;
}
else
{
this.value = value;
}
valueOffset = 0;
valueLength = this.value.length;
}
/**
* Creates a new ASN1 BER element with the specified type and value.
*
* @param type The BER type for this element.
* @param value The array containing the encoded value for this element.
* It must not be {@code null}.
* @param offset The offset within the array at which the value begins.
* @param length The number of bytes contained in the value.
*/
public ASN1Element(final byte type, final byte[] value, final int offset,
final int length)
{
this.type = type;
this.value = value;
valueOffset = offset;
valueLength = length;
}
/**
* Retrieves the BER type for this element.
*
* @return The BER type for this element.
*/
public final byte getType()
{
return type;
}
/**
* Retrieves the array containing the value. The returned array may be
* larger than the actual value, so it must be used in conjunction with the
* values returned by the {@link #getValueOffset} and {@link #getValueLength}
* methods.
*
* @return The array containing the value.
*/
byte[] getValueArray()
{
return value;
}
/**
* Retrieves the position in the value array at which the value actually
* begins.
*
* @return The position in the value array at which the value actually
* begins.
*/
int getValueOffset()
{
return valueOffset;
}
/**
* Retrieves the number of bytes contained in the value.
*
* @return The number of bytes contained in the value.
*/
public int getValueLength()
{
return valueLength;
}
/**
* Retrieves the encoded value for this element.
*
* @return The encoded value for this element.
*/
public byte[] getValue()
{
if ((valueOffset == 0) && (valueLength == value.length))
{
return value;
}
else
{
final byte[] returnValue = new byte[valueLength];
System.arraycopy(value, valueOffset, returnValue, 0, valueLength);
return returnValue;
}
}
/**
* Encodes this ASN.1 element to a byte array.
*
* @return A byte array containing the encoded representation of this ASN.1
* element.
*/
public final byte[] encode()
{
final byte[] valueArray = getValueArray();
final int length = getValueLength();
final int offset = getValueOffset();
if (length == 0)
{
return new byte[] { type, 0x00 };
}
final byte[] lengthBytes = encodeLength(length);
final byte[] elementBytes = new byte[1 + lengthBytes.length + length];
elementBytes[0] = type;
System.arraycopy(lengthBytes, 0, elementBytes, 1, lengthBytes.length);
System.arraycopy(valueArray, offset, elementBytes, 1+lengthBytes.length,
length);
return elementBytes;
}
/**
* Encodes the provided length to the given buffer.
*
* @param length The length to be encoded.
* @param buffer The buffer to which the length should be appended.
*/
static void encodeLengthTo(final int length, final ByteStringBuffer buffer)
{
if ((length & 0x7F) == length)
{
buffer.append((byte) length);
}
else if ((length & 0xFF) == length)
{
buffer.append((byte) 0x81);
buffer.append((byte) (length & 0xFF));
}
else if ((length & 0xFFFF) == length)
{
buffer.append((byte) 0x82);
buffer.append((byte) ((length >> 8) & 0xFF));
buffer.append((byte) (length & 0xFF));
}
else if ((length & 0xFFFFFF) == length)
{
buffer.append((byte) 0x83);
buffer.append((byte) ((length >> 16) & 0xFF));
buffer.append((byte) ((length >> 8) & 0xFF));
buffer.append((byte) (length & 0xFF));
}
else
{
buffer.append((byte) 0x84);
buffer.append((byte) ((length >> 24) & 0xFF));
buffer.append((byte) ((length >> 16) & 0xFF));
buffer.append((byte) ((length >> 8) & 0xFF));
buffer.append((byte) (length & 0xFF));
}
}
/**
* Appends an encoded representation of this ASN.1 element to the provided
* buffer.
*
* @param buffer The buffer to which the encoded representation should be
* appended.
*/
public void encodeTo(final ByteStringBuffer buffer)
{
final byte[] valueArray = getValueArray();
final int length = getValueLength();
final int offset = getValueOffset();
buffer.append(type);
if (length == 0)
{
buffer.append((byte) 0x00);
}
else
{
encodeLengthTo(length, buffer);
buffer.append(valueArray, offset, length);
}
}
/**
* Encodes the provided length to a byte array.
*
* @param length The length to be encoded.
*
* @return A byte array containing the encoded length.
*/
public static byte[] encodeLength(final int length)
{
switch (length)
{
case 0: return LENGTH_0;
case 1: return LENGTH_1;
case 2: return LENGTH_2;
case 3: return LENGTH_3;
case 4: return LENGTH_4;
case 5: return LENGTH_5;
case 6: return LENGTH_6;
case 7: return LENGTH_7;
case 8: return LENGTH_8;
case 9: return LENGTH_9;
case 10: return LENGTH_10;
case 11: return LENGTH_11;
case 12: return LENGTH_12;
case 13: return LENGTH_13;
case 14: return LENGTH_14;
case 15: return LENGTH_15;
case 16: return LENGTH_16;
case 17: return LENGTH_17;
case 18: return LENGTH_18;
case 19: return LENGTH_19;
case 20: return LENGTH_20;
case 21: return LENGTH_21;
case 22: return LENGTH_22;
case 23: return LENGTH_23;
case 24: return LENGTH_24;
case 25: return LENGTH_25;
case 26: return LENGTH_26;
case 27: return LENGTH_27;
case 28: return LENGTH_28;
case 29: return LENGTH_29;
case 30: return LENGTH_30;
case 31: return LENGTH_31;
case 32: return LENGTH_32;
case 33: return LENGTH_33;
case 34: return LENGTH_34;
case 35: return LENGTH_35;
case 36: return LENGTH_36;
case 37: return LENGTH_37;
case 38: return LENGTH_38;
case 39: return LENGTH_39;
case 40: return LENGTH_40;
case 41: return LENGTH_41;
case 42: return LENGTH_42;
case 43: return LENGTH_43;
case 44: return LENGTH_44;
case 45: return LENGTH_45;
case 46: return LENGTH_46;
case 47: return LENGTH_47;
case 48: return LENGTH_48;
case 49: return LENGTH_49;
case 50: return LENGTH_50;
case 51: return LENGTH_51;
case 52: return LENGTH_52;
case 53: return LENGTH_53;
case 54: return LENGTH_54;
case 55: return LENGTH_55;
case 56: return LENGTH_56;
case 57: return LENGTH_57;
case 58: return LENGTH_58;
case 59: return LENGTH_59;
case 60: return LENGTH_60;
case 61: return LENGTH_61;
case 62: return LENGTH_62;
case 63: return LENGTH_63;
case 64: return LENGTH_64;
case 65: return LENGTH_65;
case 66: return LENGTH_66;
case 67: return LENGTH_67;
case 68: return LENGTH_68;
case 69: return LENGTH_69;
case 70: return LENGTH_70;
case 71: return LENGTH_71;
case 72: return LENGTH_72;
case 73: return LENGTH_73;
case 74: return LENGTH_74;
case 75: return LENGTH_75;
case 76: return LENGTH_76;
case 77: return LENGTH_77;
case 78: return LENGTH_78;
case 79: return LENGTH_79;
case 80: return LENGTH_80;
case 81: return LENGTH_81;
case 82: return LENGTH_82;
case 83: return LENGTH_83;
case 84: return LENGTH_84;
case 85: return LENGTH_85;
case 86: return LENGTH_86;
case 87: return LENGTH_87;
case 88: return LENGTH_88;
case 89: return LENGTH_89;
case 90: return LENGTH_90;
case 91: return LENGTH_91;
case 92: return LENGTH_92;
case 93: return LENGTH_93;
case 94: return LENGTH_94;
case 95: return LENGTH_95;
case 96: return LENGTH_96;
case 97: return LENGTH_97;
case 98: return LENGTH_98;
case 99: return LENGTH_99;
case 100: return LENGTH_100;
case 101: return LENGTH_101;
case 102: return LENGTH_102;
case 103: return LENGTH_103;
case 104: return LENGTH_104;
case 105: return LENGTH_105;
case 106: return LENGTH_106;
case 107: return LENGTH_107;
case 108: return LENGTH_108;
case 109: return LENGTH_109;
case 110: return LENGTH_110;
case 111: return LENGTH_111;
case 112: return LENGTH_112;
case 113: return LENGTH_113;
case 114: return LENGTH_114;
case 115: return LENGTH_115;
case 116: return LENGTH_116;
case 117: return LENGTH_117;
case 118: return LENGTH_118;
case 119: return LENGTH_119;
case 120: return LENGTH_120;
case 121: return LENGTH_121;
case 122: return LENGTH_122;
case 123: return LENGTH_123;
case 124: return LENGTH_124;
case 125: return LENGTH_125;
case 126: return LENGTH_126;
case 127: return LENGTH_127;
}
if ((length & 0x000000FF) == length)
{
return new byte[]
{
(byte) 0x81,
(byte) (length & 0xFF)
};
}
else if ((length & 0x0000FFFF) == length)
{
return new byte[]
{
(byte) 0x82,
(byte) ((length >> 8) & 0xFF),
(byte) (length & 0xFF)
};
}
else if ((length & 0x00FFFFFF) == length)
{
return new byte[]
{
(byte) 0x83,
(byte) ((length >> 16) & 0xFF),
(byte) ((length >> 8) & 0xFF),
(byte) (length & 0xFF)
};
}
else
{
return new byte[]
{
(byte) 0x84,
(byte) ((length >> 24) & 0xFF),
(byte) ((length >> 16) & 0xFF),
(byte) ((length >> 8) & 0xFF),
(byte) (length & 0xFF)
};
}
}
/**
* Decodes the content in the provided byte array as an ASN.1 element.
*
* @param elementBytes The byte array containing the data to decode.
*
* @return The decoded ASN.1 BER element.
*
* @throws ASN1Exception If the provided byte array does not represent a
* valid ASN.1 element.
*/
public static ASN1Element decode(final byte[] elementBytes)
throws ASN1Exception
{
try
{
int valueStartPos = 2;
int length = (elementBytes[1] & 0x7F);
if (length != elementBytes[1])
{
final int numLengthBytes = length;
length = 0;
for (int i=0; i < numLengthBytes; i++)
{
length <<= 8;
length |= (elementBytes[valueStartPos++] & 0xFF);
}
}
if ((elementBytes.length - valueStartPos) != length)
{
throw new ASN1Exception(ERR_ELEMENT_LENGTH_MISMATCH.get(length,
(elementBytes.length - valueStartPos)));
}
final byte[] value = new byte[length];
System.arraycopy(elementBytes, valueStartPos, value, 0, length);
return new ASN1Element(elementBytes[0], value);
}
catch (final ASN1Exception ae)
{
debugException(ae);
throw ae;
}
catch (final Exception e)
{
debugException(e);
throw new ASN1Exception(ERR_ELEMENT_DECODE_EXCEPTION.get(e), e);
}
}
/**
* Decodes this ASN.1 element as a Boolean element.
*
* @return The decoded Boolean element.
*
* @throws ASN1Exception If this element cannot be decoded as a Boolean
* element.
*/
public final ASN1Boolean decodeAsBoolean()
throws ASN1Exception
{
return ASN1Boolean.decodeAsBoolean(this);
}
/**
* Decodes this ASN.1 element as an enumerated element.
*
* @return The decoded enumerated element.
*
* @throws ASN1Exception If this element cannot be decoded as an enumerated
* element.
*/
public final ASN1Enumerated decodeAsEnumerated()
throws ASN1Exception
{
return ASN1Enumerated.decodeAsEnumerated(this);
}
/**
* Decodes this ASN.1 element as an integer element.
*
* @return The decoded integer element.
*
* @throws ASN1Exception If this element cannot be decoded as an integer
* element.
*/
public final ASN1Integer decodeAsInteger()
throws ASN1Exception
{
return ASN1Integer.decodeAsInteger(this);
}
/**
* Decodes this ASN.1 element as a long element.
*
* @return The decoded long element.
*
* @throws ASN1Exception If this element cannot be decoded as a long
* element.
*/
public final ASN1Long decodeAsLong()
throws ASN1Exception
{
return ASN1Long.decodeAsLong(this);
}
/**
* Decodes this ASN.1 element as a null element.
*
* @return The decoded null element.
*
* @throws ASN1Exception If this element cannot be decoded as a null
* element.
*/
public final ASN1Null decodeAsNull()
throws ASN1Exception
{
return ASN1Null.decodeAsNull(this);
}
/**
* Decodes this ASN.1 element as an octet string element.
*
* @return The decoded octet string element.
*/
public final ASN1OctetString decodeAsOctetString()
{
return ASN1OctetString.decodeAsOctetString(this);
}
/**
* Decodes this ASN.1 element as a sequence element.
*
* @return The decoded sequence element.
*
* @throws ASN1Exception If this element cannot be decoded as a sequence
* element.
*/
public final ASN1Sequence decodeAsSequence()
throws ASN1Exception
{
return ASN1Sequence.decodeAsSequence(this);
}
/**
* Decodes this ASN.1 element as a set element.
*
* @return The decoded set element.
*
* @throws ASN1Exception If this element cannot be decoded as a set
* element.
*/
public final ASN1Set decodeAsSet()
throws ASN1Exception
{
return ASN1Set.decodeAsSet(this);
}
/**
* Reads an ASN.1 element from the provided input stream.
*
* @param inputStream The input stream from which to read the element.
*
* @return The element read from the input stream, or {@code null} if the end
* of the input stream is reached without reading any data.
*
* @throws java.io.IOException If a problem occurs while attempting to read from the
* input stream.
*
* @throws ASN1Exception If a problem occurs while attempting to decode the
* element.
*/
public static ASN1Element readFrom(final InputStream inputStream)
throws IOException, ASN1Exception
{
return readFrom(inputStream, -1);
}
/**
* Reads an ASN.1 element from the provided input stream.
*
* @param inputStream The input stream from which to read the element.
* @param maxSize The maximum value size in bytes that will be allowed.
* A value less than or equal to zero indicates that no
* maximum size should be enforced. An attempt to read
* an element with a value larger than this will cause an
* {@code ASN1Exception} to be thrown.
*
* @return The element read from the input stream, or {@code null} if the end
* of the input stream is reached without reading any data.
*
* @throws java.io.IOException If a problem occurs while attempting to read from the
* input stream.
*
* @throws ASN1Exception If a problem occurs while attempting to decode the
* element.
*/
public static ASN1Element readFrom(final InputStream inputStream,
final int maxSize)
throws IOException, ASN1Exception
{
final int typeInt = inputStream.read();
if (typeInt < 0)
{
return null;
}
final byte type = (byte) typeInt;
int length = inputStream.read();
if (length < 0)
{
throw new ASN1Exception(ERR_READ_END_BEFORE_FIRST_LENGTH.get());
}
else if (length > 127)
{
final int numLengthBytes = length & 0x7F;
length = 0;
if ((numLengthBytes < 1) || (numLengthBytes > 4))
{
throw new ASN1Exception(ERR_READ_LENGTH_TOO_LONG.get(numLengthBytes));
}
for (int i=0; i < numLengthBytes; i++)
{
final int lengthInt = inputStream.read();
if (lengthInt < 0)
{
throw new ASN1Exception(ERR_READ_END_BEFORE_LENGTH_END.get());
}
length <<= 8;
length |= (lengthInt & 0xFF);
}
}
if ((length < 0) || ((maxSize > 0) && (length > maxSize)))
{
throw new ASN1Exception(ERR_READ_LENGTH_EXCEEDS_MAX.get(length, maxSize));
}
int totalBytesRead = 0;
int bytesRemaining = length;
final byte[] value = new byte[length];
while (totalBytesRead < length)
{
final int bytesRead =
inputStream.read(value, totalBytesRead, bytesRemaining);
if (bytesRead < 0)
{
throw new ASN1Exception(ERR_READ_END_BEFORE_VALUE_END.get());
}
totalBytesRead += bytesRead;
bytesRemaining -= bytesRead;
}
final ASN1Element e = new ASN1Element(type, value);
debugASN1Read(e);
return e;
}
/**
* Writes an encoded representation of this ASN.1 element to the provided
* output stream.
*
* @param outputStream The output stream to which the element should be
* written.
*
* @return The total number of bytes written to the output stream.
*
* @throws java.io.IOException If a problem occurs while attempting to write to the
* provided output stream.
*
* @see com.hwlcn.ldap.asn1.ASN1Writer#writeElement(com.hwlcn.ldap.asn1.ASN1Element, java.io.OutputStream)
*/
public final int writeTo(final OutputStream outputStream)
throws IOException
{
debugASN1Write(this);
final ByteStringBuffer buffer = new ByteStringBuffer();
encodeTo(buffer);
buffer.write(outputStream);
return buffer.length();
}
/**
* Retrieves a hash code for this ASN.1 BER element.
*
* @return A hash code for this ASN.1 BER element.
*/
@Override()
public final int hashCode()
{
if (hashCode == -1)
{
int hash = 0;
for (final byte b : getValue())
{
hash = hash * 31 + b;
}
hashCode = hash;
}
return hashCode;
}
/**
* Indicates whether the provided object is equal to this ASN.1 BER element.
* The object will only be considered equal to this ASN.1 element if it is a
* non-null ASN.1 element with the same type and value as this element.
*
* @param o The object for which to make the determination.
*
* @return {@code true} if the provided object is considered equal to this
* ASN.1 element, or {@code false} if not.
*/
@Override()
public final boolean equals(final Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
try
{
final ASN1Element e = (ASN1Element) o;
return ((type == e.getType()) && Arrays.equals(getValue(), e.getValue()));
}
catch (final Exception e)
{
debugException(e);
return false;
}
}
/**
* Indicates whether the provided ASN.1 element is equal to this element,
* ignoring any potential difference in the BER type.
*
* @param element The ASN.1 BER element for which to make the determination.
*
* @return {@code true} if the provided ASN.1 element is considered equal to
* this element (ignoring type differences), or {@code false} if not.
*/
public final boolean equalsIgnoreType(final ASN1Element element)
{
if (element == null)
{
return false;
}
if (element == this)
{
return true;
}
return Arrays.equals(getValue(), element.getValue());
}
/**
* Retrieves a string representation of the value for ASN.1 element.
*
* @return A string representation of the value for this ASN.1 element.
*/
@Override()
public final String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of the value for this ASN.1 element to the
* provided buffer.
*
* @param buffer The buffer to which to append the information.
*/
public void toString(final StringBuilder buffer)
{
final byte[] v = getValue();
buffer.append("ASN1Element(type=");
toHex(type, buffer);
buffer.append(", valueLength=");
buffer.append(v.length);
buffer.append(", valueBytes='");
toHex(v, buffer);
buffer.append("')");
}
}