package org.apache.haox.asn1.type;
import org.apache.haox.asn1.EncodingOption;
import org.apache.haox.asn1.LimitedByteBuffer;
import org.apache.haox.asn1.TagClass;
import org.apache.haox.asn1.TaggingOption;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
public abstract class AbstractAsn1Type<T> implements Asn1Type {
private TagClass tagClass = TagClass.UNKNOWN;
private int tagNo = -1;
private int tagFlags = -1;
protected EncodingOption encodingOption = EncodingOption.UNKNOWN;
private int encodingLen = -1;
private T value;
public AbstractAsn1Type(TagClass tagClass, int tagNo) {
this(tagClass, tagNo, null);
}
public AbstractAsn1Type(int tagFlags, int tagNo) {
this(tagFlags, tagNo, null);
}
public AbstractAsn1Type(int tagFlags, int tagNo, T value) {
this(TagClass.fromTagFlags(tagFlags), tagNo, value);
setTagFlags(tagFlags);
}
public AbstractAsn1Type(TagClass tagClass, int tagNo, T value) {
this.tagClass = tagClass;
this.tagNo = tagNo;
this.value = value;
}
public void setEncodingOption(EncodingOption encodingOption) {
this.encodingOption = encodingOption;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
protected TagClass tagClass() {
return tagClass;
}
@Override
public int tagNo() {
return tagNo;
}
protected void setTagFlags(int tagFlags) {
this.tagFlags = tagFlags & 0xe0;
}
protected void setTagNo(int tagNo) {
this.tagNo = tagNo;
}
@Override
public byte[] encode() {
ByteBuffer byteBuffer = ByteBuffer.allocate(encodingLength());
encode(byteBuffer);
byteBuffer.flip();
return byteBuffer.array();
}
@Override
public void encode(ByteBuffer buffer) {
encodeTag(buffer, tagFlags(), tagNo());
encodeLength(buffer, encodingBodyLength());
encodeBody(buffer);
}
protected void encodeBody(ByteBuffer buffer) { }
@Override
public void decode(byte[] content) throws IOException {
decode(new LimitedByteBuffer(content));
}
@Override
public void decode(ByteBuffer content) throws IOException {
decode(new LimitedByteBuffer(content));
}
@Override
public int tagFlags() {
if (tagFlags == -1) {
int flags = tagClass.getValue();
if (isConstructed()) flags |= EncodingOption.CONSTRUCTED_FLAG;
return flags;
}
return tagFlags;
}
@Override
public int encodingLength() {
if (encodingLen == -1) {
int bodyLen = encodingBodyLength();
encodingLen = lengthOfTagLength(tagNo()) + lengthOfBodyLength(bodyLen) + bodyLen;
}
return encodingLen;
}
public boolean isConstructed() {
if (tagFlags != -1) {
return (tagFlags & EncodingOption.CONSTRUCTED_FLAG) != 0;
} else {
return false;
}
}
public boolean isUniversal() {
return tagClass.isUniversal();
}
public boolean isAppSpecific() {
return tagClass.isAppSpecific();
}
public boolean isContextSpecific() {
return tagClass.isContextSpecific();
}
public boolean isTagged() {
return tagClass.isTagged();
}
public boolean isSimple() {
return isUniversal() && Asn1Simple.isSimple(tagNo);
}
public boolean isCollection() {
return isUniversal() && Asn1Collection.isCollection(tagNo);
}
protected abstract int encodingBodyLength();
protected void decode(LimitedByteBuffer content) throws IOException {
int tag = readTag(content);
int tagNo = readTagNo(content, tag);
int length = readLength(content);
decode(tag, tagNo, new LimitedByteBuffer(content, length));
}
public void decode(int tagFlags, int tagNo, LimitedByteBuffer content) throws IOException {
if (this.tagClass != TagClass.UNKNOWN && this.tagClass != TagClass.fromTagFlags(tagFlags)) {
throw new IOException("Unexpected tagFlags " + tagFlags + ", expecting " + this.tagClass);
}
if (this.tagNo != -1 && this.tagNo != tagNo) {
throw new IOException("Unexpected tagNo " + tagNo + ", expecting " + this.tagNo);
}
this.tagClass = TagClass.fromTagFlags(tagFlags);
this.tagFlags = tagFlags;
this.tagNo = tagNo;
decodeBody(content);
}
protected abstract void decodeBody(LimitedByteBuffer content) throws IOException;
protected int taggedEncodingLength(TaggingOption taggingOption) {
int taggingTagNo = taggingOption.getTagNo();
int taggingBodyLen = taggingOption.isImplicit() ? encodingBodyLength() : encodingLength();
int taggingEncodingLen = lengthOfTagLength(taggingTagNo) + lengthOfBodyLength(taggingBodyLen) + taggingBodyLen;
return taggingEncodingLen;
}
public byte[] taggedEncode(TaggingOption taggingOption) {
ByteBuffer byteBuffer = ByteBuffer.allocate(taggedEncodingLength(taggingOption));
taggedEncode(byteBuffer, taggingOption);
byteBuffer.flip();
return byteBuffer.array();
}
@Override
public void taggedEncode(ByteBuffer buffer, TaggingOption taggingOption) {
int taggingTagFlags = taggingOption.tagFlags(isConstructed());
encodeTag(buffer, taggingTagFlags, taggingOption.getTagNo());
int taggingBodyLen = taggingOption.isImplicit() ? encodingBodyLength() : encodingLength();
encodeLength(buffer, taggingBodyLen);
if (taggingOption.isImplicit()) {
encodeBody(buffer);
} else {
encode(buffer);
}
}
public void taggedDecode(byte[] content, TaggingOption taggingOption) throws IOException {
taggedDecode(ByteBuffer.wrap(content), taggingOption);
}
@Override
public void taggedDecode(ByteBuffer content, TaggingOption taggingOption) throws IOException {
LimitedByteBuffer limitedBuffer = new LimitedByteBuffer(content);
taggedDecode(limitedBuffer, taggingOption);
}
protected void taggedDecode(LimitedByteBuffer content, TaggingOption taggingOption) throws IOException {
int taggingTag = readTag(content);
int taggingTagNo = readTagNo(content, taggingTag);
int taggingLength = readLength(content);
LimitedByteBuffer newContent = new LimitedByteBuffer(content, taggingLength);
int tagFlags = taggingTag & 0xe0;
taggedDecode(tagFlags, taggingTagNo, newContent, taggingOption);
}
protected void taggedDecode(int taggingTagFlags, int taggingTagNo, LimitedByteBuffer content, TaggingOption taggingOption) throws IOException {
int expectedTaggingTagFlags = taggingOption.tagFlags(isConstructed());
if (expectedTaggingTagFlags != taggingTagFlags) {
throw new IOException("Unexpected tag flags" + taggingTagFlags + ", expecting " + expectedTaggingTagFlags);
}
if (taggingOption.getTagNo() != taggingTagNo) {
throw new IOException("Unexpected tagNo " + taggingTagNo + ", expecting " + taggingOption.getTagNo());
}
if (taggingOption.isImplicit()) {
decodeBody(content);
} else {
decode(content);
}
}
public static Asn1Item decodeOne(LimitedByteBuffer content) throws IOException {
int tag = readTag(content);
int tagNo = readTagNo(content, tag);
boolean isConstructed = EncodingOption.isConstructed(tag);
int length = readLength(content);
if (length < 0) {
throw new IOException("Unexpected length");
}
LimitedByteBuffer valueContent = new LimitedByteBuffer(content, length);
content.skip(length);
Asn1Item result = new Asn1Item(tag, tagNo, valueContent);
if (result.isSimple()) {
result.decodeValueAsSimple();
}
return result;
}
public static void skipOne(LimitedByteBuffer content) throws IOException {
int tag = readTag(content);
int tagNo = readTagNo(content, tag);
int length = readLength(content);
if (length < 0) {
throw new IOException("Unexpected length");
}
content.skip(length);
}
public static int lengthOfBodyLength(int bodyLength) {
int length = 1;
if (bodyLength > 127) {
int payload = bodyLength;
while (payload != 0) {
payload >>= 8;
length++;
}
}
return length;
}
public static int lengthOfTagLength(int tagNo) {
int length = 1;
if (tagNo >= 31) {
if (tagNo < 128) {
length++;
} else {
length++;
do {
tagNo >>= 7;
length++;
} while (tagNo > 127);
}
}
return length;
}
public static void encodeTag(ByteBuffer buffer, int flags, int tagNo) {
if (tagNo < 31) {
buffer.put((byte) (flags | tagNo));
} else {
buffer.put((byte) (flags | 0x1f));
if (tagNo < 128) {
buffer.put((byte) tagNo);
} else {
byte[] tmpBytes = new byte[5]; // 5 * 7 > 32
int iPut = tmpBytes.length;
tmpBytes[--iPut] = (byte)(tagNo & 0x7f);
do {
tagNo >>= 7;
tmpBytes[--iPut] = (byte)(tagNo & 0x7f | 0x80);
} while (tagNo > 127);
buffer.put(tmpBytes, iPut, tmpBytes.length - iPut);
}
}
}
public static void encodeLength(ByteBuffer buffer, int bodyLength) {
if (bodyLength < 128) {
buffer.put((byte) bodyLength);
} else {
int length = 0;
int payload = bodyLength;
while (payload != 0) {
payload >>= 8;
length++;
}
buffer.put((byte) (length | 0x80));
payload = bodyLength;
for (int i = length - 1; i >= 0; i--) {
buffer.put((byte) (payload >> (i * 8)));
}
}
}
public static int readTag(LimitedByteBuffer buffer) throws IOException {
int tag = buffer.readByte() & 0xff;
if (tag == 0) {
throw new IOException("Bad tag 0 found");
}
return tag;
}
public static int readTagNo(LimitedByteBuffer buffer, int tag) throws IOException {
int tagNo = tag & 0x1f;
if (tagNo == 0x1f) {
tagNo = 0;
int b = buffer.readByte() & 0xff;
if ((b & 0x7f) == 0) {
throw new IOException("Invalid high tag number found");
}
while ((b >= 0) && ((b & 0x80) != 0)) {
tagNo |= (b & 0x7f);
tagNo <<= 7;
b = buffer.readByte();
}
tagNo |= (b & 0x7f);
}
return tagNo;
}
public static int readLength(LimitedByteBuffer buffer) throws IOException {
int bodyLength = buffer.readByte() & 0xff;
if (bodyLength < 0) {
throw new EOFException("Unexpected EOF");
}
if (bodyLength > 127) {
int length = bodyLength & 0x7f;
if (length > 4) {
throw new IOException("Bad bodyLength of more than 4 bytes: " + length);
}
bodyLength = 0;
int tmp;
for (int i = 0; i < length; i++) {
tmp = buffer.readByte() & 0xff;
bodyLength = (bodyLength << 8) + tmp;
}
if (bodyLength < 0) {
throw new IOException("Invalid bodyLength " + bodyLength);
}
if (bodyLength > buffer.hasLeft()) {
throw new IOException("Corrupt stream - less data "
+ buffer.hasLeft() + " than expected " + bodyLength);
}
}
return bodyLength;
}
}