/*
* 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.protocols.ldap.LDAPConstants.*;
import java.util.LinkedList;
import java.io.IOException;
import org.opends.messages.Message;
import org.opends.server.types.ByteSequenceReader;
import org.opends.server.types.ByteString;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.loggers.debug.DebugTracer;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
/**
* An ASN.1 reader that reads from a {@link ByteSequenceReader}.
*/
final class ASN1ByteSequenceReader 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 final int maxElementSize;
private ByteSequenceReader reader;
private final LinkedList<ByteSequenceReader> readerStack;
/**
* Creates a new ASN1 reader whose source is the provided byte
* sequence reader and having a user defined maximum BER element
* size.
*
* @param reader
* The byte sequence reader to be read.
* @param maxElementSize
* The maximum BER element size, or <code>0</code> to
* indicate that there is no limit.
*/
ASN1ByteSequenceReader(ByteSequenceReader reader, int maxElementSize)
{
this.reader = reader;
this.readerStack = new LinkedList<ByteSequenceReader>();
this.maxElementSize = maxElementSize;
}
/**
* {@inheritDoc}
*/
public byte peekType() throws ASN1Exception
{
if(state == ELEMENT_READ_STATE_NEED_TYPE)
{
// Read just the type.
if(reader.remaining() <= 0)
{
Message message =
ERR_ASN1_TRUCATED_TYPE_BYTE.get();
throw new ASN1Exception(message);
}
int type = reader.get();
peekType = (byte)type;
state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
}
return peekType;
}
/**
* {@inheritDoc}
*/
public int peekLength() throws ASN1Exception
{
peekType();
if(state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
{
needFirstLengthByteState(true);
}
return peekLength;
}
/**
* Determines if a complete ASN.1 element is waiting to be read from the
* byte sequence.
*
* @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
{
if(state == ELEMENT_READ_STATE_NEED_TYPE &&
!needTypeState(false)) {
return false;
}
if(state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE &&
!needFirstLengthByteState(false)) {
return false;
}
return peekLength <= reader.remaining();
}
/**
* Determines if the byte sequence contains at least one ASN.1 element to
* be read.
*
* @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
{
return state != ELEMENT_READ_STATE_NEED_TYPE || needTypeState(false);
}
/**
* Internal helper method reading the ASN.1 type byte and transition to
* the next state if successful.
*
* @param throwEofException <code>true</code> to throw an exception when
* the end of the sequence is encountered.
* @return <code>true</code> if the type byte was successfully read
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
private boolean needTypeState(boolean throwEofException)
throws ASN1Exception
{
// Read just the type.
if(reader.remaining() <= 0)
{
if(throwEofException)
{
Message message =
ERR_ASN1_TRUCATED_TYPE_BYTE.get();
throw new ASN1Exception(message);
}
return false;
}
int type = reader.get();
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 throwEofException <code>true</code> to throw an exception when
* the end of the sequence is encountered.
* @return <code>true</code> if the length bytes was successfully read
* @throws ASN1Exception If an error occurs while trying to decode
* an ASN1 element.
*/
private boolean needFirstLengthByteState(boolean throwEofException)
throws ASN1Exception
{
if(reader.remaining() <= 0)
{
if(throwEofException)
{
Message message =
ERR_ASN1_TRUNCATED_LENGTH_BYTE.get();
throw new ASN1Exception(message);
}
return false;
}
int readByte = reader.get();
peekLength = (readByte & 0x7F);
if (peekLength != readByte)
{
int lengthBytesNeeded = peekLength;
if (lengthBytesNeeded > 4)
{
Message message =
ERR_ASN1_INVALID_NUM_LENGTH_BYTES.get(lengthBytesNeeded);
throw new ASN1Exception(message);
}
peekLength = 0x00;
if(reader.remaining() < lengthBytesNeeded)
{
if(throwEofException)
{
Message message =
ERR_ASN1_TRUNCATED_LENGTH_BYTES.get(lengthBytesNeeded);
throw new ASN1Exception(message);
}
return false;
}
while(lengthBytesNeeded > 0)
{
readByte = reader.get();
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 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);
}
if(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_BOOLEAN_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
int readByte = reader.get();
state = ELEMENT_READ_STATE_NEED_TYPE;
return readByte != 0x00;
}
/**
* {@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);
}
if(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_INTEGER_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
if(peekLength > 4)
{
long longValue = 0;
for (int i=0; i < peekLength; i++)
{
int readByte = reader.get();
if (i == 0 && 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 = reader.get();
if (i == 0 && readByte < 0)
{
intValue = 0xFFFFFFFF;
}
intValue = (intValue << 8) | (readByte & 0xFF);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return intValue;
}
}
/**
* {@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);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
}
/**
* {@inheritDoc}
*/
public ByteString readOctetString() throws ASN1Exception {
// Read the header if haven't done so already
peekLength();
if(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return reader.getByteString(peekLength);
}
/**
* {@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(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
return reader.getString(peekLength);
}
/**
* {@inheritDoc}
*/
public void readOctetString(ByteStringBuilder buffer) throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
// Copy the value.
if(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
buffer.append(reader, peekLength);
state = ELEMENT_READ_STATE_NEED_TYPE;
}
/**
* {@inheritDoc}
*/
public void readStartSequence() throws ASN1Exception
{
// Read the header if haven't done so already
peekLength();
if(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_SEQUENCE_SET_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
ByteSequenceReader subByteString = reader.getByteSequence(peekLength)
.asReader();
readerStack.addFirst(reader);
reader = subByteString;
// 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 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(readerStack.isEmpty())
{
Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
throw new ASN1Exception(message);
}
if(reader.remaining() > 0 && debugEnabled())
{
TRACER.debugWarning("Ignoring %d unused trailing bytes in " +
"ASN.1 SEQUENCE", reader.remaining());
}
reader = readerStack.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();
if(reader.remaining() < peekLength)
{
Message message =
ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength);
throw new ASN1Exception(message);
}
state = ELEMENT_READ_STATE_NEED_TYPE;
reader.skip(peekLength);
}
/**
* {@inheritDoc}
*/
public void close() throws IOException
{
readerStack.clear();
}
}