ASN1ByteChannelReader.java revision fc2dab2c4a694677a94470b728850adada8e4369
/*
* 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 2011-2012 ForgeRock AS.
*/
package org.opends.server.protocols.asn1;
import org.opends.server.types.ByteSequenceReader;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.types.ByteString;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.IllegalBlockingModeException;
import java.io.IOException;
import java.io.InputStream;
/**
* This class is for reading ASN.1 elements from a readable byte
* channel. It will handle all partial element reads from the channel
* and save any unread ASN.1 elements if required. All data read from
* the channel will be ready to be read as ASN.1 elements no matter
* how many times the channel is read. However, to minimize the the
* amount of memory used by this reader, the client should read ASN.1
* elements as soon as they are read off the channel.
* <p>
* {@code ASN1ByteChannelReader}s are created using the factory
* methods in {@link ASN1}.
* <p>
* The client should use this class in the following manner:
*<p>
* When NIO signals new data is available in the channel, the client
* should call {@link #processChannelData()}.
*<p>
* If bytes are read from the channel, the client should call
* {@link #elementAvailable()} to see if a complete element is ready to
* be read. However, if no data is actually read, the client should
* wait for the next signal and try again.
* <p>
* As long as a complete element is ready, the client should read the
* appropriate ASN.1 element(s). Once no more complete elements are
* available, the client should call {@link #processChannelData()}
* again to read more data (if available).
* <p>
* <b>NOTE:</b> Since this reader is non blocking, reading ASN.1
* elements before making sure they are ready could result in
* {@link IllegalBlockingModeException}s being thrown while reading
* ASN.1 elements. Once an exception is thrown, the state of the reader
* is no longer stable and can not be used again.
*/
public final class ASN1ByteChannelReader implements ASN1Reader
{
// The byte channel to read from.
private final ReadableByteChannel byteChannel;
// The wrapped ASN.1 InputStream reader.
private final ASN1InputStreamReader reader;
// The NIO ByteStringBuilder that stores any immediate data read off
// the channel.
private final ByteBuffer byteBuffer;
// The save buffer used to store any unprocessed data waiting
// to be read as ASN.1 elements. (Usually due to reading
// incomplete elements from the channel).
private final ByteStringBuilder saveBuffer;
// The save buffer reader.
private final ByteSequenceReader saveBufferReader;
/**
* An adaptor class for reading from a save buffer and the NIO byte buffer
* sequentially using the InputStream interface.
*
* Since the NIO byte buffer is re-used when reading off the channel, any
* unused data will be appended to the save buffer before reading off the
* channel again. This reader will always read the save buffer first before
* the actual NIO byte buffer to ensure bytes are read in the same order
* as they are received.
*
* The read methods of this stream will throw an IllegalBlockingModeException
* if invoked when there are no data to read from the save buffer or the
* channel buffer.
*
* The stream will not support the mark or reset methods.
*/
private final class CombinedBufferInputStream extends InputStream
{
/**
* {@inheritDoc}
*/
@Override
public int available()
{
// The number of available bytes is the sum of the save buffer
// and the last read data in the NIO ByteStringBuilder.
return saveBufferReader.remaining() + byteBuffer.remaining();
}
/**
* Reads the next byte of data from the save buffer or channel buffer.
* The value byte is returned as an int in the range 0 to 255.
* If no byte is available in the save buffer or channel buffer,
* IllegalBlockingModeException will be thrown.
*
* @return the next byte of data.
* @throws IllegalBlockingModeException if there are more bytes available.
*/
@Override
public int read()
{
if(saveBufferReader.remaining() > 0)
{
// Try saved buffer first
return 0xFF & saveBufferReader.get();
}
if(byteBuffer.remaining() > 0)
{
// Must still be on the channel buffer
return 0xFF & byteBuffer.get();
}
throw new IllegalBlockingModeException();
}
/**
* Reads up to len bytes of data from the save buffer or channel buffer
* into an array of bytes. An attempt is made to read as many as len bytes,
* but a smaller number may be read. The number of bytes actually read is
* returned as an integer.
*
* If b is null, a NullPointerException is thrown.
*
* If the length of b is zero, then no bytes are read and 0 is returned;
* otherwise, there is an attempt to read at least one byte. If no byte is
* available in the save buffer or channel buffer,
* IllegalBlockingModeException will be thrown; otherwise, at least one
* byte is read and stored into b.
*
* The first byte read is stored into element b[0], the next one into
* b[o1], and so on. The number of bytes read is, at most, equal to the
* length of b. Let k be the number of bytes actually read; these bytes
* will be stored in elements b[0] through b[k-1], leaving elements b[k]
* through b[b.length-1] unaffected.
*
* @return the total number of bytes read into the buffer.
* @throws IllegalBlockingModeException if there are more bytes available.
*/
@Override
public int read(byte[] b)
{
return read(b, 0, b.length);
}
/**
* Reads up to len bytes of data from the save buffer or channel buffer
* into an array of bytes. An attempt is made to read as many as len bytes,
* but a smaller number may be read. The number of bytes actually read is
* returned as an integer.
*
* If b is null, a NullPointerException is thrown.
*
* If off is negative, or len is negative, or off+len is greater than the
* length of the array b, then an IndexOutOfBoundsException is thrown.
*
* If len is zero, then no bytes are read and 0 is returned; otherwise,
* there is an attempt to read at least one byte. If no byte is available
* in the save buffer or channel buffer, IllegalBlockingModeException will
* be thrown; otherwise, at least one byte is read and stored into b.
*
* The first byte read is stored into element b[off], the next one into
* b[off+1], and so on. The number of bytes read is, at most, equal to len.
* Let k be the number of bytes actually read; these bytes will be stored
* in elements b[off] through b[off+k-1], leaving elements b[off+k]
* through b[off+len-1] unaffected.
*
* In every case, elements b[0] through b[off] and elements b[off+len]
* through b[b.length-1] are unaffected.
*
* @return the total number of bytes read into the buffer.
* @throws IllegalBlockingModeException if there are more bytes available.
*/
@Override
public int read(byte[] b, int off, int len)
{
if ((off < 0) || (len < 0) || (off + len > b.length))
{
throw new IndexOutOfBoundsException();
}
if(len == 0)
{
return 0;
}
int bytesCopied=0;
int getLen;
if(saveBufferReader.remaining() > 0)
{
// Copy out of the last saved buffer first
getLen = Math.min(saveBufferReader.remaining(), len);
saveBufferReader.get(b, off, getLen);
bytesCopied += getLen;
}
if(bytesCopied < len && byteBuffer.remaining() > 0)
{
// Copy out of the channel buffer if we haven't got
// everything we needed.
getLen = Math.min(byteBuffer.remaining(), len - bytesCopied);
byteBuffer.get(b, off + bytesCopied, getLen);
bytesCopied += getLen;
}
if(bytesCopied < len)
{
throw new IllegalBlockingModeException();
}
return bytesCopied;
}
/**
* {@inheritDoc}
*/
@Override
public long skip(long length)
{
int bytesSkipped=0;
int len;
if(saveBufferReader.remaining() > 0)
{
// Skip in the last saved buffer first
len = Math.min(saveBufferReader.remaining(), (int)length);
saveBufferReader.position(saveBufferReader.position() + len);
bytesSkipped += len;
}
if(bytesSkipped < length && byteBuffer.remaining() > 0)
{
//Skip in the channel buffer if we haven't skipped enough.
len = Math.min(byteBuffer.remaining(), (int)length - bytesSkipped);
byteBuffer.position(byteBuffer.position() + len);
bytesSkipped += len;
}
if(bytesSkipped < length)
{
throw new IllegalBlockingModeException();
}
return bytesSkipped;
}
}
/**
* Creates a new ASN.1 byte channel reader whose source is the
* provided readable byte channel, having a user defined buffer
* size, and user defined maximum BER element size.
*
* @param channel
* The readable byte channel to use.
* @param bufferSize
* The buffer size to use when reading from the channel.
* @param maxElementSize
* The max ASN.1 element size this reader will read.
*/
ASN1ByteChannelReader(ReadableByteChannel channel, int bufferSize,
int maxElementSize)
{
this.byteChannel = channel;
this.byteBuffer = ByteBuffer.allocate(bufferSize);
this.byteBuffer.flip();
this.saveBuffer = new ByteStringBuilder();
this.saveBufferReader = saveBuffer.asReader();
CombinedBufferInputStream bufferStream = new CombinedBufferInputStream();
this.reader = new ASN1InputStreamReader(bufferStream, maxElementSize);
}
/**
* Process any new data on the channel so they can be read as ASN.1
* elements. This method should only be called when there are no
* more complete elements in the reader. Calling this method when
* there are complete elements still in the reader will result in
* unnecessary memory allocations to store any unread data. This
* method will perform the following operations:
* <ul>
* <li>Clear the save buffer if everything was read.
* <li>Append any unread data from the NIO byte buffer to the save
* buffer.
* <li>Clear the NIO byte buffer and read from the channel.
* </ul>
*
* @return The number of bytes read from the channel or -1 if
* channel is closed.
* @throws IOException
* If an exception occurs while reading from the channel.
*/
public int processChannelData() throws IOException
{
// Clear the save buffer if we have read all of it
if (saveBufferReader.remaining() == 0)
{
saveBuffer.clear();
saveBufferReader.rewind();
}
// Append any unused data in the channel buffer to the save buffer
if (byteBuffer.remaining() > 0)
{
saveBuffer.append(byteBuffer, byteBuffer.remaining());
}
byteBuffer.clear();
try
{
int read = byteChannel.read(byteBuffer);
return read;
}
finally
{
// Make sure that the buffer is flipped even if the read fails in order to
// ensure that subsequent calls which query the remaining data return
// valid results.
byteBuffer.flip();
}
}
/**
* Determines if a complete ASN.1 element is ready to be read from
* channel.
*
* @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
{
return reader.elementAvailable();
}
/**
* Determines if the channel 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 reader.hasNextElement();
}
/**
* Returns {@code true} if this ASN.1 reader contains unread data.
*
* @return {@code true} if this ASN.1 reader contains unread data.
*/
public boolean hasRemainingData()
{
return (saveBufferReader.remaining() != 0) || (byteBuffer.remaining() != 0);
}
/**
* {@inheritDoc}
*/
public int peekLength() throws ASN1Exception {
return reader.peekLength();
}
/**
* {@inheritDoc}
*/
public byte peekType() throws ASN1Exception {
return reader.peekType();
}
/**
* {@inheritDoc}
*/
public boolean readBoolean() throws ASN1Exception {
return reader.readBoolean();
}
/**
* {@inheritDoc}
*/
public void readEndExplicitTag() throws ASN1Exception {
reader.readEndExplicitTag();
}
/**
* {@inheritDoc}
*/
public void readEndSequence() throws ASN1Exception {
reader.readEndSequence();
}
/**
* {@inheritDoc}
*/
public void readEndSet() throws ASN1Exception {
reader.readEndSet();
}
/**
* {@inheritDoc}
*/
public int readEnumerated() throws ASN1Exception {
return reader.readEnumerated();
}
/**
* {@inheritDoc}
*/
public long readInteger() throws ASN1Exception {
return reader.readInteger();
}
/**
* {@inheritDoc}
*/
public void readNull() throws ASN1Exception {
reader.readNull();
}
/**
* {@inheritDoc}
*/
public ByteString readOctetString() throws ASN1Exception {
return reader.readOctetString();
}
/**
* {@inheritDoc}
*/
public void readOctetString(ByteStringBuilder buffer) throws ASN1Exception {
reader.readOctetString(buffer);
}
/**
* {@inheritDoc}
*/
public String readOctetStringAsString() throws ASN1Exception {
return reader.readOctetStringAsString();
}
/**
* {@inheritDoc}
*/
public String readOctetStringAsString(String charSet) throws ASN1Exception {
return reader.readOctetStringAsString(charSet);
}
/**
* {@inheritDoc}
*/
public void readStartExplicitTag() throws ASN1Exception {
reader.readStartExplicitTag();
}
/**
* {@inheritDoc}
*/
public void readStartSequence() throws ASN1Exception {
reader.readStartSequence();
}
/**
* {@inheritDoc}
*/
public void readStartSet() throws ASN1Exception {
reader.readStartSet();
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
reader.close();
byteChannel.close();
}
/**
* {@inheritDoc}
*/
public void skipElement() throws ASN1Exception
{
reader.skipElement();
}
}