/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2012 ForgeRock AS
*/
package org.opends.server.protocols.asn1;
import static org.opends.messages.ProtocolMessages.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.protocols.ldap.LDAPConstants.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import org.opends.messages.Message;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.util.SizeLimitInputStream;
/**
* An ASN1Reader that reads from an input stream.
*/
final class ASN1InputStreamReader implements ASN1Reader
{
private static final DebugTracer TRACER = getTracer();
private int state = ELEMENT_READ_STATE_NEED_TYPE;
private byte peekType = 0;
private int peekLength = -1;
private int lengthBytesNeeded = 0;
private final int maxElementSize;
private InputStream in;
private final LinkedList<InputStream> streamStack;
private byte[] buffer;
/**
* Creates a new ASN1 reader whose source is the provided input
* stream and having a user defined maximum BER element size.
*
* @param stream
* The input stream to be read.
* @param maxElementSize
* The maximum BER element size, or <code>0</code> to
* indicate that there is no limit.
*/
ASN1InputStreamReader(InputStream stream, int maxElementSize)
{
this.in = stream;
this.streamStack = new LinkedList<InputStream>();
this.buffer = new byte[512];
this.maxElementSize = maxElementSize;
}
/**
* Determines if a complete ASN.1 element is ready to be read from the
* input stream without blocking.
*
* @return <code>true</code> if another complete element is available or
* <code>false</code> otherwise.
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
public boolean elementAvailable() throws ASN1Exception
{
try
{
if(state == ELEMENT_READ_STATE_NEED_TYPE &&
!needTypeState(false, false)) {
return false;
}
if(state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE &&
!needFirstLengthByteState(false, false)) {
return false;
}
if(state == ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES &&
!needAdditionalLengthBytesState(false, false)) {
return false;
}
return peekLength <= in.available();
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* Determines if the input stream contains at least one ASN.1 element to
* be read. This method will block until enough data is available on the
* stream to determine if an element is available.
*
* @return <code>true</code> if another element is available or
* <code>false</code> otherwise.
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
public boolean hasNextElement() throws ASN1Exception
{
try
{
if(!streamStack.isEmpty())
{
// We are reading a sub sequence. Return true as long as we haven't
// exausted the size limit for the sub sequence sub input stream.
SizeLimitInputStream subSq = (SizeLimitInputStream)in;
return (subSq.getSizeLimit() - subSq.getBytesRead() > 0);
}
return state != ELEMENT_READ_STATE_NEED_TYPE ||
needTypeState(true, false);
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* Internal helper method reading the ASN.1 type byte and transition to
* the next state if successful.
*
* @param isBlocking <code>true</code> to block if the type byte is not
* available or <code>false</code> to check for
* availability first.
* @param throwEofException <code>true</code> to throw an exception when
* an EOF is encountered or <code>false</code> to
* return false.
* @return <code>true</code> if the type byte was successfully read
* @throws IOException If an error occurs while reading from the stream.
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
private boolean needTypeState(boolean isBlocking, boolean throwEofException)
throws IOException, ASN1Exception
{
// Read just the type.
if(!isBlocking && in.available() <= 0)
{
return false;
}
int type = in.read();
if(type == -1)
{
if(throwEofException)
{
Message message =
ERR_ASN1_TRUCATED_TYPE_BYTE.get();
throw new ASN1Exception(message);
}
return false;
}
peekType = (byte)type;
state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
return true;
}
/**
* Internal helper method reading the first length bytes and transition to
* the next state if successful.
*
* @param isBlocking <code>true</code> to block if the type byte is not
* available or <code>false</code> to check for
* availability first.
* @param throwEofException <code>true</code> to throw an exception when
* an EOF is encountered or <code>false</code> to
* return false.
* @return <code>true</code> if the length bytes was successfully read
* @throws IOException If an error occurs while reading from the stream.
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
private boolean needFirstLengthByteState(boolean isBlocking,
boolean throwEofException)
throws IOException, ASN1Exception
{
if(!isBlocking && in.available() <= 0)
{
return false;
}
int readByte = in.read();
if(readByte == -1)
{
if(throwEofException)
{
Message message =
ERR_ASN1_TRUNCATED_LENGTH_BYTE.get();
throw new ASN1Exception(message);
}
return false;
}
peekLength = (readByte & 0x7F);
if (peekLength != readByte)
{
lengthBytesNeeded = peekLength;
if (lengthBytesNeeded > 4)
{
Message message =
ERR_ASN1_INVALID_NUM_LENGTH_BYTES.get(lengthBytesNeeded);
throw new ASN1Exception(message);
}
peekLength = 0x00;
if(!isBlocking && in.available() < lengthBytesNeeded)
{
state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
return false;
}
while(lengthBytesNeeded > 0)
{
readByte = in.read();
if(readByte == -1)
{
state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
if(throwEofException)
{
Message message =
ERR_ASN1_TRUNCATED_LENGTH_BYTES.get(lengthBytesNeeded);
throw new ASN1Exception(message);
}
return false;
}
peekLength = (peekLength << 8) | (readByte & 0xFF);
lengthBytesNeeded--;
}
}
// Make sure that the element is not larger than the maximum allowed
// message size.
if ((maxElementSize > 0) && (peekLength > maxElementSize))
{
Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
peekLength, maxElementSize);
throw new ASN1Exception(m);
}
state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
return true;
}
/**
* Internal helper method reading the additional ASN.1 length bytes and
* transition to the next state if successful.
*
* @param isBlocking <code>true</code> to block if the type byte is not
* available or <code>false</code> to check for
* availability first.
* @param throwEofException <code>true</code> to throw an exception when
* an EOF is encountered or <code>false</code> to
* return false.
* @return <code>true</code> if the length bytes was successfully read.
* @throws IOException If an error occurs while reading from the stream.
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
private boolean needAdditionalLengthBytesState(boolean isBlocking,
boolean throwEofException)
throws IOException, ASN1Exception
{
if(!isBlocking && in.available() < lengthBytesNeeded)
{
return false;
}
int readByte;
while(lengthBytesNeeded > 0)
{
readByte = in.read();
if(readByte == -1)
{
state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
if(throwEofException)
{
Message message =
ERR_ASN1_TRUNCATED_LENGTH_BYTES.get(lengthBytesNeeded);
throw new ASN1Exception(message);
}
return false;
}
peekLength = (peekLength << 8) | (readByte & 0xFF);
lengthBytesNeeded--;
}
// Make sure that the element is not larger than the maximum allowed
// message size.
if ((maxElementSize > 0) && (peekLength > maxElementSize))
{
Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
peekLength, maxElementSize);
throw new ASN1Exception(m);
}
state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
return true;
}
/**
* {@inheritDoc}
*/
public byte peekType() throws ASN1Exception
{
try
{
if(state == ELEMENT_READ_STATE_NEED_TYPE)
{
needTypeState(true, true);
}
return peekType;
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* {@inheritDoc}
*/
public int peekLength() throws ASN1Exception
{
peekType();
try
{
switch(state)
{
case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
needFirstLengthByteState(true, true);
break;
case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
needAdditionalLengthBytesState(true, true);
}
return peekLength;
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* {@inheritDoc}
*/
public boolean readBoolean() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
if (peekLength != 1)
{
Message message =
ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
throw new ASN1Exception(message);
}
try
{
int readByte = in.read();
if(readByte == -1)
{
Message message = ERR_ASN1_BOOLEAN_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)",
peekType, peekLength, String.valueOf(readByte != 0x00)));
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return readByte != 0x00;
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* {@inheritDoc}
*/
public int readEnumerated() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
if ((peekLength < 1) || (peekLength > 4))
{
Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
throw new ASN1Exception(message);
}
// From an implementation point of view, an enumerated value is
// equivalent to an integer.
return (int) readInteger();
}
/**
* {@inheritDoc}
*/
public long readInteger() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
if ((peekLength < 1) || (peekLength > 8))
{
Message message =
ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
throw new ASN1Exception(message);
}
try
{
if(peekLength > 4)
{
long longValue = 0;
for (int i=0; i < peekLength; i++)
{
int readByte = in.read();
if(readByte == -1)
{
Message message =
ERR_ASN1_INTEGER_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
if(i == 0 && ((byte)readByte) < 0)
{
longValue = 0xFFFFFFFFFFFFFFFFL;
}
longValue = (longValue << 8) | (readByte & 0xFF);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return longValue;
}
else
{
int intValue = 0;
for (int i=0; i < peekLength; i++)
{
int readByte = in.read();
if(readByte == -1)
{
Message message =
ERR_ASN1_INTEGER_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
if (i == 0 && ((byte)readByte) < 0)
{
intValue = 0xFFFFFFFF;
}
intValue = (intValue << 8) | (readByte & 0xFF);
}
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 INTEGER(type=0x%x, length=%d, " +
"value=%d)", peekType, peekLength, intValue));
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return intValue;
}
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* {@inheritDoc}
*/
public void readNull() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
// Make sure that the decoded length is exactly zero byte.
if (peekLength != 0)
{
Message message =
ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
throw new ASN1Exception(message);
}
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 NULL(type=0x%x, length=%d)",
peekType, peekLength));
}
state = ELEMENT_READ_STATE_NEED_TYPE;
}
/**
* {@inheritDoc}
*/
public ByteString readOctetString() throws ASN1Exception {
// Read the header if haven't done so already
peekLength();
if(peekLength == 0)
{
state = ELEMENT_READ_STATE_NEED_TYPE;
return ByteString.empty();
}
try
{
// Copy the value and construct the element to return.
byte[] value = new byte[peekLength];
int bytesNeeded = peekLength;
int bytesRead;
while(bytesNeeded > 0)
{
bytesRead = in.read(value, peekLength - bytesNeeded, bytesNeeded);
if(bytesRead < 0)
{
Message message =
ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
bytesNeeded -= bytesRead;
}
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 OCTETSTRING(type=0x%x, length=%d)",
peekType, peekLength));
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return ByteString.wrap(value);
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* {@inheritDoc}
*/
public String readOctetStringAsString() throws ASN1Exception
{
// We could cache the UTF-8 CharSet if performance proves to be an
// issue.
return readOctetStringAsString("UTF-8");
}
/**
* {@inheritDoc}
*/
public String readOctetStringAsString(String charSet) throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
if(peekLength == 0)
{
state = ELEMENT_READ_STATE_NEED_TYPE;
return "";
}
// Resize the temp buffer if needed
if(peekLength > buffer.length)
{
buffer = new byte[peekLength];
}
try
{
int bytesNeeded = peekLength;
int bytesRead;
while(bytesNeeded > 0)
{
bytesRead = in.read(buffer, peekLength - bytesNeeded, bytesNeeded);
if(bytesRead < 0)
{
Message message =
ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
bytesNeeded -= bytesRead;
}
state = ELEMENT_READ_STATE_NEED_TYPE;
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
String str;
try
{
str = new String(buffer, 0, peekLength, charSet);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
str = new String(buffer, 0, peekLength);
}
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 OCTETSTRING(type=0x%x, length=%d, " +
"value=%s)", peekType, peekLength, str));
}
return str;
}
/**
* {@inheritDoc}
*/
public void readOctetString(ByteStringBuilder buffer) throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
if(peekLength == 0)
{
state = ELEMENT_READ_STATE_NEED_TYPE;
return;
}
try
{
// Copy the value and construct the element to return.
int bytesNeeded = peekLength;
int bytesRead;
while(bytesNeeded > 0)
{
bytesRead = buffer.append(in, bytesNeeded);
if(bytesRead < 0)
{
Message message =
ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
bytesNeeded -= bytesRead;
}
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 OCTETSTRING(type=0x%x, length=%d)",
peekType, peekLength));
}
state = ELEMENT_READ_STATE_NEED_TYPE;
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* {@inheritDoc}
*/
public void readStartSequence() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
SizeLimitInputStream subStream =
new SizeLimitInputStream(in, peekLength);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("READ ASN.1 SEQUENCE(type=0x%x, length=%d)",
peekType, peekLength));
}
streamStack.addFirst(in);
in = subStream;
// Reset the state
state = ELEMENT_READ_STATE_NEED_TYPE;
}
/**
* {@inheritDoc}
*/
public void readStartExplicitTag() throws ASN1Exception
{
// From an implementation point of view, an explicit tag is equivalent to a
// sequence, as it is also a constructed type.
readStartSequence();
}
/**
* {@inheritDoc}
*/
public void readStartSet() throws ASN1Exception
{
// From an implementation point of view, a set is equivalent to a
// sequence.
readStartSequence();
}
/**
* {@inheritDoc}
*/
public void readEndSequence() throws ASN1Exception
{
if(streamStack.isEmpty())
{
Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
throw new ASN1Exception(message);
}
// Ignore all unused trailing components.
SizeLimitInputStream subSq = (SizeLimitInputStream)in;
if(subSq.getSizeLimit() - subSq.getBytesRead() > 0)
{
if(debugEnabled())
{
TRACER.debugWarning("Ignoring %d unused trailing bytes in " +
"ASN.1 SEQUENCE", subSq.getSizeLimit() - subSq.getBytesRead());
}
try
{
subSq.skip(subSq.getSizeLimit() - subSq.getBytesRead());
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
in = streamStack.removeFirst();
// Reset the state
state = ELEMENT_READ_STATE_NEED_TYPE;
}
/**
* {@inheritDoc}
*/
public void readEndExplicitTag() throws ASN1Exception
{
// From an implementation point of view, an explicit tag is equivalent to a
// sequence, as it is also a constructed type.
readEndSequence();
}
/**
* {@inheritDoc}
*/
public void readEndSet() throws ASN1Exception
{
// From an implementation point of view, a set is equivalent to a
// sequence.
readEndSequence();
}
/**
* {@inheritDoc}
*/
public void skipElement() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
try
{
long bytesSkipped = in.skip(peekLength);
if(bytesSkipped != peekLength)
{
Message message =
ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
}
catch(IOException ioe)
{
Message message =
ERR_ASN1_READ_ERROR.get(ioe.toString());
throw new ASN1Exception(message, ioe);
}
}
/**
* Closes this ASN.1 reader and the underlying stream.
*
* @throws IOException if an I/O error occurs
*/
public void close() throws IOException
{
// Calling close of SizeLimitInputStream should close the parent stream.
in.close();
streamStack.clear();
}
}