/* * 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.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.net.Socket; /** * This class provides a mechanism for reading ASN.1 elements from an input * stream. It is not a true <CODE>java.io.Reader</CODE>, but it should be good * enough for most purposes. * * * @author Neil A. Wilson */ public class ASN1Reader { /** * The size of the buffer that will be used for reading in data. */ public static final int BUFFER_SIZE = 4 * 1024; // The input stream from which the ASN.1 elements will be read. InputStream inputStream; // The buffer that will be used to read data from the input stream. byte[] defaultBuffer; // The buffer that will be used to hold a temporary copy of buffer data. byte[] tmpBuffer; // The buffer that will be used to read additional information from the // input stream when the current data available is not enough. byte[] readMoreBuffer; // The buffer that will be used to hold data that has been read in but is not // yet needed (e.g., when multiple ASN.1 elements are sent at the same time). byte[] overflowBuffer; // The socket that provided the input stream from which the data is being // read. Socket socket; /** * Creates a new ASN.1 reader to read elements from the specified input * stream. * * @param inputStream The input stream from which ASN.1 elements are to be * read. */ public ASN1Reader(InputStream inputStream) { this.defaultBuffer = new byte[BUFFER_SIZE]; this.tmpBuffer = new byte[BUFFER_SIZE]; this.readMoreBuffer = new byte[BUFFER_SIZE]; this.inputStream = inputStream; overflowBuffer = ASN1Element.EMPTY_BYTES; socket = null; } /** * Creates a new ASN.1 reader to read elements from the input stream of the * provided socket. * * @param socket The network socket from which the ASN.1 elements are to be * read. * * @throws IOException If the socket's input stream cannot be obtained. */ public ASN1Reader(Socket socket) throws IOException { this.defaultBuffer = new byte[BUFFER_SIZE]; this.tmpBuffer = new byte[BUFFER_SIZE]; this.readMoreBuffer = new byte[BUFFER_SIZE]; this.inputStream = socket.getInputStream(); this.socket = socket; overflowBuffer = ASN1Element.EMPTY_BYTES; } /** * Retrieves the number of bytes that can be read without blocking. * * @return The number of bytes that can be read without blocking. * * @throws IOException If a problem occurs while trying to make the * determination. */ public int available() throws IOException { return inputStream.available(); } /** * Reads an ASN.1 element from the provided input stream. * * @return The ASN.1 element that was read, or <CODE>null</CODE> if there was * no more data to read. * * @throws ASN1Exception If the data read from the input stream does not * contain a valid ASN.1 element. * * @throws IOException If there is a problem reading the information from * the input stream. */ public ASN1Element readElement() throws ASN1Exception, IOException { try { // Create a buffer to hold the information we will read in. byte[] buffer; int bytesRead; // First, see if we have information in the overflow buffer. If so, then // use it. If not, then read from the input stream. if (overflowBuffer.length > 0) { buffer = overflowBuffer; bytesRead = overflowBuffer.length; overflowBuffer = ASN1Element.EMPTY_BYTES; } else { buffer = defaultBuffer; bytesRead = inputStream.read(buffer); if (bytesRead < 0) { return null; } } // Make sure that there are at least two bytes to read from the stream. // If not, then we can't have a valid ASN.1 element. if (bytesRead < 2) { byte[] moreData = readMore(); byte[] newBuffer = new byte[bytesRead + moreData.length]; System.arraycopy(buffer, 0, newBuffer, 0, bytesRead); System.arraycopy(moreData, 0, newBuffer, bytesRead, moreData.length); buffer = newBuffer; bytesRead = newBuffer.length; } // Read the type. Make sure that it is only a single byte byte type = buffer[0]; if ((type & 0x1F) == 0x1F) { throw new ASN1Exception("Multibyte type detected (not supported in " + "this package)"); } // Read the first byte of the length and see if there should be more int length = buffer[1]; int valueStartPos = 2; if (valueStartPos >= bytesRead) { byte[] moreData = readMore(); byte[] newBuffer = new byte[bytesRead + moreData.length]; System.arraycopy(buffer, 0, newBuffer, 0, bytesRead); System.arraycopy(moreData, 0, newBuffer, bytesRead, moreData.length); buffer = newBuffer; bytesRead = newBuffer.length; } if ((length & 0x7F) == 0x00) { length = 128; } else if ((length & 0x7F) != length) { // This is a multibyte length. Find the actual length int numLengthBytes = (length & 0x7F); valueStartPos += numLengthBytes; if (valueStartPos >= bytesRead) { byte[] moreData = readMore(); byte[] newBuffer = new byte[bytesRead + moreData.length]; System.arraycopy(buffer, 0, newBuffer, 0, bytesRead); System.arraycopy(moreData, 0, newBuffer, bytesRead, moreData.length); buffer = newBuffer; bytesRead = newBuffer.length; } length = 0x00000000; switch (numLengthBytes) { case 1: length |= (0x000000FF & buffer[2]); break; case 2: length |= ((0x000000FF & buffer[2]) << 8) | (0x000000FF & buffer[3]); break; case 3: length |= ((0x000000FF & buffer[2]) << 16) | ((0x000000FF & buffer[3]) << 8) | (0x000000FF & buffer[4]); break; case 4: length |= ((0x000000FF & buffer[2]) << 24) | ((0x000000FF & buffer[3]) << 16) | ((0x000000FF & buffer[4]) << 8) | (0x000000FF & buffer[5]); break; default: throw new ASN1Exception("Length cannot be represented as " + "a Java int"); } } // See how much information we have available relative to the amount that // we need. if ((valueStartPos + length) == bytesRead) { overflowBuffer = ASN1Element.EMPTY_BYTES; } else if ((valueStartPos + length) < bytesRead) { // Oh, no, I've read too much. overflowBuffer = new byte[bytesRead - (valueStartPos + length)]; System.arraycopy(buffer, (valueStartPos+length), overflowBuffer, 0, overflowBuffer.length); byte[] tempBuffer = new byte[valueStartPos + length]; System.arraycopy(buffer, 0, tempBuffer, 0, (valueStartPos+length)); buffer = tempBuffer; bytesRead = buffer.length; } else if ((valueStartPos + length) > bytesRead) { // I haven't read enough. while ((valueStartPos + length) > bytesRead) { byte[] tempReadBuffer = this.tmpBuffer; int tempBytesRead = inputStream.read(tempReadBuffer); byte[] tempBuffer = new byte[bytesRead+tempBytesRead]; System.arraycopy(buffer, 0, tempBuffer, 0, bytesRead); System.arraycopy(tempReadBuffer, 0, tempBuffer, bytesRead, tempBytesRead); buffer = tempBuffer; bytesRead += tempBytesRead; } // When reading in more data, we could have read too much, so stuff that // off into the overflow buffer. if ((valueStartPos + length) < bytesRead) { overflowBuffer = new byte[bytesRead - (valueStartPos + length)]; System.arraycopy(buffer, (valueStartPos+length), overflowBuffer, 0, overflowBuffer.length); byte[] tempBuffer = new byte[valueStartPos + length]; System.arraycopy(buffer, 0, tempBuffer, 0, (valueStartPos+length)); buffer = tempBuffer; bytesRead = buffer.length; } else { overflowBuffer = ASN1Element.EMPTY_BYTES; } } // Get the value of the ASN.1 element from the buffer. byte[] value = new byte[length]; System.arraycopy(buffer, valueStartPos, value, 0, value.length); ASN1Element element = new ASN1Element(type, value); return element; } catch (Exception e) { if (e instanceof ASN1Exception) { throw (ASN1Exception) e; } else if (e instanceof IOException) { throw (IOException) e; } else { throw new ASN1Exception("Error while attempting to read an ASN.1 " + "element: " + e, e); } } } /** * Reads an ASN.1 element from the provided input stream, waiting a maximum of * <CODE>timeout</CODE> milliseconds for the response. If no response has * been received during that time, then an <CODE>InterruptedIOException</CODE> * will be thrown. Note that this is only applicable to ASN.1 readers that * were created using the version of the constructor that accepts a socket as * the argument. If no socket has been provided, then no timeout will be * used. * * @param timeout The maximum length of time in milliseconds that the read * operation will be allowed to block while waiting for * information. * * * @return The ASN.1 element that was read. * * @throws ASN1Exception If the data read from the input stream does not * contain a valid ASN.1 element. * * @throws InterruptedIOException If the read was interrupted because the * timeout was reached. * * @throws IOException If there is a problem reading the information from * the input stream or setting a timeout on the socket. */ public ASN1Element readElement(int timeout) throws ASN1Exception, InterruptedIOException, IOException { int originalTimeout = 0; if (socket != null) { originalTimeout = socket.getSoTimeout(); socket.setSoTimeout(timeout); } ASN1Element returnElement = readElement(); if (socket != null) { socket.setSoTimeout(originalTimeout); } return returnElement; } /** * Reads additional information from the input stream and returns it in a * byte array of exactly the right size. * * @return The byte array containing the additional data read. * * @throws IOException If a problem occurs while reading from the input * stream. */ private byte[] readMore() throws IOException { int moreBytesRead = inputStream.read(readMoreBuffer); byte[] returnArray = new byte[moreBytesRead]; System.arraycopy(readMoreBuffer, 0, returnArray, 0, moreBytesRead); return returnArray; } /** * Closes this ASN.1 reader. * * @throws IOException If a problem occurs while closing the ASN.1 reader. */ public void close() throws IOException { // No implementation necessary. } }