/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.asn1; import java.util.ArrayList; /** * This class defines an ASN.1 element that serves as a sequence, whose value is * an ordered set of other ASN.1 elements. * * * @author Neil A. Wilson */ public class ASN1Sequence extends ASN1Element { /** * The set of elements that will be used if there are no elements. */ public static final ASN1Element[] NO_ELEMENTS = new ASN1Element[0]; // The set of ASN.1 elements associated with this sequence. ASN1Element[] elements; /** * Creates a new ASN.1 sequence with no elements encoded in the value. */ public ASN1Sequence() { this(ASN1_SEQUENCE_TYPE, NO_ELEMENTS); } /** * Creates a new ASN.1 sequence with the specified type and no elements * encoded in the value. * * @param type The type to use for this ASN.1 sequence. */ public ASN1Sequence(byte type) { this(type, NO_ELEMENTS); } /** * Creates a new ASN.1 sequence to hold the specified set of ASN.1 elements. * * @param elements The set of ASN.1 elements to encode in the value of this * sequence. */ public ASN1Sequence(ASN1Element[] elements) { this(ASN1_SEQUENCE_TYPE, elements); } /** * Creates a new ASN.1 sequence with the given type to hold the specified set * of ASN.1 elements. * * @param type The type to use for this ASN.1 sequence. * @param elements The set of ASN.1 elements to encode in the value of this * sequence. */ public ASN1Sequence(byte type, ASN1Element[] elements) { super(type); this.elements = elements; if ((elements == null) || (elements.length == 0)) { this.elements = NO_ELEMENTS; } replaceElements(elements); } /** * Creates a new ASN.1 sequence to hold the specified set of ASN.1 elements. * * @param elements The set of ASN.1 elements to encode in the value of this * sequence. */ public ASN1Sequence(ArrayList<ASN1Element> elements) { this(ASN1_SEQUENCE_TYPE, elements); } /** * Creates a new ASN.1 sequence with the given type to hold the specified set * of ASN.1 elements. * * @param type The type to use for this ASN.1 sequence. * @param elements The set of ASN.1 elements to encode in the value of this * sequence. */ public ASN1Sequence(byte type, ArrayList<ASN1Element> elements) { super(type); if ((elements == null) || elements.isEmpty()) { this.elements = NO_ELEMENTS; } else { ASN1Element[] elementArray = new ASN1Element[elements.size()]; elements.toArray(elementArray); replaceElements(elementArray); } } /** * Retrieves the set of elements that are encoded in this ASN.1 sequence. * * @return The set of elements that are encoded in this ASN.1 sequence. */ public ASN1Element[] getElements() { return elements; } /** * Adds the specified ASN.1 element to the set of elements encoded in this * sequence. * * @param element The ASN.1 element to include in the set of elements * encoded in this sequence. */ public void addElement(ASN1Element element) { // Add the specified element to the current set. ASN1Element[] newElements = new ASN1Element[elements.length+1]; System.arraycopy(elements, 0, newElements, 0, elements.length); newElements[elements.length] = element; this.elements = newElements; // Create the new encoded value by appending the bytes of the new element // to the existing value. byte[] encodedElement = element.encode(); byte[] newSequenceValue = new byte[value.length + encodedElement.length]; System.arraycopy(value, 0, newSequenceValue, 0, value.length); System.arraycopy(encodedElement, 0, newSequenceValue, value.length, encodedElement.length); setValue(newSequenceValue); } /** * Removes all elements encoded in the value of this ASN.1 element. */ public void removeAllElements() { setValue(EMPTY_BYTES); elements = NO_ELEMENTS; } /** * Replaces the current set of elements with the provided ASN.1 element. * * @param element The ASN.1 element to use to replace the existing set of * elements encoded in this sequence. */ public void replaceElements(ASN1Element element) { replaceElements(new ASN1Element[] { element }); } /** * Replaces the current set of elements with the provided set. * * @param elements The set of ASN.1 elements to use to replace the existing * set of elements encoded in this sequence. */ public void replaceElements(ASN1Element[] elements) { if (elements == null) { elements = NO_ELEMENTS; } this.elements = elements; // Figure out the total length of the encoded value int totalLength = 0; for (int i=0; i < elements.length; i++) { totalLength += elements[i].encodedElement.length; } byte[] encodedValue = new byte[totalLength]; int startPos = 0; for (int i=0; i < elements.length; i++) { startPos += elements[i].encode(encodedValue, startPos); } setValue(encodedValue); } /** * Decodes the provided byte array as an ASN.1 sequence element. * * @param encodedValue The encoded ASN.1 element. * * @return The decoded ASN.1 sequence element. * * @throws ASN1Exception If the provided byte array cannot be decoded as an * ASN.1 sequence element. */ public static ASN1Sequence decodeAsSequence(byte[] encodedValue) throws ASN1Exception { // First make sure that there actually was a value provided if ((encodedValue == null) || (encodedValue.length == 0)) { throw new ASN1Exception("No data to decode"); } // Make sure that the encoded value is at least two bytes. Otherwise, there // can't be both a type and a length if (encodedValue.length < 2) { throw new ASN1Exception("Not enough data to make a valid ASN.1 element"); } // First, see if the type is supposed to be a single byte or multiple bytes. if ((encodedValue[0] & 0x1F) == 0x1F) { // This indicates that the type is supposed to consist of multiple bytes, // which we do not support, so throw an exception throw new ASN1Exception("Multibyte type detected (not supported in " + "this package)"); } byte type = encodedValue[0]; // Next, look at the second byte to see if there is a single byte or // multibyte length. int length = 0; int valueStartPos = 2; if ((encodedValue[1] & 0x7F) != encodedValue[1]) { if ((encodedValue[1] & 0x7F) == 0x00) { length = 128; } else { int numLengthBytes = (encodedValue[1] & 0x7F); if (encodedValue.length < (numLengthBytes + 2)) { throw new ASN1Exception ("Determined the length is encoded in " + numLengthBytes + " bytes, but not enough " + "bytes exist in the encoded value"); } else { byte[] lengthArray = new byte[numLengthBytes+1]; lengthArray[0] = encodedValue[1]; System.arraycopy(encodedValue, 2, lengthArray, 1, numLengthBytes); length = decodeLength(lengthArray); valueStartPos += numLengthBytes; } } } else { length = encodedValue[1]; } // Make sure that there are the correct number of bytes in the value. If // not, then throw an exception. if ((encodedValue.length - valueStartPos) != length) { throw new ASN1Exception("Expected a value of " + length + " bytes, but " + (encodedValue.length - valueStartPos) + " bytes exist"); } byte[] value = new byte[length]; ASN1Element[] elements = NO_ELEMENTS; if (value.length > 0) { System.arraycopy(encodedValue, valueStartPos, value, 0, length); elements = decodeSequenceElements(value); } // Finally, create the sequence and return it. Because we have already done // all the work up front, don't do it again by using a constructor that will // re-encode. Instead, make use of the fact that we have direct access to // instance variables. ASN1Sequence sequence = new ASN1Sequence(type); sequence.setValue(value); sequence.elements = elements; return sequence; } /** * Decodes the provided byte array as if it were a set of ASN.1 elements. * * @param sequenceValue The set of encoded ASN.1 elements. * * @return The ASN.1 elements that were decoded from the provided byte array. * * @throws ASN1Exception If the provided byte array cannot be decoded into a * set of ASN.1 elements. */ public static ASN1Element[] decodeSequenceElements(byte[] sequenceValue) throws ASN1Exception { ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(); int startPos = 0; while (startPos < sequenceValue.length) { // The first byte is going to be the type. Make sure it's not a // multibyte type. byte type = sequenceValue[startPos]; if ((type & 0x1F) == 0x1F) { throw new ASN1Exception("Multibyte type detected (not supported in " + "this package)"); } // The length starts at the second position, but may extend beyond that byte firstLengthByte = sequenceValue[startPos+1]; int length = 0; int valueStartPos = startPos+2; if ((firstLengthByte & 0x7F) == firstLengthByte) { length = firstLengthByte; } else if ((firstLengthByte & 0x7F) == 0x00) { length = 128; } else { // There are multiple bytes in the length. Figure out how many. int numLengthBytes = (firstLengthByte & 0x7F); length = 0x00000000; switch (numLengthBytes) { case 1: length |= (0x000000FF & sequenceValue[startPos+2]); valueStartPos++; break; case 2: length |= ((0x000000FF & sequenceValue[startPos+2]) << 8) | (0x000000FF & sequenceValue[startPos+3]); valueStartPos += 2; break; case 3: length |= ((0x000000FF & sequenceValue[startPos+2]) << 16) | ((0x000000FF & sequenceValue[startPos+3]) << 8) | (0x000000FF & sequenceValue[startPos+4]); valueStartPos += 3; break; case 4: length |= ((0x000000FF & sequenceValue[startPos+2]) << 24) | ((0x000000FF & sequenceValue[startPos+3]) << 16) | ((0x000000FF & sequenceValue[startPos+4]) << 8) | (0x000000FF & sequenceValue[startPos+5]); valueStartPos += 4; break; default: throw new ASN1Exception("Specified length cannot be " + "represented as a Java int"); } } // Make sure that the specified number of bytes actually exist. if ((valueStartPos + length ) > sequenceValue.length) { throw new ASN1Exception("There are not enough bytes in the value to " + "hold the indicated length of " + length); } // Copy the value in place byte[] value = new byte[length]; System.arraycopy(sequenceValue, valueStartPos, value, 0, length); // Create the new ASN.1 element and add it to the list ASN1Element element = new ASN1Element(type, value); elementList.add(element); // Reset the start position for the next element in the list startPos = valueStartPos + length; } // Convert the list elements to an array and return it ASN1Element[] elements = new ASN1Element[elementList.size()]; elementList.toArray(elements); return elements; } /** * Decodes the provided byte array into a set of the specified number of ASN.1 * elements. * * @param sequenceValue The set of encoded ASN.1 elements. * @param numElements The number of elements to extract. * * @return The ASN.1 elements that were decoded from the provided byte array. * * @throws ASN1Exception If the provided byte array cannot be decoded into a * set of ASN.1 elements. */ public static ASN1Element[] decodeSequenceElements(byte[] sequenceValue, int numElements) throws ASN1Exception { ASN1Element[] elements = new ASN1Element[numElements]; int startPos = 0; int elementNum = 0; while ((elementNum < numElements) && (startPos < sequenceValue.length)) { // The first byte is going to be the type. Make sure it's not a // multibyte type. byte type = sequenceValue[startPos]; if ((type & 0x1F) == 0x1F) { throw new ASN1Exception("Multibyte type detected (not supported in " + "this package)"); } // The length starts at the second position, but may extend beyond that byte firstLengthByte = sequenceValue[startPos+1]; int length = 0; int valueStartPos = startPos+2; if ((firstLengthByte & 0x7F) == firstLengthByte) { length = firstLengthByte; } else if ((firstLengthByte & 0x7F) == 0x00) { length = 128; } else { // There are multiple bytes in the length. Figure out how many. int numLengthBytes = (firstLengthByte & 0x7F); length = 0x00000000; switch (numLengthBytes) { case 1: length |= (0x000000FF & sequenceValue[startPos+2]); valueStartPos++; break; case 2: length |= ((0x000000FF & sequenceValue[startPos+2]) << 8) | (0x000000FF & sequenceValue[startPos+3]); valueStartPos += 2; break; case 3: length |= ((0x000000FF & sequenceValue[startPos+2]) << 16) | ((0x000000FF & sequenceValue[startPos+3]) << 8) | (0x000000FF & sequenceValue[startPos+4]); valueStartPos += 3; break; case 4: length |= ((0x000000FF & sequenceValue[startPos+2]) << 24) | ((0x000000FF & sequenceValue[startPos+3]) << 16) | ((0x000000FF & sequenceValue[startPos+4]) << 8) | (0x000000FF & sequenceValue[startPos+5]); valueStartPos += 4; break; default: throw new ASN1Exception("Specified length cannot be " + "represented as a Java int"); } } // Make sure that the specified number of bytes actually exist. if ((valueStartPos + length ) > sequenceValue.length) { throw new ASN1Exception("There are not enough bytes in the value to " + "hold the indicated length of " + length); } // Copy the value in place byte[] value = new byte[length]; System.arraycopy(sequenceValue, valueStartPos, value, 0, length); // Create the new ASN.1 element and add it to the list elements[elementNum++] = new ASN1Element(type, value); // Reset the start position for the next element in the list startPos = valueStartPos + length; } // Return the element array. return elements; } /** * Retrieves a string representation of this ASN.1 sequence. It will * recursively display string representations for each of the elements. * * @param indent The number of spaces to indent the information in the * returned string. * * @return A string representation of this ASN.1 sequence. */ @Override() public String toString(int indent) { String indentStr = ""; for (int i=0; i < indent; i++) { indentStr += " "; } String elementsStr = ""; for (int i=0; i < elements.length; i++) { ASN1Element element = elements[i]; elementsStr += indentStr + " Element " + i + eol + element.toString(indent+2); } return indentStr + "Type: " + type + eol + byteArrayToString(new byte[] { type }, 2+indent) + eol + indentStr + "Length: " + value.length + eol + byteArrayToString(encodeLength(value.length), 2+indent) + eol + indentStr + "Value: " + new String(value) + eol + byteArrayToString(value, 2+indent) + eol + "Elements: " + eol + elementsStr; } }