/*
* 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.server.protocols.asn1.ASN1Constants.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import org.opends.server.types.ByteSequence;
import org.opends.server.types.ByteStringBuilder;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.util.StaticUtils;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import org.opends.server.loggers.debug.DebugTracer;
/**
* An ASN1Writer implementation that outputs to an outputstream.
*/
final class ASN1OutputStreamWriter implements ASN1Writer
{
private static final DebugTracer TRACER = getTracer();
private final OutputStream rootStream;
private final ArrayList<ByteSequenceOutputStream> streamStack;
private final int maxInternalBufferSize;
private OutputStream out;
private int stackDepth;
/**
* Creates a new ASN.1 output stream reader.
*
* @param stream
* The underlying output stream.
* @param maxInternalBufferSize
* The threshold capacity beyond which internal cached buffers used
* for encoding and decoding protocol messages will be trimmed after
* use.
*/
ASN1OutputStreamWriter(OutputStream stream, int maxInternalBufferSize)
{
this.out = stream;
this.rootStream = stream;
this.streamStack = new ArrayList<ByteSequenceOutputStream>();
this.stackDepth = -1;
this.maxInternalBufferSize = maxInternalBufferSize;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeInteger(int intValue) throws IOException
{
return writeInteger(UNIVERSAL_INTEGER_TYPE, intValue);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeInteger(byte type, int intValue) throws IOException
{
out.write(type);
if ((intValue < 0 && ((intValue & 0xFFFFFF80) == 0xFFFFFF80)) ||
(intValue & 0x0000007F) == intValue)
{
writeLength(1);
out.write((byte) (intValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 1, intValue));
}
}
else if ((intValue < 0 && ((intValue & 0xFFFF8000) == 0xFFFF8000)) ||
(intValue & 0x00007FFF) == intValue)
{
writeLength(2);
out.write((byte) ((intValue >> 8) & 0xFF));
out.write((byte) (intValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 2, intValue));
}
}
else if ((intValue < 0 && ((intValue & 0xFF800000) == 0xFF800000)) ||
(intValue & 0x007FFFFF) == intValue)
{
writeLength(3);
out.write((byte) ((intValue >> 16) & 0xFF));
out.write((byte) ((intValue >> 8) & 0xFF));
out.write((byte) (intValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 3, intValue));
}
}
else
{
writeLength(4);
out.write((byte) ((intValue >> 24) & 0xFF));
out.write((byte) ((intValue >> 16) & 0xFF));
out.write((byte) ((intValue >> 8) & 0xFF));
out.write((byte) (intValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 4, intValue));
}
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeInteger(long longValue) throws IOException
{
return writeInteger(UNIVERSAL_INTEGER_TYPE, longValue);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeInteger(byte type, long longValue) throws IOException
{
out.write(type);
if ((longValue < 0 &&
((longValue & 0xFFFFFFFFFFFFFF80L) == 0xFFFFFFFFFFFFFF80L)) ||
(longValue & 0x000000000000007FL) == longValue)
{
writeLength(1);
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 1, longValue));
}
}
else if ((longValue < 0 &&
((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L)) ||
(longValue & 0x0000000000007FFFL) == longValue)
{
writeLength(2);
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 2, longValue));
}
}
else if ((longValue < 0 &&
((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L)) ||
(longValue & 0x00000000007FFFFFL) == longValue)
{
writeLength(3);
out.write((byte) ((longValue >> 16) & 0xFF));
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 3, longValue));
}
}
else if ((longValue < 0 &&
((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L)) ||
(longValue & 0x000000007FFFFFFFL) == longValue)
{
writeLength(4);
out.write((byte) ((longValue >> 24) & 0xFF));
out.write((byte) ((longValue >> 16) & 0xFF));
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 4, longValue));
}
}
else if ((longValue < 0 &&
((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L)) ||
(longValue & 0x0000007FFFFFFFFFL) == longValue)
{
writeLength(5);
out.write((byte) ((longValue >> 32) & 0xFF));
out.write((byte) ((longValue >> 24) & 0xFF));
out.write((byte) ((longValue >> 16) & 0xFF));
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 5, longValue));
}
}
else if ((longValue < 0 &&
((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L)) ||
(longValue & 0x00007FFFFFFFFFFFL) == longValue)
{
writeLength(6);
out.write((byte) ((longValue >> 40) & 0xFF));
out.write((byte) ((longValue >> 32) & 0xFF));
out.write((byte) ((longValue >> 24) & 0xFF));
out.write((byte) ((longValue >> 16) & 0xFF));
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 6, longValue));
}
}
else if ((longValue < 0 &&
((longValue & 0xFF80000000000000L) == 0xFF80000000000000L)) ||
(longValue & 0x007FFFFFFFFFFFFFL) == longValue)
{
writeLength(7);
out.write((byte) ((longValue >> 48) & 0xFF));
out.write((byte) ((longValue >> 40) & 0xFF));
out.write((byte) ((longValue >> 32) & 0xFF));
out.write((byte) ((longValue >> 24) & 0xFF));
out.write((byte) ((longValue >> 16) & 0xFF));
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 7, longValue));
}
}
else
{
writeLength(8);
out.write((byte) ((longValue >> 56) & 0xFF));
out.write((byte) ((longValue >> 48) & 0xFF));
out.write((byte) ((longValue >> 40) & 0xFF));
out.write((byte) ((longValue >> 32) & 0xFF));
out.write((byte) ((longValue >> 24) & 0xFF));
out.write((byte) ((longValue >> 16) & 0xFF));
out.write((byte) ((longValue >> 8) & 0xFF));
out.write((byte) (longValue & 0xFF));
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
type, 8, longValue));
}
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeEnumerated(int intValue) throws IOException {
return writeInteger(UNIVERSAL_ENUMERATED_TYPE, intValue);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeBoolean(boolean booleanValue) throws IOException
{
return writeBoolean(UNIVERSAL_BOOLEAN_TYPE, booleanValue);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeBoolean(byte type, boolean booleanValue)
throws IOException
{
out.write(type);
writeLength(1);
out.write(booleanValue ? BOOLEAN_VALUE_TRUE : BOOLEAN_VALUE_FALSE);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)",
type, 1, String.valueOf(booleanValue)));
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeNull(byte type) throws IOException
{
out.write(type);
writeLength(0);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 NULL(type=0x%x, length=%d)",
type, 0));
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeNull() throws IOException
{
return writeNull(UNIVERSAL_NULL_TYPE);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeOctetString(byte type, byte[] value,
int offset, int length)
throws IOException
{
out.write(type);
writeLength(length);
out.write(value, offset, length);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)",
type, length));
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeOctetString(byte[] value, int offset, int length)
throws IOException
{
return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value, offset, length);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeOctetString(byte type, String value) throws IOException
{
out.write(type);
if(value == null)
{
writeLength(0);
return this;
}
byte[] bytes = StaticUtils.getBytes(value);
writeLength(bytes.length);
out.write(bytes);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, " +
"value=%s)", type, bytes.length, value));
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeOctetString(String value) throws IOException
{
return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeOctetString(byte type, ByteSequence value)
throws IOException {
out.write(type);
writeLength(value.length());
value.copyTo(out);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)",
type, value.length()));
}
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeOctetString(ByteSequence value) throws IOException {
return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeStartSet() throws IOException
{
return writeStartSet(UNIVERSAL_SET_TYPE);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeStartSet(byte type) throws IOException
{
// From an implementation point of view, a set is equivalent to a
// sequence.
return writeStartSequence(type);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeStartSequence() throws IOException
{
return writeStartSequence(UNIVERSAL_SEQUENCE_TYPE);
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeStartSequence(byte type) throws IOException
{
// Write the type in current stream switch to next sub-stream
out.write(type);
// Increment the stack depth and get the sub-stream from the stack
++stackDepth;
// Make sure we have a cached sub-stream at this depth
if(stackDepth >= streamStack.size())
{
ByteSequenceOutputStream subStream = new ByteSequenceOutputStream(
new ByteStringBuilder(), maxInternalBufferSize);
streamStack.add(subStream);
out = subStream;
}
else
{
ByteSequenceOutputStream childStream = streamStack.get(stackDepth);
childStream.reset(); // Precaution.
out = childStream;
}
/*
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 START SEQUENCE(type=0x%x)",
type));
}
*/
return this;
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeEndSet() throws IOException
{
return writeEndSequence();
}
/**
* {@inheritDoc}
*/
public ASN1Writer writeEndSequence() throws IOException
{
if(stackDepth < 0)
{
throw new IOException();
}
ByteSequenceOutputStream childStream = streamStack.get(stackDepth);
// Decrement the stack depth and get the parent stream
--stackDepth;
OutputStream parentStream = stackDepth < 0 ? rootStream :
streamStack.get(stackDepth);
// Switch to parent stream and reset the sub-stream
out = parentStream;
// Write the length and contents of the sub-stream
writeLength(childStream.length());
childStream.writeTo(parentStream);
if(debugEnabled())
{
TRACER.debugProtocolElement(DebugLogLevel.VERBOSE,
String.format("WRITE ASN.1 END SEQUENCE(length=%d)",
childStream.length()));
}
childStream.reset();
return this;
}
/**
* {@inheritDoc}
*/
public void close() throws IOException
{
try
{
flush();
}
finally
{
try
{
rootStream.close();
}
finally
{
// Reset for next usage in case where the root stream is reusable (e.g.
// ByteStringBuilder).
stackDepth = -1;
out = rootStream;
}
}
}
/**
* {@inheritDoc}
*/
public void flush() throws IOException
{
while (stackDepth >= 0)
{
writeEndSequence();
}
rootStream.flush();
}
/**
* Writes the provided value for use as the length of an ASN.1 element.
*
* @param length The length to encode for use in an ASN.1 element.
* @throws IOException if an error occurs while writing.
*/
private void writeLength(int length) throws IOException
{
if (length < 128)
{
out.write((byte) length);
}
else if ((length & 0x000000FF) == length)
{
out.write((byte) 0x81);
out.write((byte) (length & 0xFF));
}
else if ((length & 0x0000FFFF) == length)
{
out.write((byte) 0x82);
out.write((byte) ((length >> 8) & 0xFF));
out.write((byte) (length & 0xFF));
}
else if ((length & 0x00FFFFFF) == length)
{
out.write((byte) 0x83);
out.write((byte) ((length >> 16) & 0xFF));
out.write((byte) ((length >> 8) & 0xFF));
out.write((byte) (length & 0xFF));
}
else
{
out.write((byte) 0x84);
out.write((byte) ((length >> 24) & 0xFF));
out.write((byte) ((length >> 16) & 0xFF));
out.write((byte) ((length >> 8) & 0xFF));
out.write((byte) (length & 0xFF));
}
}
}