/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) 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 OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.protocols.snmp.asn1;
import java.math.BigInteger;
/**
* The BerEncoder class is used to implement the AsnEncoder interface for the
* Basic Encoding Rules (BER). The encoding rules are used to encode and decode
* SNMP values using BER.
*
* @author <a href="mailto:weave@oculan.com">Brian Weaver </a>
*/
public class BerEncoder implements AsnEncoder {
/**
* Defines the ASN.1 long length marker for the Basic Encoding Rule (BER)
*/
private static final byte LONG_LENGTH = (byte) 0x80;
/**
* Defines the "high bit" that is the sign extension bit for a 8-bit signed
* value.
*/
private static final byte HIGH_BIT = (byte) 0x80;
/**
* Defines the BER extension "value" that is used to mark an extension type.
*/
private static final byte EXTENSION_ID = (byte) 0x1F;
/**
* Defines the BER constructor id
*/
private static final byte CONSTRUCTOR = (byte) 0x20;
/**
* Converts a primitive byte to a primitive long using "unsigned" logic.
*
* @param b
* The 8-bit value to convert
* @return Returns the 32-bit converted value
*
*/
protected static int byteToInt(byte b) {
return (b < 0) ? 256 + (int) b : (int) b;
}
/**
* Converts a primitive byte to a primitive long. The byte is converted
* using "unsigned" logic
*
* @param b
* The 8-bit value to convert
* @return Returns the 64-bit converted value
*
*/
protected static long byteToLong(byte b) {
return (b < 0) ? 256 + (long) b : (long) b;
}
/**
* Used to determine if the ASN.1 type is a constructor.
*
* @param b
* The ASN.1 type
*
* @return True if the ASN.1 type is a constructor, otherwise a false is
* returned.
*
*/
protected static boolean isConstructor(byte b) {
return ((b & CONSTRUCTOR) == CONSTRUCTOR);
}
/**
* Used to test if the ASN.1 type is an extension.
*
* @param b
* The ASN.1 type.
*
* @return True if the ASN.1 type is an extension. False if the ASN.1 type
* is not an extension.
*
*/
protected static boolean isExtensionId(byte b) {
return ((b & EXTENSION_ID) == EXTENSION_ID);
}
/**
* Used to copy data from one buffer to another. The method has the
* flexability to allow the caller to specify an offset in each buffer and
* the total number of bytes to copy
*
* @param src
* The source buffer
* @param srcOff
* The offset of the first byte in the source buffer
* @param dest
* The destination buffer
* @param destOff
* The offset of the first byte in the destination buffer
* @param bytesToCopy
* The number of bytes to copy
*
* @exception ArrayIndexOutOfBoundsException
* Thrown if there is insufficent space in either array to
* copy the data.
*
*/
protected static void copy(byte[] src, int srcOff, byte[] dest, int destOff, int bytesToCopy) throws ArrayIndexOutOfBoundsException {
if ((dest.length - destOff) < bytesToCopy || (src.length - srcOff) < bytesToCopy)
throw new ArrayIndexOutOfBoundsException("Destination or source buffer is insufficent");
for (int x = bytesToCopy - 1; x >= 0; x--) {
dest[destOff + x] = src[srcOff + x];
}
}
/**
* Used to copy data from one buffer to another. The method has the
* flexability to allow the caller to specify an offset in each buffer and
* the total number of integers to copy
*
* @param src
* The source buffer
* @param srcOff
* The offset of the first integer in the source buffer
* @param dest
* The destination buffer
* @param destOff
* The offset of the first integer in the destination buffer
* @param intsToCopy
* The number of integer elements to copy
*
* @exception ArrayIndexOutOfBoundsException
* Thrown if there is insufficent space in either array to
* copy the data.
*
*/
protected static void copy(int[] src, int srcOff, int[] dest, int destOff, int intsToCopy) throws ArrayIndexOutOfBoundsException {
if ((dest.length - destOff) < intsToCopy || (src.length - srcOff) < intsToCopy)
throw new ArrayIndexOutOfBoundsException("Destination or source buffer is insufficent");
for (int x = intsToCopy - 1; x >= 0; x--) {
dest[destOff + x] = src[srcOff + x];
}
}
/**
* Rotates a give buffer area marked by begin, pivot, and end. The pivot
* marks the point where the bytes between [pivot..end) are moved to the
* position marked by begin. The bytes between [begin..pivot) are shifted
* such that begin is at [begin+(end-pivot)].
*
* @param buf
* The buffer containing the data to rotate
* @param begin
* The start of the rotation
* @param pivot
* The pivot point for the rotation
* @param end
* The end of the rotational buffer
*
* @exception ArrayIndexOutOfBoundsException
* Thrown if an access exception occurs
*
*/
protected static void rotate(byte[] buf, int begin, int pivot, int end) throws ArrayIndexOutOfBoundsException {
int dist = end - pivot;
byte[] hold = new byte[dist];
copy(buf, // source
pivot, // source offset
hold, // destination
0, // destination offset
dist); // length
//
// shift from end of buffer to front. This
// way we do not have to worry about data
// corruption
//
for (int x = (pivot - begin) - 1; x >= 0; x--) {
buf[begin + dist + x] = buf[begin + x]; // SHIFT!
}
copy(hold, // source
0, // source offset
buf, // destination
begin, // destination offset
dist); // length
}
/**
* Default constructor for the BER Encoder.
*
*/
public BerEncoder() {
// default class constructor.
// Does nothing
}
/**
*
* The buildLength() method is used to encode an ASN.1 length into the
* specified byte buffer. The method is defined in the AsnEncoder interface.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnLength
* The length to be encoded.
*
* @return Returns the new offset for the next encoding routine. If the
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*/
public int buildLength(byte[] buf, int startOffset, int asnLength) throws AsnEncodingException {
if (asnLength <= 0x7f) {
//
// check the buffer length
//
if ((buf.length - startOffset) < 1)
throw new AsnEncodingException("Buffer overflow error");
buf[startOffset++] = (byte) (asnLength & 0x7f);
} else if (asnLength <= 0xff) {
//
// check the buffer length
//
if ((buf.length - startOffset) < 2)
throw new AsnEncodingException("Buffer overflow error");
buf[startOffset++] = (byte) (0x01 | LONG_LENGTH);
buf[startOffset++] = (byte) (asnLength & 0xff);
} else // 0xff < asnLength <= 0xffff
{
//
// check the buffer length
//
if ((buf.length - startOffset) < 3)
throw new AsnEncodingException("Buffer overflow error");
buf[startOffset++] = (byte) (0x02 | LONG_LENGTH);
buf[startOffset++] = (byte) ((asnLength >>> 8) & 0xff);
buf[startOffset++] = (byte) (asnLength & 0xff);
}
return startOffset;
}
/**
*
* The parseLength() method is used to decode an ASN.1 length from the
* specified buffer. The method is defined to support the AsnEncoder
* interface.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset and the
* decoded length. The first object is an Integer object and
* contains the new offset for the next object in buf. The second
* object is an Integer and contains the actual decoded length.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseLength(byte[] buf, int startOffset) throws AsnDecodingException {
//
// check the buffer length
//
if ((buf.length - startOffset) < 1)
throw new AsnDecodingException("Buffer underflow error");
//
// 1) Integer w/new offset value
// 2) Integer w/recovered length
//
Object[] retVals = new Object[2];
//
// get the first byte and check it for
// the long length field
//
byte numBytes = buf[startOffset++];
if ((numBytes & LONG_LENGTH) == 0) {
//
// short definiate length encoding
//
numBytes = (byte) (numBytes & ~LONG_LENGTH);
retVals[1] = new Integer(byteToInt(numBytes));
} else {
//
// Long length encoding
//
numBytes = (byte) (numBytes & ~LONG_LENGTH);
if (numBytes == 1) {
if ((buf.length - startOffset) < 1)
throw new AsnDecodingException("Buffer underflow error");
retVals[1] = new Integer(byteToInt(buf[startOffset++]));
} else if (numBytes == 2) {
if ((buf.length - startOffset) < 2)
throw new AsnDecodingException("Buffer underflow error");
int val = byteToInt(buf[startOffset++]) << 8 | byteToInt(buf[startOffset++]);
retVals[1] = new Integer(val);
} else {
throw new AsnDecodingException("Invalid ASN.1 length");
}
}
//
// create the offset object
//
retVals[0] = new Integer(startOffset);
return retVals;
}
/**
*
* The buildHeader() method is used to encode an ASN.1 header into the
* specified byte buffer. The method is defined to support the AsnEncoder
* interface. This method is dependant on the buildLength() method.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
* @param asnLength
* The length to be encoded.
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildHeader(byte[] buf, int startOffset, byte asnType, int asnLength) throws AsnEncodingException {
if ((buf.length - startOffset) < 1)
throw new AsnEncodingException("Buffer overflow error");
buf[startOffset++] = asnType;
return buildLength(buf, startOffset, asnLength);
}
/**
*
* The parseHeader() method is used to decode an ASN.1 header from the
* specified buffer. The method is defined to support the AsnEncoder
* interface. The method also calls the parseLength() method.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset, ASN.1 type,
* and decoded length. The first object is an Integer object and
* contains the new offset for the next object in buf. The second
* object is a Byte object that represents the decoded ASN.1 Type.
* The third object is an Integer and contains the actual decoded
* length.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseHeader(byte[] buf, int startOffset) throws AsnDecodingException {
if ((buf.length - startOffset) < 1)
throw new AsnDecodingException("Insufficent buffer length");
//
// get the ASN.1 Type
//
byte asnType = buf[startOffset++];
if (isExtensionId(asnType))
throw new AsnDecodingException("Buffer underflow error");
//
// get the length
//
Object[] lenVals = parseLength(buf, startOffset);
//
// create the return values
// 1) offset for next object
// 2) ASN.1 type
// 3) ASN.1 length
//
Object[] rVals = new Object[3];
rVals[0] = lenVals[0];
rVals[1] = new Byte(asnType);
rVals[2] = lenVals[1];
//
// return the results
//
return rVals;
}
/**
*
* The buildInteger32() method is used to encode an ASN.1 32-bit signed
* integer into the specified byte buffer.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
* @param asnInt32
* The 32-bit signed integer to encode.
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildInteger32(byte[] buf, int startOffset, byte asnType, int asnInt32) throws AsnEncodingException {
//
// Store a copy of the value to mask off the
// unnecessary bits. There should not be any
// sequence of 9 consecutive 1's or 0 bits
//
int mask = 0xff800000; // negative :)
int intSz = 4; // int == 32-bits == 4 bytes in java
while (((asnInt32 & mask) == 0 || (asnInt32 & mask) == mask) && intSz > 1) {
--intSz;
asnInt32 = (asnInt32 << 8);
}
//
// build the header
//
startOffset = buildHeader(buf, startOffset, asnType, intSz);
//
// verify the buffer length
//
if ((buf.length - startOffset) < intSz)
throw new AsnEncodingException("Insufficent buffer size");
//
// mask off and store the values
//
mask = 0xff000000;
while (intSz-- > 0) {
byte b = (byte) ((asnInt32 & mask) >>> 24);
buf[startOffset++] = b;
asnInt32 = (asnInt32 << 8);
}
//
// return the result
//
return startOffset;
}
/**
*
* The parseInteger32() method is used to decode an ASN.1 32-bit signed
* integer from the specified buffer.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset, ASN.1 type,
* and value. The first object is an Integer object and contains the
* new offset for the next object in buf. The second object is a
* Byte object that represents the decoded ASN.1 Type. The third
* object is an Integer and contains the actual decoded value.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseInteger32(byte[] buf, int startOffset) throws AsnDecodingException {
//
// parse the header first
//
Object[] hdrVals = parseHeader(buf, startOffset);
startOffset = ((Integer) hdrVals[0]).intValue();
Byte asnType = (Byte) hdrVals[1];
int asnLength = ((Integer) hdrVals[2]).intValue();
//
// check for sufficent data
//
if ((buf.length - startOffset) < asnLength)
throw new AsnDecodingException("Buffer underflow error");
//
// check to see that we can actually decode
// the value (must fit in integer == 32-bits)
//
if (asnLength > 4)
throw new AsnDecodingException("Integer too large: cannot decode");
//
// check for negativity!
//
int asnValue = 0;
if ((buf[startOffset] & HIGH_BIT) == HIGH_BIT)
asnValue = -1;
//
// extract the information from the buffer
//
while (asnLength-- > 0) {
asnValue = (asnValue << 8) | byteToInt(buf[startOffset++]);
}
//
// return the data!
//
Object[] rVals = new Object[3];
rVals[0] = new Integer(startOffset);
rVals[1] = asnType; // java.lang.Byte()
rVals[2] = new Integer(asnValue);
return rVals;
}
/**
*
* The buildUInteger32() method is used to encode an ASN.1 32-bit unsigned
* integer into the specified byte buffer.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
* @param asnUInt32
* The 32-bit unsigned integer to encode.
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildUInteger32(byte[] buf, int startOffset, byte asnType, long asnUInt32) throws AsnEncodingException {
//
// NOTE: the value is a 'long' which is 64 bits long, but we only use
// the lower order 32-bits! If the number is greater than 2^32 - 1
// the upper order bits will be lost!
//
// Store a copy of the value to mask off the
// unnecessary bits. There should not be any
// sequence of 9 consecutive 1's or 0 bits
//
long mask = 0xff800000L;
int intSz = 4; // int == 32-bits == 4 bytes in java
boolean bAddNullByte = false;
//
// check to see if an additional (zero) byte is needed
// Since an Integer is a signed 32-bit quantity, if the
// passed long is greater than Integer.MAX_VALUE (2^31-1)
// then it must have the high-bit (2^32) set!
//
// effectively checking to see if (asnUInt32 & 0x80000000L) != 0
//
if (asnUInt32 > (long) (Integer.MAX_VALUE)) {
bAddNullByte = true;
intSz++;
}
//
// check for and remove any sequence of 9 consecutive zeros
// from the head of the number
//
// NOTE: 10/9/00 Weave - This use to also mask off any set
// of 9 consecutive 1's as well, but that didn't make
// any sense because it's unsigned. What if you wanted
// to send 0xffffffffL, masking off the ones would be
// incorrect cause for unsigned there isn't going to
// be a sign extension.
//
while ((asnUInt32 & mask) == 0 && intSz > 1) {
--intSz;
asnUInt32 = (asnUInt32 << 8);
}
//
// build the header
//
startOffset = buildHeader(buf, startOffset, asnType, intSz);
//
// verify the buffer length
//
if ((buf.length - startOffset) < intSz)
throw new AsnEncodingException("Buffer overflow error");
//
// Add the null byte if necessary
//
if (bAddNullByte) {
buf[startOffset++] = (byte) 0;
--intSz;
}
//
// mask off and store the values
//
mask = 0xff000000L;
while (intSz-- > 0) {
byte b = (byte) ((asnUInt32 & mask) >>> 24);
buf[startOffset++] = b;
asnUInt32 = (asnUInt32 << 8);
}
//
// return the result
//
return startOffset;
}
/**
*
* The parseUInteger32() method is used to decode an ASN.1 32-bit unsigned
* integer from the specified buffer.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset, ASN.1 type,
* and value. The first object is an Integer object and contains the
* new offset for the next object in buf. The second object is a
* Byte object that represents the decoded ASN.1 Type. The third
* object is a Long object and contains the actual decoded value.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseUInteger32(byte[] buf, int startOffset) throws AsnDecodingException {
//
// parse the header first
//
Object[] hdrVals = parseHeader(buf, startOffset);
startOffset = ((Integer) hdrVals[0]).intValue();
Byte asnType = (Byte) hdrVals[1];
int asnLength = ((Integer) hdrVals[2]).intValue();
//
// check for sufficent data
//
if ((buf.length - startOffset) < asnLength)
throw new AsnDecodingException("Buffer underflow error");
//
// check to see that we can actually decode
// the value (must fit in integer == 32-bits)
//
if (asnLength > 5)
throw new AsnDecodingException("Integer too large: cannot decode");
//
// check for negativity!
//
long asnValue = 0;
if ((buf[startOffset] & HIGH_BIT) == HIGH_BIT)
asnValue = -1;
//
// extract the information from the buffer
//
while (asnLength-- > 0) {
asnValue = (asnValue << 8) | byteToLong(buf[startOffset++]);
}
//
// remember this is a 32-bit number, mask off all but
// the last 32-bits!
//
asnValue = (asnValue & 0xffffffffL);
//
// return the data!
//
Object[] rVals = new Object[3];
rVals[0] = new Integer(startOffset);
rVals[1] = asnType;
rVals[2] = new Long(asnValue);
return rVals;
}
/**
*
* The buildUInteger64() method is used to encode an ASN.1 64-bit unsigned
* integer into the specified byte buffer.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
* @param asnUInt64
* The 64-bit unsigned integer to encode.
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildUInteger64(byte[] buf, int startOffset, byte asnType, BigInteger asnUInt64) throws AsnEncodingException {
//
// compute the number of bits required and the
// integer size required to represent it minimally
//
byte[] bytes = asnUInt64.toByteArray(); // returns 2 complement minimum
// representation + sign bit!
//
// build the header
//
startOffset = buildHeader(buf, startOffset, asnType, bytes.length);
//
// verify the buffer length
//
if ((buf.length - startOffset) < bytes.length)
throw new AsnEncodingException("Buffer overflow error");
for (int i = 0; i < bytes.length; ++i)
buf[startOffset++] = bytes[i];
//
// return the result
//
return startOffset;
}
/**
*
* The parseUInteger64() method is used to decode an ASN.1 64-bit unsigned
* integer from the specified buffer.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset, ASN.1 type,
* and value. The first object is an Integer object and contains the
* new offset for the next object in buf. The second object is a
* Byte object that represents the decoded ASN.1 Type. The third
* object is a Long object and contains the actual decoded value.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseUInteger64(byte[] buf, int startOffset) throws AsnDecodingException {
//
// parse the header first
//
Object[] hdrVals = parseHeader(buf, startOffset);
startOffset = ((Integer) hdrVals[0]).intValue();
Byte asnType = (Byte) hdrVals[1];
int asnLength = ((Integer) hdrVals[2]).intValue();
//
// check for sufficent data
//
if ((buf.length - startOffset) < asnLength)
throw new AsnDecodingException("Buffer underflow error");
//
// check to see that we can actually decode
// the value (must fit in integer == 64-bits)
//
if (asnLength > 9)
throw new AsnDecodingException("Integer too large: cannot decode");
byte[] asnBuf = new byte[asnLength];
for (int i = 0; i < asnLength; ++i)
asnBuf[i] = buf[startOffset++];
BigInteger asnValue = new BigInteger(asnBuf);
//
// return the data!
//
Object[] rVals = new Object[3];
rVals[0] = new Integer(startOffset);
rVals[1] = asnType;
rVals[2] = asnValue;
return rVals;
}
/**
*
* The buildNull() method is used to encode an ASN.1 NULL value into the
* specified byte buffer.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildNull(byte[] buf, int startOffset, byte asnType) throws AsnEncodingException {
return buildHeader(buf, startOffset, asnType, 0);
}
/**
*
* The parseNull() method is used to decode an ASN.1 Null value from the
* specified buffer. Since there is no "null" value only the new offset and
* ASN.1 type are returned.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset and the
* ASN.1 type. The first object is an Integer object and contains
* the new offset for the next object in buf. The second object is a
* Byte object that represents the decoded ASN.1 Type.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseNull(byte[] buf, int startOffset) throws AsnDecodingException {
Object[] hdrVals = parseHeader(buf, startOffset);
//
// Verify the ASN.1 length == 0
//
if (((Integer) hdrVals[2]).intValue() != 0)
throw new AsnDecodingException("Malformed ASN.1 Type");
Object[] rVals = new Object[2];
rVals[0] = hdrVals[0];
rVals[1] = hdrVals[1];
return rVals;
}
/**
*
* The buildString() method is used to encode an ASN.1 string value into the
* specified byte buffer.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
* @param opaque
* An array of bytes to encode into the string.
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildString(byte[] buf, int startOffset, byte asnType, byte[] opaque) throws AsnEncodingException {
//
// get the length of the data
//
int asnLength = opaque.length;
//
// build the header
//
startOffset = buildHeader(buf, startOffset, asnType, asnLength);
//
// check the data length verses the remaining buffer
// and then copy the data
//
if ((buf.length - startOffset) < opaque.length)
throw new AsnEncodingException("Insufficent buffer length");
try {
copy(opaque, // source
0, // source offset
buf, // destination
startOffset, // destination offset
opaque.length); // bytes to copy
} catch (ArrayIndexOutOfBoundsException err) {
throw new AsnEncodingException("Buffer overflow error");
}
//
// return the new offset
//
return startOffset + opaque.length;
}
/**
*
* The parseString() method is used to decode an ASN.1 opaque string from
* the specified buffer.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset and ASN.1
* type, and byte array. The first object is an Integer object and
* contains the new offset for the next object in buf. The second
* object is a Byte object that represents the decoded ASN.1 Type.
* The third object is an array of primitive bytes.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseString(byte[] buf, int startOffset) throws AsnDecodingException {
Object[] hdrVals = parseHeader(buf, startOffset);
//
// get the header values
//
startOffset = ((Integer) hdrVals[0]).intValue();
Byte asnType = ((Byte) hdrVals[1]);
int asnLength = ((Integer) hdrVals[2]).intValue();
//
// verify that there is enough data to decode
//
if ((buf.length - startOffset) < asnLength)
throw new AsnDecodingException("Insufficent buffer length");
//
// copy the data
//
byte[] opaque = new byte[asnLength];
try {
copy(buf, // source buffer
startOffset, // source offset
opaque, // destination buffer
0, // destination offset
asnLength); // number of items to copy
} catch (ArrayIndexOutOfBoundsException err) {
throw new AsnDecodingException("Buffer underflow exception");
}
//
// fix the return values
//
Object[] rVals = new Object[3];
rVals[0] = new Integer(startOffset + asnLength);
rVals[1] = asnType;
rVals[2] = opaque;
return rVals;
}
/**
*
* The buildObjectId() method is used to encode an ASN.1 object id value
* into the specified byte buffer.
*
* @param buf
* The output buffer of encoded bytes.
* @param startOffset
* The offset from the start of the buffer where the method
* should start writing the encoded data.
* @param asnType
* The ASN.1 type to place in the buffer
* @param oids
* An array of integers to encode.
*
* @return Returns the new offset for the next encoding routine. If
* startOffset is subtracted from the return value then the length
* of the encoded data can be determined.
*
* @exception AsnEncodingException
* Thrown if an error occurs encoding the datatype.
*
*/
public int buildObjectId(byte[] buf, int startOffset, byte asnType, int[] oids) throws AsnEncodingException {
if ((buf.length - startOffset) < 1)
throw new AsnEncodingException("Buffer overflow error");
int[] toEncode = oids;
int begin = startOffset; // used for rotate!
//
// silently create an oid = ".0.0" for arrays
// less than 2 in length
//
if (oids.length < 2) {
toEncode = new int[2];
toEncode[0] = 0;
toEncode[1] = 0;
}
//
// verify that it is a valid object id!
//
if (toEncode[0] < 0 || toEncode[0] > 2)
throw new AsnEncodingException("Invalid Object Identifier");
if (toEncode[1] < 0 || toEncode[1] > 40)
throw new AsnEncodingException("Invalid Object Identifier");
//
// add the first oid!
//
buf[startOffset++] = (byte) (toEncode[0] * 40 + toEncode[1]);
int oidNdx = 2;
//
// encode the remainder
//
while (oidNdx < toEncode.length) {
//
// get the next object id
//
int oid = toEncode[oidNdx++];
//
// encode it
//
if (oid >= 0 && oid < 127) {
if ((buf.length - startOffset) < 1)
throw new AsnEncodingException("Buffer overflow error");
buf[startOffset++] = (byte) oid;
} else // oid >= 127
{
int mask = 0, bits = 0; // avoids compiler whining!
int tmask = 0, tbits = 0; // even if it may be right ;)
//
// figure out the number of bits required
//
tmask = 0x7f;
tbits = 0;
while (tmask != 0) {
if ((oid & tmask) != 0) {
mask = tmask;
bits = tbits;
}
tmask <<= 7;
tbits += 7;
}
while (mask != 0x7f) {
if ((buf.length - startOffset) < 1)
throw new AsnEncodingException("Buffer overflow error");
buf[startOffset++] = (byte) (((oid & mask) >>> bits) | HIGH_BIT);
mask = (mask >>> 7);
bits -= 7;
//
// fix an off-shift mask (4 bits --> 7 bits)
//
if (mask == 0x01e00000)
mask = 0x0fe00000;
}
//
// add the last byte
//
if ((buf.length - startOffset) < 1)
throw new AsnEncodingException("Insufficent buffer space");
buf[startOffset++] = (byte) (oid & mask);
} // end else
} // end while oids!
//
// mark the "pivot" of the rotation
//
int pivot = startOffset;
//
// encode the length
//
int asnLength = pivot - begin;
int end = buildHeader(buf, pivot, asnType, asnLength);
//
// rotate the bytes around
//
try {
rotate(buf, begin, pivot, end);
} catch (ArrayIndexOutOfBoundsException err) {
throw new AsnEncodingException("Insufficent buffer space");
}
return end;
}
/**
*
* The parseObjectId() method is used to decode an ASN.1 Object Identifer
* from the specified buffer.
*
* @param buf
* The input buffer
* @param startOffset
* The offset to start decoding in the buffer
*
* @return Returns an Object array that contains the new offset and ASN.1
* type, and ObjectId array. The first object is an Integer object
* and contains the new offset for the next object in buf. The
* second object is a Byte object that represents the decoded ASN.1
* Type. The third object is an array of primitive integers.
*
* @exception AsnDecodingException
* Thrown if an error occurs decoding the buffer.
*/
public Object[] parseObjectId(byte[] buf, int startOffset) throws AsnDecodingException {
Object[] hdrVals = parseHeader(buf, startOffset);
startOffset = ((Integer) hdrVals[0]).intValue();
Byte asnType = (Byte) hdrVals[1];
int asnLength = ((Integer) hdrVals[2]).intValue();
//
// check for sufficent data
//
if ((buf.length - startOffset) < asnLength)
throw new AsnDecodingException("Buffer underflow error");
//
// if the length is zero then
// silently create a ".0.0" object
// id and return it!
//
if (asnLength == 0) {
int[] ids = new int[2];
ids[0] = ids[1] = 0;
Object[] rVals = new Object[3];
rVals[0] = new Integer(startOffset);
rVals[1] = asnType;
rVals[2] = ids;
return rVals;
}
//
// build a large buffer for the moment.
// definately may need to srink the buffer
// Use asnLength + 1 since the first byte
// encode's two object ids
//
int idsOff = 0;
int[] ids = new int[asnLength + 1];
//
// decode the first byte
//
{
--asnLength;
int oid = byteToInt(buf[startOffset++]);
ids[idsOff++] = oid / 40;
ids[idsOff++] = oid % 40;
}
//
// decode the rest of the identifiers
//
while (asnLength > 0) {
int oid = 0;
boolean done = false;
do {
--asnLength;
byte b = buf[startOffset++];
oid = (oid << 7) | (int) (b & 0x7f);
if ((b & HIGH_BIT) == 0)
done = true;
} while (!done);
ids[idsOff++] = oid;
}
//
// now perpare the return value
//
int[] retOids;
if (idsOff == ids.length) {
retOids = ids;
} else {
retOids = new int[idsOff];
copy(ids, // source
0, // source offset
retOids, // destination
0, // destination offset
idsOff); // number of items to copy
}
//
// build the return objects
//
Object[] rVals = new Object[3];
rVals[0] = new Integer(startOffset);
rVals[1] = asnType;
rVals[2] = retOids;
return rVals;
}
} // end class!