blob: 80ece43db74d28aac08121bec27dd34f07b05a8e [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package common.asn1;
import streamer.ByteBuffer;
public abstract class Tag implements Asn1Constants {
/**
* Name of this tag, for debugging purposes.
*/
public String name = "";
/**
* Is this tag required or optional, for explicit tags only.
*/
public boolean optional = false;
/**
* Tag primitive (e.g. implicit boolean), or constructed (e.g. sequence, or
* explicit boolean).
*/
public boolean constructed = false;
/**
* Class of tag, when it is explicit.
*/
public int tagClass = UNIVERSAL_CLASS;
/**
* Tag number (e.g. index in sequence), when tag is explicit.
*/
public int tagNumber = -1;
/**
* Tag type (e.g. INDER), when tag is implicit.
*/
public int tagType = -1;
/**
* If tag is explicit, then it is prefixed with tag number, so it can be
* optional or used in unordered set.
*/
public boolean explicit = false;
public Tag(String name) {
this.name = name;
}
/**
* Write tag value, with or without prefix.
*/
public void writeTag(ByteBuffer buf) {
if (!isMustBeWritten())
return;
// Write prefix, when necessary
if (explicit) {
// Write tag prefix, always constructed
BerType berTagPrefix = new BerType(tagClass, true, tagNumber);
writeBerType(buf, berTagPrefix);
// Write tag prefix length
buf.writeBerLength(calculateLength());
// Write tag value
writeTagValue(buf);
} else {
// If implicit, just write tag value
writeTagValue(buf);
}
}
/**
* Must return true when value of this tag is set or tag is required, so it
* can be written, false otherwise.
*/
public boolean isMustBeWritten() {
return !optional || isValueSet();
}
/**
* Must return true when value of this tag is set or tag is required, so it
* can be written, false otherwise.
*/
public abstract boolean isValueSet();
/**
* Calculate full length of tag, including type (or prefix, when explicit).
*/
public long calculateFullLength() {
if (!isMustBeWritten())
return 0;
// Length of value, including type
long length = calculateLength();
if (!explicit) {
// Length of tag type and it length
length += calculateLengthOfTagTypeOrTagNumber(tagType) + calculateLengthOfLength(length);
} else {
// Length of tag prefix and it length
length += calculateLengthOfTagTypeOrTagNumber(tagNumber) + calculateLengthOfLength(length);
}
return length;
}
/**
* Calculate length of tag, including type when explicit, but without length
* of prefix (or type, when implicit).
*/
public long calculateLength() {
if (!isMustBeWritten())
return 0;
// Length of value
long length = calculateLengthOfValuePayload();
if (explicit) {
// Length of tag type and it length
length += calculateLengthOfTagTypeOrTagNumber(tagType) + calculateLengthOfLength(length);
}
return length;
}
/**
* Calculate length of BER length.
*/
public int calculateLengthOfLength(long length) {
if (length < 0)
throw new RuntimeException("[" + this + "] ERROR: Length of tag cannot be less than zero: " + length + ".");
if (length <= 0x7f)
return 1;
if (length <= 0xff)
return 2;
if (length <= 0xffFF)
return 3;
if (length <= 0xffFFff)
return 4;
if (length <= 0xffFFffFFL)
return 5;
if (length <= 0xffFFffFFffL)
return 6;
if (length <= 0xffFFffFFffFFL)
return 7;
if (length <= 0xffFFffFFffFFffL)
return 8;
return 9;
}
/**
* Calculate length of type to tag number. Values less than 31 are encoded
* using lower 5 bits of first byte of tag. Values larger than 31 are
* indicated by lower 5 bits set to 1 (0x1F, 31), and next bytes are contain
* value in network order, where topmost bit of byte (0x80) indicates is value
* contains more bytes, i.e. last byte of sequence has this bit set to 0.
*/
public int calculateLengthOfTagTypeOrTagNumber(int tagType) {
if (tagType >= EXTENDED_TYPE)
throw new RuntimeException("Multibyte tag types are not supported yet.");
return 1;
}
/**
* Calculate length of payload only, without tag prefix, tag type, and
* lengths.
*
* @return
*/
public abstract long calculateLengthOfValuePayload();
/**
* Write tag value only, without prefix.
*/
public void writeTagValue(ByteBuffer buf) {
// Write type
BerType valueType = new BerType(UNIVERSAL_CLASS, constructed, tagType);
writeBerType(buf, valueType);
// Write length
long lengthOfPayload = calculateLengthOfValuePayload();
buf.writeBerLength(lengthOfPayload);
// Store cursor to check is calculated length matches length of actual bytes
// written
int storedCursor = buf.cursor;
// Write value
writeTagValuePayload(buf);
// Check is calculated length matches length of actual bytes written, to catch errors early
int actualLength = buf.cursor - storedCursor;
if (actualLength != lengthOfPayload)
throw new RuntimeException("[" + this + "] ERROR: Unexpected length of data in buffer. Expected " + lengthOfPayload + " of bytes of payload, but "
+ actualLength + " bytes are written instead. Data: " + buf + ".");
}
/**
* Write tag value only, without prefix, tag type, and length.
*/
public abstract void writeTagValuePayload(ByteBuffer buf);
/**
* Read required tag, i.e. we are 100% sure that byte buffer will contain this
* tag, or exception will be thrown otherwise.
*
* @param buf
* buffer with tag data
*/
public void readTag(ByteBuffer buf) {
BerType typeAndFlags = readBerType(buf);
// * DEBUG */System.out.println("Tag, read " + typeAndFlags);
if (!isTypeValid(typeAndFlags))
throw new RuntimeException("[" + this + "] Unexpected type: " + typeAndFlags + ".");
readTag(buf, typeAndFlags);
}
/**
* Read tag when it type is already read.
*/
public void readTag(ByteBuffer buf, BerType typeAndFlags) {
if (explicit) {
long length = buf.readBerLength();
if (length > buf.length)
throw new RuntimeException("BER value is too long: " + length + " bytes. Data: " + buf + ".");
ByteBuffer value = buf.readBytes((int)length);
readTagValue(value);
value.unref();
} else {
readTagValue(buf, typeAndFlags);
}
}
/**
* Read tag value only, i.e. it prefix is already read.
*/
public void readTagValue(ByteBuffer value) {
BerType typeAndFlags = readBerType(value);
// * DEBUG */System.out.println("Tag, read value " + typeAndFlags);
if (!isTypeValid(typeAndFlags, false))
throw new RuntimeException("[" + this + "] Unexpected type: " + typeAndFlags + ".");
readTagValue(value, typeAndFlags);
}
/**
* Check are tag type and flags valid for this tag.
*/
public final boolean isTypeValid(BerType typeAndFlags) {
return isTypeValid(typeAndFlags, explicit);
}
/**
* Check are tag type and flags valid for this tag with or without tag prefix.
*
* @param explicit
* if true, then value is wrapped in tag prefix
*/
public boolean isTypeValid(BerType typeAndFlags, boolean explicit) {
if (explicit)
return typeAndFlags.tagClass == tagClass && typeAndFlags.constructed && typeAndFlags.typeOrTagNumber == tagNumber;
else
return typeAndFlags.tagClass == UNIVERSAL_CLASS && !typeAndFlags.constructed && typeAndFlags.typeOrTagNumber == tagType;
}
@Override
public String toString() {
return " \nTag [name="
+ name
+ ((constructed) ? ", constructed=" + constructed : "")
+ (", tagType=" + tagTypeOrNumberToString(UNIVERSAL_CLASS, tagType))
+ ((explicit) ? ", explicit=" + explicit + ", optional=" + optional + ", tagClass=" + tagClassToString(tagClass) + ", tagNumber="
+ tagTypeOrNumberToString(tagClass, tagNumber) : "") + "]";
}
public static final String tagTypeOrNumberToString(int tagClass, int tagTypeOrNumber) {
switch (tagClass) {
case UNIVERSAL_CLASS:
switch (tagTypeOrNumber) {
case EOF:
return "EOF";
case BOOLEAN:
return "BOOLEAN";
case INTEGER:
return "INTEGER";
case BIT_STRING:
return "BIT_STRING";
case OCTET_STRING:
return "OCTET_STRING";
case NULL:
return "NULL";
case OBJECT_ID:
return "OBJECT_ID";
case REAL:
return "REAL";
case ENUMERATED:
return "ENUMERATED";
case SEQUENCE:
return "SEQUENCE";
case SET:
return "SET";
case NUMERIC_STRING:
return "NUMERIC_STRING";
case PRINTABLE_STRING:
return "PRINTABLE_STRING";
case TELETEX_STRING:
return "TELETEX_STRING";
case VIDEOTEXT_STRING:
return "VIDEOTEXT_STRING";
case IA5_STRING:
return "IA5_STRING";
case UTCTIME:
return "UTCTIME";
case GENERAL_TIME:
return "GENERAL_TIME";
case GRAPHIC_STRING:
return "GRAPHIC_STRING";
case VISIBLE_STRING:
return "VISIBLE_STRING";
case GENERAL_STRING:
return "GENERAL_STRING";
case EXTENDED_TYPE:
return "EXTENDED_TYPE (multibyte)";
default:
return "UNKNOWN(" + tagTypeOrNumber + ")";
}
default:
return "[" + tagTypeOrNumber + "]";
}
}
public static final String tagClassToString(int tagClass) {
switch (tagClass) {
case UNIVERSAL_CLASS:
return "UNIVERSAL";
case CONTEXT_CLASS:
return "CONTEXT";
case APPLICATION_CLASS:
return "APPLICATION";
case PRIVATE_CLASS:
return "PRIVATE";
default:
return "UNKNOWN";
}
}
/**
* Read BER tag type.
*/
public BerType readBerType(ByteBuffer buf) {
int typeAndFlags = buf.readUnsignedByte();
int tagClass = typeAndFlags & CLASS_MASK;
boolean constructed = (typeAndFlags & CONSTRUCTED) != 0;
int type = typeAndFlags & TYPE_MASK;
if (type == EXTENDED_TYPE)
throw new RuntimeException("Extended tag types/numbers (31+) are not supported yet.");
return new BerType(tagClass, constructed, type);
}
/**
* Write BER tag type.
*/
public void writeBerType(ByteBuffer buf, BerType berType) {
if (berType.typeOrTagNumber >= EXTENDED_TYPE || berType.typeOrTagNumber < 0)
throw new RuntimeException("Extended tag types/numbers (31+) are not supported yet: " + berType + ".");
if ((berType.tagClass & CLASS_MASK) != berType.tagClass)
throw new RuntimeException("Value of BER tag class is out of range: " + berType.tagClass + ". Expected values: " + UNIVERSAL_CLASS + ", " + CONTEXT_CLASS
+ ", " + APPLICATION_CLASS + ", " + PRIVATE_CLASS + ".");
int typeAndFlags = berType.tagClass | ((berType.constructed) ? CONSTRUCTED : 0) | berType.typeOrTagNumber;
buf.writeByte(typeAndFlags);
}
/**
* Read tag value only, i.e. it prefix is already read, when value type is
* already read.
*
* @param buf
* buffer with tag data
*/
public abstract void readTagValue(ByteBuffer buf, BerType typeAndFlags);
/**
* Create deep copy of this tag with given suffix appended to name.
*
* @param suffix
* suffix to add to tag name, or empty string
* @return deep copy of this tag
*/
public abstract Tag deepCopy(String suffix);
/**
* Create deep copy of this tag for array or set.
*
* @param index
* index of element in array or set
* @return deep copy of this tag
*/
public Tag deepCopy(int index) {
return deepCopy("[" + index + "]");
}
/**
* Copy tag values from an other tag, except name.
*
* @return this
*/
public Tag copyFrom(Tag tag) {
constructed = tag.constructed;
explicit = tag.explicit;
optional = tag.optional;
tagClass = tag.tagClass;
tagNumber = tag.tagNumber;
return this;
}
}