/* ====================================================================
* Limited Evaluation License:
*
* This software is open source, but licensed. The license with this package
* is an evaluation license, which may not be used for productive systems. If
* you want a full license, please contact us.
*
* This software is open source, but licensed. The license with this package
* is an evaluation license, which may not be used for productive systems. If
* you want a full license, please contact us.
*
* The exclusive owner of this work is the OpenRate project.
* This work, including all associated documents and components
* is Copyright of the OpenRate project 2006-2015.
*
* The following restrictions apply unless they are expressly relaxed in a
* contractual agreement between the license holder or one of its officially
* assigned agents and you or your organisation:
*
* 1) This work may not be disclosed, either in full or in part, in any form
* electronic or physical, to any third party. This includes both in the
* form of source code and compiled modules.
* 2) This work contains trade secrets in the form of architecture, algorithms
* methods and technologies. These trade secrets may not be disclosed to
* third parties in any form, either directly or in summary or paraphrased
* form, nor may these trade secrets be used to construct products of a
* similar or competing nature either by you or third parties.
* 3) This work may not be included in full or in part in any application.
* 4) You may not remove or alter any proprietary legends or notices contained
* in or on this work.
* 5) This software may not be reverse-engineered or otherwise decompiled, if
* you received this work in a compiled form.
* 6) This work is licensed, not sold. Possession of this software does not
* imply or grant any right to you.
* 7) You agree to disclose any changes to this work to the copyright holder
* and that the copyright holder may include any such changes at its own
* discretion into the work
* 8) You agree not to derive other works from the trade secrets in this work,
* and that any such derivation may make you liable to pay damages to the
* copyright holder
* 9) You agree to use this software exclusively for evaluation purposes, and
* that you shall not use this software to derive commercial profit or
* support your business or personal activities.
*
* This software is provided "as is" and any expressed or impled warranties,
* including, but not limited to, the impled warranties of merchantability
* and fitness for a particular purpose are disclaimed. In no event shall
* The OpenRate Project or its officially assigned agents be liable to any
* direct, indirect, incidental, special, exemplary, or consequential damages
* (including but not limited to, procurement of substitute goods or services;
* Loss of use, data, or profits; or any business interruption) however caused
* and on theory of liability, whether in contract, strict liability, or tort
* (including negligence or otherwise) arising in any way out of the use of
* this software, even if advised of the possibility of such damage.
* This software contains portions by The Apache Software Foundation, Robert
* Half International.
* ====================================================================
*/
package OpenRate.parser;
import OpenRate.exception.ASN1Exception;
import java.util.Formatter;
/**
* ASN.1 file parser
*
* @author Magnus
*/
public class ASN1Parser implements IBinaryParser
{
/* Own types - used to make sure the right parsing is used */
/**
* 0: Constructed (Sequence/Choice...)
*/
public static final int CONSTRUCTED = 0x00;
/**
* 170: BCD String (Octet String)
*/
public static final int BCDString = 0xAA;
/**
* 171: BCD String (Octet String), little endian format
*/
public static final int BCDStringLE = 0xAB;
/* Types - not used in v.1, maybe when improved further */
/**
* 1: Boolean
*/
public static final int BOOLEAN = 0x01;
/**
* 2: Integer
*/
public static final int INTEGER = 0x02;
/**
* 2: Bit string
*/
public static final int BITSTRING = 0x03;
/**
* 4: Byte string
*/
public static final int OCTETSTRING = 0x04;
/**
* 5: NULL
*/
public static final int NULLTAG = 0x05;
/**
* 6: Object Identifier
*/
public static final int OID = 0x06;
/**
* 7: Object Descriptor
*/
public static final int OBJDESCRIPTOR = 0x07;
/**
* 8: External
*/
public static final int EXTERNAL = 0x08;
/**
* 9: Real
*/
public static final int REAL = 0x09;
/**
* 10: Enumerated
*/
public static final int ENUMERATED = 0x0A;
/**
* 11: Embedded Presentation Data Value
*/
public static final int EMBEDDED_PDV = 0x0B;
/**
* 12: UTF8 string
*/
public static final int UTF8STRING = 0x0C;
/**
* 16: Sequence/sequence of
*/
public static final int SEQUENCE = 0x10;
/**
* 17: Set/set of
*/
public static final int SET = 0x11;
/**
* 18: Numeric string
*/
public static final int NUMERICSTRING = 0x12;
/**
* 19: Printable string (ASCII subset)
*/
public static final int PRINTABLESTRING = 0x13;
/**
* 20: T61/Teletex string
*/
public static final int T61STRING = 0x14;
/**
* 21: Videotex string
*/
public static final int VIDEOTEXSTRING = 0x15;
/**
* 22: IA5/ASCII string
*/
public static final int IA5STRING = 0x16;
/**
* 23: UTC time
*/
public static final int UTCTIME = 0x17;
/**
* 24: Generalized time
*/
public static final int GENERALIZEDTIME = 0x18;
/**
* 25: Graphic string
*/
public static final int GRAPHICSTRING = 0x19;
/**
* 26: Visible string (ASCII subset)
*/
public static final int VISIBLESTRING = 0x1A;
/**
* 27: General string
*/
public static final int GENERALSTRING = 0x1B;
/**
* 28: Universal string
*/
public static final int UNIVERSALSTRING = 0x1C;
/**
* 30: Basic Multilingual Plane/Unicode string
*/
public static final int BMPSTRING = 0x1E;
// The definition file we are using
private IASN1Def ASN1Def;
// The byte stream reader for this decoder
private ASN1Parser.ByteArrayReader reader = new ASN1Parser.ByteArrayReader();
/**
* Find out whether the reader is ready for another call
*
* @return true if ready, otherwise false
*/
public boolean ready()
{
return reader.ready();
}
/**
* The internal reader object
*/
private class ByteArrayReader {
private byte[] data;
private int byteArrayLength = 0;
private int bytePosition = 0;
/**
* Constructor to set the byte array reader to the given value.
*
* @param data The binary data to set.
*/
public ByteArrayReader(byte[] data)
{
// Set the internal data buffer with the given data
setData(data);
}
/**
* Default constructor.
*/
public ByteArrayReader()
{
// Nothing
}
/**
* Set the internal data buffer and reset the pointer.
*
* @param data The data to set
*/
private void setData(byte[]data) {
this.byteArrayLength = data.length;
this.bytePosition = 0;
this.data = data;
}
/**
* Read a single byte out of the reader, moving the byte pointer on to
* be ready for the next byte.
*
* @return
*/
public byte readByte() {
byte output;
output=this.data[bytePosition];
this.bytePosition += 1;
return output;
}
/**
* Returns true if there are more bytes to read, otherwise false.
*
* @return True if there are more bytes left in the reader
*/
public boolean ready() {
return (this.byteArrayLength > this.bytePosition);
}
/**
* Chop out an array section from an existing array.
*
* @param header The original byte array
* @param length The number of bytes to chop
* @return The new chopped byte array
*/
public byte[] chopByteArray(byte[] header, int length) {
byte[] newhead = new byte[length];
System.arraycopy(header, 0, newhead, 0, length);
return newhead;
}
}
/**
* Set the data to be parsed.
*
* @param data The data to be parsed
*/
@Override
public void setDataToParse(byte[] data)
{
reader.setData(data);
}
/**
* Create a new ASN.1 parser using the supplied specification
*
* @param ASN1Specification The specification to use
*/
public ASN1Parser(IASN1Def ASN1Specification)
{
ASN1Def = ASN1Specification;
}
/**
* formats the value of the tag as an integer and return as a BCD string value.
* No value checking is done. Padding is removed.
*
* @param value the input BCD encoded array
* @return The decoded string
*
*/
public String parseBCDString(byte[] value) {
StringBuilder buf = new StringBuilder(value.length * 2);
for (int i = 0; i < value.length; ++i) {
int hiNibble = ((value[i] & 0xf0) >> 4);
int loNibble = (value[i] & 0x0f);
if ((i != value.length) && (hiNibble != 0x0f)) // if not pad char
buf.append((char) (hiNibble + '0'));
if ((i != value.length) && (loNibble != 0x0f)) // if not pad char
buf.append((char) (loNibble + '0'));
}
return buf.toString();
}
/**
* formats the value of the tag as an integer and return as a BCD string value.
* No value checking is done. Padding is removed. The nibbles of the BCD are
* reversed, à la Ericsson.
*
* @param value the input BCD encoded array
* @return The decoded string
*
*/
public String parseBCDStringLE(byte[] value) {
StringBuilder buf = new StringBuilder(value.length * 2);
for (int i = 0; i < value.length; ++i) {
int loNibble = ((value[i] & 0xf0) >> 4);
int hiNibble = (value[i] & 0x0f);
if ((i != value.length) && (hiNibble != 0x0f)) // if not pad char
buf.append((char) (hiNibble + '0'));
if ((i != value.length) && (loNibble != 0x0f)) // if not pad char
buf.append((char) (loNibble + '0'));
}
return buf.toString();
}
/**
* formats the value of the tag as an integer and return as a string value.
* No value checking is done.
*
* @param value the input hexadecimal byte encoded array
* @return The parsed value
*/
public String parseInteger(byte[] value) {
String output = "";
boolean negative;
long sum_up;
if ( value != null ) {
negative = ((value[0]>>7) != 0);
sum_up=0;
for(int i = 0; i < value.length; i++)
{
sum_up<<=8;
sum_up+=(long)(value[i] & 0xFF);
if (negative) sum_up-=0x01<<(8*value.length);
}
output += "" + sum_up;
}
return output;
}
/**
* formats the value of the tag as an integer and return as an integer value.
* No value checking is done.
*
* @param value the input hexadecimal byte encoded array
* @return The parsed value
*/
public int parseIntegerAsInteger(byte[] value) {
boolean negative;
int sum_up = 0;
if ( value != null ) {
negative = ((value[0]>>7) != 0);
sum_up=0;
for(int i = 0; i < value.length; i++)
{
sum_up<<=8;
sum_up+=(long)(value[i] & 0xFF);
if (negative) sum_up-=0x01<<(8*value.length);
}
}
return sum_up;
}
/**
* formats the value of the tag as a printable string. No value checking
* is done.
*
* @param value the input hexadecimal byte encoded array
* @return The string
*/
public String parsePrintableString(byte[] value)
{
String output = "";
if (value != null )
{
for(int i = 0; i < value.length; i++) {
output += (char)value[i];
}
}
return output;
}
/**
* formats the value of the tag as an IA5String. No value checking
* is done.
*
* @param value the input hexadecimal byte encoded array
* @return The decoded string
*/
public String parseIA5String(byte[] value) {
String output = "";
int i,len = value.length;
for (i = 0; i < len; ++i) {
char c = (char) ( value[i] & 0xFF );
if ((c < 0) || (c > 127)) return "";
output += (char)( value[i] & 0xFF );
}
return output;
}
/**
* formats the value of the tag as a hexadecimal byte array. Value checking
* is done. If a null value is passed in, we pass back an empty string.
*
* @param value the input hexadecimal byte encoded array
* @return The decoded string
*/
public String parseBytes(byte[] value) {
if (value == null)
{
return "";
}
else
{
StringBuilder buf = new StringBuilder(value.length * 2);
Formatter formatter = new Formatter(buf);
for (byte b : value) {
formatter.format("%02x", b);
}
return buf.toString();
}
}
/**
* formats the value of the tag as a hexadecimal byte array. Value checking
* is done. If a null value is passed in, we pass back an empty string.
*
* @param value the input hexadecimal byte encoded array
* @return The decoded string
*/
public String parseBytes(byte[] value, int length) {
if (value == null)
{
return "";
}
else
{
StringBuilder buf = new StringBuilder(value.length * 2);
Formatter formatter = new Formatter(buf);
for (byte b : value) {
formatter.format("%02x", b);
}
return buf.toString().substring(0, length*2);
}
}
/**
* Parse the ASN.1
*
* @param tag The tag
* @param value The value byte array
* @return The string
* @throws ASN1Exception
*/
public String parseASN1(int tagType, byte[] value) throws ASN1Exception
{
String output ="";
if ( value != null ) {
switch (tagType) {
case INTEGER: output=parseInteger(value); break;
case PRINTABLESTRING: output=parsePrintableString(value); break;
case OCTETSTRING: output=parsePrintableString(value); break;
case IA5STRING: output=parseIA5String(value); break;
case BCDString: output=parseBCDString(value); break;
case BCDStringLE: output=parseBCDStringLE(value); break;
default: output=parseBytes(value);break;
}
}
return output;
}
/**
* Reads a block out of the byte stream without looking at the contents. This
* allows us to easily separate records out of logical streams and treat them
* recursively or procedurally.
*
* @param length The length of the block to return
* @return The block
*/
public byte[] readBlock(int length)
{
byte[] block = new byte[length];
for (int idx = 0 ; idx < length ; idx++)
block[idx] = reader.readByte();
return block;
}
/**
* Reads the next element in the parsing sequence
*
* @return The next element
* @throws Exception
*/
public Asn1Class readNextElement() throws Exception
{
Asn1Class output = new Asn1Class();
int length;
/* Local variables */
int index = 0;
byte value;
byte[] header = new byte[5];
// Get the first byte for analysis
byte nextByte = reader.readByte();
// if this is a filler byte skip it - this is used as packing in some
// formats e.g. Ericsson to get to the end of a block boundary
if (nextByte == 0x00)
{
output.setNullTag(true);
return output;
}
output.setTag(nextByte);
header[0] = (byte) output.getTag();
output.setId(output.getTag() & ~output.TAG_MASK);
if ((output.getTag() & output.TAG_MASK) == output.TAG_MASK) {
/* Long tag encoded as sequence of 7-bit values. This doesn't try to
handle tags > INT_MAX, it'd be pretty peculiar ASN.1 if it had to
use tags this large */
output.setTag(0);
do {
value = reader.readByte();
header[index + 1] = value;
output.setTag((output.getTag() << 7) | (value & 0x7F));
index++;
} while (((value & output.LEN_XTND) != 0) && (index < 5) && (reader.ready()));
// If we ran off the end of the header, it is an error
if (index == 5) {
return null;
}
// set the tag
output.setTagFromByteArray(header);
// set the raw tag
output.setRawTag(parseBytes(header,index+1));
}
else
{
// Simple 1 byte tag
output.setTag(output.getTag() & output.TAG_MASK);
output.setRawTag(parseBytes(header,1));
}
// Parse the length out of the stream
length = reader.readByte();
if (length == 0) {
// it really is 0
length = 0;
} else if ((length & output.LEN_MASK) != length) {
// This is a multibyte length. Find the actual length
int numLengthBytes = (length & output.LEN_MASK);
length = 0x00000000;
byte[] buffer = new byte[numLengthBytes];
try {
buffer = readBlock(numLengthBytes);
} catch (Exception ex) {
throw ex;
}
switch (numLengthBytes) {
case 0:
// we have a zero length
break;
case 1:
length |= (0x000000FF & buffer[0]);
break;
case 2:
length |= ((0x000000FF & buffer[0]) << 8)
| (0x000000FF & buffer[1]);
break;
case 3:
length |= ((0x000000FF & buffer[0]) << 16)
| ((0x000000FF & buffer[1]) << 8)
| (0x000000FF & buffer[2]);
break;
case 4:
length |= ((0x000000FF & buffer[0]) << 24)
| ((0x000000FF & buffer[1]) << 16)
| ((0x000000FF & buffer[2]) << 8)
| (0x000000FF & buffer[3]);
break;
default:
throw new Exception("Length cannot be represented as a Java int");
}
}
output.setLength(length);
if (!output.isConstructed()) {
output.setValue(readBlock(output.getLength()));
}
return output;
}
}