package com.hwlcn.ldap.asn1;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import com.hwlcn.ldap.util.ByteStringBuffer;
import com.hwlcn.ldap.util.DebugType;
import com.hwlcn.core.annotation.Mutable;
import com.hwlcn.core.annotation.ThreadSafety;
import com.hwlcn.ldap.util.ThreadSafetyLevel;
import static com.hwlcn.ldap.util.Debug.*;
@Mutable()
@ThreadSafety(level = ThreadSafetyLevel.NOT_THREADSAFE)
public final class ASN1Buffer
implements Serializable {
private static final int DEFAULT_MAX_BUFFER_SIZE = 1048576;
private static final byte[] MULTIBYTE_LENGTH_HEADER_PLUS_ONE =
{(byte) 0x81, (byte) 0x00};
private static final byte[] MULTIBYTE_LENGTH_HEADER_PLUS_TWO =
{(byte) 0x82, (byte) 0x00, (byte) 0x00};
private static final byte[] MULTIBYTE_LENGTH_HEADER_PLUS_THREE =
{(byte) 0x83, (byte) 0x00, (byte) 0x00, (byte) 0x00};
private static final byte[] MULTIBYTE_LENGTH_HEADER_PLUS_FOUR =
{(byte) 0x84, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
private static final long serialVersionUID = -4898230771376551562L;
private final AtomicBoolean zeroBufferOnClear;
private final ByteStringBuffer buffer;
private final int maxBufferSize;
public ASN1Buffer() {
this(DEFAULT_MAX_BUFFER_SIZE);
}
public ASN1Buffer(final int maxBufferSize) {
this.maxBufferSize = maxBufferSize;
buffer = new ByteStringBuffer();
zeroBufferOnClear = new AtomicBoolean(false);
}
public boolean zeroBufferOnClear() {
return zeroBufferOnClear.get();
}
public void setZeroBufferOnClear() {
zeroBufferOnClear.set(true);
}
public void clear() {
buffer.clear(zeroBufferOnClear.getAndSet(false));
if ((maxBufferSize > 0) && (buffer.capacity() > maxBufferSize)) {
buffer.setCapacity(maxBufferSize);
}
}
public int length() {
return buffer.length();
}
public void addElement(final ASN1Element element) {
element.encodeTo(buffer);
}
public void addBoolean(final boolean booleanValue) {
addBoolean(ASN1Constants.UNIVERSAL_BOOLEAN_TYPE, booleanValue);
}
public void addBoolean(final byte type, final boolean booleanValue) {
buffer.append(type);
buffer.append((byte) 0x01);
if (booleanValue) {
buffer.append((byte) 0xFF);
} else {
buffer.append((byte) 0x00);
}
}
public void addEnumerated(final int intValue) {
addInteger(ASN1Constants.UNIVERSAL_ENUMERATED_TYPE, intValue);
}
public void addEnumerated(final byte type, final int intValue) {
addInteger(type, intValue);
}
public void addInteger(final int intValue) {
addInteger(ASN1Constants.UNIVERSAL_INTEGER_TYPE, intValue);
}
public void addInteger(final byte type, final int intValue) {
buffer.append(type);
if (intValue < 0) {
if ((intValue & 0xFFFFFF80) == 0xFFFFFF80) {
buffer.append((byte) 0x01);
buffer.append((byte) (intValue & 0xFF));
} else if ((intValue & 0xFFFF8000) == 0xFFFF8000) {
buffer.append((byte) 0x02);
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
} else if ((intValue & 0xFF800000) == 0xFF800000) {
buffer.append((byte) 0x03);
buffer.append((byte) ((intValue >> 16) & 0xFF));
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
} else {
buffer.append((byte) 0x04);
buffer.append((byte) ((intValue >> 24) & 0xFF));
buffer.append((byte) ((intValue >> 16) & 0xFF));
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
}
} else {
if ((intValue & 0x0000007F) == intValue) {
buffer.append((byte) 0x01);
buffer.append((byte) (intValue & 0x7F));
} else if ((intValue & 0x00007FFF) == intValue) {
buffer.append((byte) 0x02);
buffer.append((byte) ((intValue >> 8) & 0x7F));
buffer.append((byte) (intValue & 0xFF));
} else if ((intValue & 0x007FFFFF) == intValue) {
buffer.append((byte) 0x03);
buffer.append((byte) ((intValue >> 16) & 0x7F));
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
} else {
buffer.append((byte) 0x04);
buffer.append((byte) ((intValue >> 24) & 0x7F));
buffer.append((byte) ((intValue >> 16) & 0xFF));
buffer.append((byte) ((intValue >> 8) & 0xFF));
buffer.append((byte) (intValue & 0xFF));
}
}
}
public void addInteger(final long longValue) {
addInteger(ASN1Constants.UNIVERSAL_INTEGER_TYPE, longValue);
}
public void addInteger(final byte type, final long longValue) {
buffer.append(type);
if (longValue < 0) {
if ((longValue & 0xFFFFFFFFFFFFFF80L) == 0xFFFFFFFFFFFFFF80L) {
buffer.append((byte) 0x01);
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L) {
buffer.append((byte) 0x02);
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L) {
buffer.append((byte) 0x03);
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L) {
buffer.append((byte) 0x04);
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L) {
buffer.append((byte) 0x05);
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L) {
buffer.append((byte) 0x06);
buffer.append((byte) ((longValue >> 40) & 0xFFL));
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L) {
buffer.append((byte) 0x07);
buffer.append((byte) ((longValue >> 48) & 0xFFL));
buffer.append((byte) ((longValue >> 40) & 0xFFL));
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else {
buffer.append((byte) 0x08);
buffer.append((byte) ((longValue >> 56) & 0xFFL));
buffer.append((byte) ((longValue >> 48) & 0xFFL));
buffer.append((byte) ((longValue >> 40) & 0xFFL));
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
}
} else {
if ((longValue & 0x000000000000007FL) == longValue) {
buffer.append((byte) 0x01);
buffer.append((byte) (longValue & 0x7FL));
} else if ((longValue & 0x0000000000007FFFL) == longValue) {
buffer.append((byte) 0x02);
buffer.append((byte) ((longValue >> 8) & 0x7FL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0x00000000007FFFFFL) == longValue) {
buffer.append((byte) 0x03);
buffer.append((byte) ((longValue >> 16) & 0x7FL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0x000000007FFFFFFFL) == longValue) {
buffer.append((byte) 0x04);
buffer.append((byte) ((longValue >> 24) & 0x7FL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0x0000007FFFFFFFFFL) == longValue) {
buffer.append((byte) 0x05);
buffer.append((byte) ((longValue >> 32) & 0x7FL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0x00007FFFFFFFFFFFL) == longValue) {
buffer.append((byte) 0x06);
buffer.append((byte) ((longValue >> 40) & 0x7FL));
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else if ((longValue & 0x007FFFFFFFFFFFFFL) == longValue) {
buffer.append((byte) 0x07);
buffer.append((byte) ((longValue >> 48) & 0x7FL));
buffer.append((byte) ((longValue >> 40) & 0xFFL));
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
} else {
buffer.append((byte) 0x08);
buffer.append((byte) ((longValue >> 56) & 0x7FL));
buffer.append((byte) ((longValue >> 48) & 0xFFL));
buffer.append((byte) ((longValue >> 40) & 0xFFL));
buffer.append((byte) ((longValue >> 32) & 0xFFL));
buffer.append((byte) ((longValue >> 24) & 0xFFL));
buffer.append((byte) ((longValue >> 16) & 0xFFL));
buffer.append((byte) ((longValue >> 8) & 0xFFL));
buffer.append((byte) (longValue & 0xFFL));
}
}
}
public void addNull() {
addNull(ASN1Constants.UNIVERSAL_NULL_TYPE);
}
public void addNull(final byte type) {
buffer.append(type);
buffer.append((byte) 0x00);
}
public void addOctetString() {
addOctetString(ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE);
}
public void addOctetString(final byte type) {
buffer.append(type);
buffer.append((byte) 0x00);
}
public void addOctetString(final byte[] value) {
addOctetString(ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE, value);
}
public void addOctetString(final CharSequence value) {
if (value == null) {
addOctetString(ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE);
} else {
addOctetString(ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE,
value.toString());
}
}
public void addOctetString(final String value) {
addOctetString(ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE, value);
}
public void addOctetString(final byte type, final byte[] value) {
buffer.append(type);
if (value == null) {
buffer.append((byte) 0x00);
} else {
ASN1Element.encodeLengthTo(value.length, buffer);
buffer.append(value);
}
}
public void addOctetString(final byte type, final CharSequence value) {
if (value == null) {
addOctetString(type);
} else {
addOctetString(type, value.toString());
}
}
public void addOctetString(final byte type, final String value) {
buffer.append(type);
if (value == null) {
buffer.append((byte) 0x00);
} else {
final int lengthStartPos = buffer.length();
ASN1Element.encodeLengthTo(value.length(), buffer);
final int valueStartPos = buffer.length();
buffer.append(value);
if (buffer.length() != (valueStartPos + value.length())) {
final byte[] valueBytes = new byte[buffer.length() - valueStartPos];
System.arraycopy(buffer.getBackingArray(), valueStartPos, valueBytes, 0,
valueBytes.length);
buffer.setLength(lengthStartPos);
ASN1Element.encodeLengthTo(valueBytes.length, buffer);
buffer.append(valueBytes);
}
}
}
public ASN1BufferSequence beginSequence() {
return beginSequence(ASN1Constants.UNIVERSAL_SEQUENCE_TYPE);
}
public ASN1BufferSequence beginSequence(final byte type) {
buffer.append(type);
return new ASN1BufferSequence(this);
}
public ASN1BufferSet beginSet() {
return beginSet(ASN1Constants.UNIVERSAL_SET_TYPE);
}
public ASN1BufferSet beginSet(final byte type) {
buffer.append(type);
return new ASN1BufferSet(this);
}
void endSequenceOrSet(final int valueStartPos) {
final int length = buffer.length() - valueStartPos;
if (length == 0) {
buffer.append((byte) 0x00);
return;
}
if ((length & 0x7F) == length) {
buffer.insert(valueStartPos, (byte) length);
} else if ((length & 0xFF) == length) {
buffer.insert(valueStartPos, MULTIBYTE_LENGTH_HEADER_PLUS_ONE);
final byte[] backingArray = buffer.getBackingArray();
backingArray[valueStartPos + 1] = (byte) (length & 0xFF);
} else if ((length & 0xFFFF) == length) {
buffer.insert(valueStartPos, MULTIBYTE_LENGTH_HEADER_PLUS_TWO);
final byte[] backingArray = buffer.getBackingArray();
backingArray[valueStartPos + 1] = (byte) ((length >> 8) & 0xFF);
backingArray[valueStartPos + 2] = (byte) (length & 0xFF);
} else if ((length & 0xFFFFFF) == length) {
buffer.insert(valueStartPos, MULTIBYTE_LENGTH_HEADER_PLUS_THREE);
final byte[] backingArray = buffer.getBackingArray();
backingArray[valueStartPos + 1] = (byte) ((length >> 16) & 0xFF);
backingArray[valueStartPos + 2] = (byte) ((length >> 8) & 0xFF);
backingArray[valueStartPos + 3] = (byte) (length & 0xFF);
} else {
buffer.insert(valueStartPos, MULTIBYTE_LENGTH_HEADER_PLUS_FOUR);
final byte[] backingArray = buffer.getBackingArray();
backingArray[valueStartPos + 1] = (byte) ((length >> 24) & 0xFF);
backingArray[valueStartPos + 2] = (byte) ((length >> 16) & 0xFF);
backingArray[valueStartPos + 3] = (byte) ((length >> 8) & 0xFF);
backingArray[valueStartPos + 4] = (byte) (length & 0xFF);
}
}
public void writeTo(final OutputStream outputStream)
throws IOException {
if (debugEnabled(DebugType.ASN1)) {
debugASN1Write(this);
}
buffer.write(outputStream);
}
public byte[] toByteArray() {
return buffer.toByteArray();
}
public ByteBuffer asByteBuffer() {
return ByteBuffer.wrap(buffer.getBackingArray(), 0, buffer.length());
}
}