package com.android.hotspot2.asn1;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Asn1Decoder {
public static final int TAG_UNIVZERO = 0x00;
public static final int TAG_BOOLEAN = 0x01;
public static final int TAG_INTEGER = 0x02;
public static final int TAG_BITSTRING = 0x03;
public static final int TAG_OCTET_STRING = 0x04;
public static final int TAG_NULL = 0x05;
public static final int TAG_OID = 0x06;
public static final int TAG_ObjectDescriptor = 0x07;
public static final int TAG_EXTERNAL = 0x08;
public static final int TAG_REAL = 0x09;
public static final int TAG_ENUMERATED = 0x0a;
public static final int TAG_UTF8String = 0x0c; // * (*) are X.509 DirectoryString's
public static final int TAG_RelativeOID = 0x0d;
public static final int TAG_SEQ = 0x10; // 30 if constructed
public static final int TAG_SET = 0x11;
public static final int TAG_NumericString = 0x12; // [UNIVERSAL 18]
public static final int TAG_PrintableString = 0x13; // * [UNIVERSAL 19]
public static final int TAG_T61String = 0x14; // * TeletexString [UNIVERSAL 20]
public static final int TAG_VideotexString = 0x15; // [UNIVERSAL 21]
public static final int TAG_IA5String = 0x16; // [UNIVERSAL 22]
public static final int TAG_UTCTime = 0x17;
public static final int TAG_GeneralizedTime = 0x18;
public static final int TAG_GraphicString = 0x19; // [UNIVERSAL 25]
public static final int TAG_VisibleString = 0x1a; // ISO64String [UNIVERSAL 26]
public static final int TAG_GeneralString = 0x1b; // [UNIVERSAL 27]
public static final int TAG_UniversalString = 0x1c; // * [UNIVERSAL 28]
public static final int TAG_BMPString = 0x1e; // * [UNIVERSAL 30]
public static final int IntOverflow = 0xffff0000;
public static final int MoreBit = 0x80;
public static final int MoreData = 0x7f;
public static final int ConstructedBit = 0x20;
public static final int ClassShift = 6;
public static final int ClassMask = 0x3;
public static final int MoreWidth = 7;
public static final int ByteWidth = 8;
public static final int ByteMask = 0xff;
public static final int ContinuationTag = 31;
public static final int IndefiniteLength = -1;
private static final Map<Integer, Asn1Tag> sTagMap = new HashMap<>();
static {
sTagMap.put(TAG_UNIVZERO, Asn1Tag.UNIVZERO);
sTagMap.put(TAG_BOOLEAN, Asn1Tag.BOOLEAN);
sTagMap.put(TAG_INTEGER, Asn1Tag.INTEGER);
sTagMap.put(TAG_BITSTRING, Asn1Tag.BITSTRING);
sTagMap.put(TAG_OCTET_STRING, Asn1Tag.OCTET_STRING);
sTagMap.put(TAG_NULL, Asn1Tag.NULL);
sTagMap.put(TAG_OID, Asn1Tag.OID);
sTagMap.put(TAG_ObjectDescriptor, Asn1Tag.ObjectDescriptor);
sTagMap.put(TAG_EXTERNAL, Asn1Tag.EXTERNAL);
sTagMap.put(TAG_REAL, Asn1Tag.REAL);
sTagMap.put(TAG_ENUMERATED, Asn1Tag.ENUMERATED);
sTagMap.put(TAG_UTF8String, Asn1Tag.UTF8String);
sTagMap.put(TAG_RelativeOID, Asn1Tag.RelativeOID);
sTagMap.put(TAG_SEQ, Asn1Tag.SEQUENCE);
sTagMap.put(TAG_SET, Asn1Tag.SET);
sTagMap.put(TAG_NumericString, Asn1Tag.NumericString);
sTagMap.put(TAG_PrintableString, Asn1Tag.PrintableString);
sTagMap.put(TAG_T61String, Asn1Tag.T61String);
sTagMap.put(TAG_VideotexString, Asn1Tag.VideotexString);
sTagMap.put(TAG_IA5String, Asn1Tag.IA5String);
sTagMap.put(TAG_UTCTime, Asn1Tag.UTCTime);
sTagMap.put(TAG_GeneralizedTime, Asn1Tag.GeneralizedTime);
sTagMap.put(TAG_GraphicString, Asn1Tag.GraphicString);
sTagMap.put(TAG_VisibleString, Asn1Tag.VisibleString);
sTagMap.put(TAG_GeneralString, Asn1Tag.GeneralString);
sTagMap.put(TAG_UniversalString, Asn1Tag.UniversalString);
sTagMap.put(TAG_BMPString, Asn1Tag.BMPString);
}
public static Asn1Tag mapTag(int tag) {
return sTagMap.get(tag);
}
public static Collection<Asn1Object> decode(ByteBuffer data) throws DecodeException {
Asn1Constructed root =
new Asn1Constructed(0, null, data.remaining(), data, data.position());
decode(0, root);
return root.getChildren();
}
private static void decode(int level, Asn1Constructed parent) throws DecodeException {
ByteBuffer data = parent.getPayload();
while (data.hasRemaining()) {
int tagPosition = data.position();
int propMask = data.get(tagPosition) & ByteMask;
if (propMask == 0 && parent.isIndefiniteLength() && data.get(tagPosition + 1) == 0) {
parent.setEndOfData(tagPosition);
return;
}
Asn1Class asn1Class = Asn1Class.values()[(propMask >> ClassShift) & ClassMask];
boolean constructed = (propMask & ConstructedBit) != 0;
int tag = decodeTag(data);
int length = decodeLength(data);
if (constructed) {
ByteBuffer payload = peelOff(data, length);
Asn1Constructed root =
new Asn1Constructed(tag, asn1Class, length, payload, tagPosition);
decode(level + 1, root);
if (length == IndefiniteLength) {
data.position(root.getEndOfData() + 2); // advance past '00'
}
parent.addChild(root);
} else {
if (asn1Class != Asn1Class.Universal) {
parent.addChild(new Asn1Octets(tag, asn1Class, length, data));
} else {
parent.addChild(buildScalar(tag, asn1Class, length, data));
}
}
}
}
private static ByteBuffer peelOff(ByteBuffer base, int length) {
ByteBuffer copy = base.duplicate();
if (length == IndefiniteLength) {
return copy;
}
copy.limit(copy.position() + length);
base.position(base.position() + length);
return copy;
}
private static Asn1Object buildScalar(int tag, Asn1Class asn1Class, int length, ByteBuffer data)
throws DecodeException {
switch (tag) {
case TAG_BOOLEAN:
return new Asn1Boolean(tag, asn1Class, length, data);
case TAG_INTEGER:
case TAG_ENUMERATED:
return new Asn1Integer(tag, asn1Class, length, data);
case TAG_BITSTRING:
int bitResidual = data.get() & ByteMask;
return new Asn1Octets(tag, asn1Class, length, data, bitResidual);
case TAG_OCTET_STRING:
return new Asn1Octets(tag, asn1Class, length, data);
case TAG_OID:
return new Asn1Oid(tag, asn1Class, length, data);
case TAG_UTF8String:
case TAG_NumericString:
case TAG_PrintableString:
case TAG_T61String:
case TAG_VideotexString:
case TAG_IA5String:
case TAG_GraphicString:
case TAG_VisibleString:
case TAG_GeneralString:
case TAG_UniversalString:
case TAG_BMPString:
return new Asn1String(tag, asn1Class, length, data);
case TAG_GeneralizedTime:
case TAG_UTCTime:
// Should really be a dedicated time object
return new Asn1String(tag, asn1Class, length, data);
default:
return new Asn1Octets(tag, asn1Class, length, data);
}
}
private static int decodeTag(ByteBuffer data) throws DecodeException {
int tag;
byte tag0 = data.get();
if ((tag = (tag0 & ContinuationTag)) == ContinuationTag) {
int tagByte;
tag = 0;
while (((tagByte = data.get() & ByteMask) & MoreBit) != 0) {
tag = (tag << MoreWidth) | (tagByte & MoreData);
if ((tag & IntOverflow) != 0)
throw new DecodeException("Tag overflow", data.position());
}
tag = (tag << MoreWidth) | tagByte;
}
return tag;
}
private static int decodeLength(ByteBuffer data) throws DecodeException {
int length;
int lenlen = data.get() & ByteMask;
if ((lenlen & MoreBit) == 0) // One byte encoding
length = lenlen;
else {
lenlen &= MoreData;
if (lenlen == 0) {
return IndefiniteLength;
}
length = 0;
while (lenlen-- > 0) {
length = (length << ByteWidth) | (data.get() & ByteMask);
if ((length & IntOverflow) != 0 && lenlen > 0)
throw new DecodeException("Length overflow", data.position());
}
}
return length;
}
}