// 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; } }