// 
// Decompiled by Procyon v0.6.0
// 

package org.bouncycastle.oer;

import java.io.EOFException;
import java.util.List;
import org.bouncycastle.asn1.ASN1Boolean;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.util.Strings;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.asn1.ASN1Integer;
import java.math.BigInteger;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.io.Streams;
import org.bouncycastle.asn1.ASN1Object;
import java.util.Iterator;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import org.bouncycastle.asn1.ASN1Encodable;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.FilterInputStream;

public class OERInputStream extends FilterInputStream
{
    private static final int[] bits;
    private static final int[] bitsR;
    protected PrintWriter debugOutput;
    private int maxByteAllocation;
    protected PrintWriter debugStream;
    
    public OERInputStream(final InputStream in) {
        super(in);
        this.debugOutput = null;
        this.maxByteAllocation = 1048576;
        this.debugStream = null;
    }
    
    public OERInputStream(final InputStream in, final int maxByteAllocation) {
        super(in);
        this.debugOutput = null;
        this.maxByteAllocation = 1048576;
        this.debugStream = null;
        this.maxByteAllocation = maxByteAllocation;
    }
    
    public static ASN1Encodable parse(final byte[] buf, final Element element) throws IOException {
        return new OERInputStream(new ByteArrayInputStream(buf)).parse(element);
    }
    
    private int countOptionalChildTypes(final Element element) {
        int n = 0;
        final Iterator<Element> iterator = element.getChildren().iterator();
        while (iterator.hasNext()) {
            n += (iterator.next().isExplicit() ? 0 : 1);
        }
        return n;
    }
    
    public ASN1Object parse(final Element element) throws IOException {
        switch (element.getBaseType()) {
            case OPAQUE: {
                return this.parse(new Element(element.resolveSupplier().build(), element));
            }
            case Switch: {
                throw new IllegalStateException("A switch element should only be found within a sequence.");
            }
            case Supplier: {
                return this.parse(new Element(element.getElementSupplier().build(), element));
            }
            case SEQ_OF: {
                final byte[] allocateArray = this.allocateArray(this.readLength().intLength());
                if (Streams.readFully(this, allocateArray) != allocateArray.length) {
                    throw new IOException("could not read all of count of seq-of values");
                }
                final int intValue = BigIntegers.fromUnsignedByteArray(allocateArray).intValue();
                this.debugPrint(element + "(len = " + intValue + ")");
                final ASN1EncodableVector asn1EncodableVector = new ASN1EncodableVector();
                if (element.getChildren().get(0).getaSwitch() != null) {
                    throw new IllegalStateException("element def for item in SEQ OF has a switch, switches only supported in sequences");
                }
                for (int i = 0; i < intValue; ++i) {
                    asn1EncodableVector.add(this.parse(Element.expandDeferredDefinition(element.getChildren().get(0), element)));
                }
                return new DERSequence(asn1EncodableVector);
            }
            case SEQ: {
                final Sequence sequence = new Sequence(this.in, element);
                this.debugPrint(element + sequence.toString());
                final ASN1EncodableVector asn1EncodableVector2 = new ASN1EncodableVector();
                List<Element> children;
                int j;
                Element element2;
                Element expandDeferredDefinition;
                Element result;
                for (children = element.getChildren(), j = 0; j < children.size(); ++j) {
                    element2 = children.get(j);
                    if (element2.getBaseType() != OERDefinition.BaseType.EXTENSION) {
                        if (element2.getBlock() > 0) {
                            break;
                        }
                        expandDeferredDefinition = Element.expandDeferredDefinition(element2, element);
                        if (expandDeferredDefinition.getaSwitch() != null) {
                            result = expandDeferredDefinition.getaSwitch().result(new SwitchIndexer.Asn1EncodableVectorIndexer(asn1EncodableVector2));
                            if (result.getParent() != element) {
                                result = new Element(result, element);
                            }
                        }
                        else {
                            result = expandDeferredDefinition;
                        }
                        if (sequence.valuePresent == null) {
                            asn1EncodableVector2.add(this.parse(result));
                        }
                        else if (sequence.valuePresent[j]) {
                            if (result.isExplicit()) {
                                asn1EncodableVector2.add(this.parse(result));
                            }
                            else {
                                asn1EncodableVector2.add(OEROptional.getInstance(this.parse(result)));
                            }
                        }
                        else if (result.getDefaultValue() != null) {
                            asn1EncodableVector2.add(expandDeferredDefinition.getDefaultValue());
                        }
                        else {
                            asn1EncodableVector2.add(this.absent(expandDeferredDefinition));
                        }
                    }
                }
                if (sequence.extensionFlagSet) {
                    final byte[] allocateArray2 = this.allocateArray(this.readLength().intLength());
                    if (Streams.readFully(this.in, allocateArray2) != allocateArray2.length) {
                        throw new IOException("did not fully read presence list.");
                    }
                    for (int n = 8, n2 = allocateArray2.length * 8 - allocateArray2[0]; j < children.size() || n < n2; ++n, ++j) {
                        final Element element3 = (j < children.size()) ? children.get(j) : null;
                        if (element3 == null) {
                            if ((allocateArray2[n / 8] & OERInputStream.bitsR[n % 8]) != 0x0) {
                                int access$000 = this.readLength().intLength();
                                while (--access$000 >= 0) {
                                    this.in.read();
                                }
                            }
                        }
                        else if (n < n2 && (allocateArray2[n / 8] & OERInputStream.bitsR[n % 8]) != 0x0) {
                            asn1EncodableVector2.add(this.parseOpenType(element3));
                        }
                        else {
                            if (element3.isExplicit()) {
                                throw new IOException("extension is marked as explicit but is not defined in presence list");
                            }
                            asn1EncodableVector2.add(OEROptional.ABSENT);
                        }
                    }
                }
                return new DERSequence(asn1EncodableVector2);
            }
            case CHOICE: {
                final Choice choice = this.choice();
                this.debugPrint(choice.toString() + " " + choice.tag);
                if (choice.isContextSpecific()) {
                    final Element expandDeferredDefinition2 = Element.expandDeferredDefinition(element.getChildren().get(choice.getTag()), element);
                    if (expandDeferredDefinition2.getBlock() > 0) {
                        this.debugPrint("Chosen (Ext): " + expandDeferredDefinition2);
                        return new DERTaggedObject(choice.tag, this.parseOpenType(expandDeferredDefinition2));
                    }
                    this.debugPrint("Chosen: " + expandDeferredDefinition2);
                    return new DERTaggedObject(choice.tag, this.parse(expandDeferredDefinition2));
                }
                else {
                    if (choice.isApplicationTagClass()) {
                        throw new IllegalStateException("Unimplemented tag type");
                    }
                    if (choice.isPrivateTagClass()) {
                        throw new IllegalStateException("Unimplemented tag type");
                    }
                    if (choice.isUniversalTagClass()) {
                        throw new IllegalStateException("Unimplemented tag type");
                    }
                    throw new IllegalStateException("Unimplemented tag type");
                }
                break;
            }
            case ENUM: {
                final BigInteger enumeration = this.enumeration();
                this.debugPrint(element + "ENUM(" + enumeration + ") = " + element.getChildren().get(enumeration.intValue()).getLabel());
                return new ASN1Enumerated(enumeration);
            }
            case INT: {
                final int intBytesForRange = element.intBytesForRange();
                byte[] val;
                BigInteger bigInteger;
                if (intBytesForRange != 0) {
                    val = this.allocateArray(Math.abs(intBytesForRange));
                    Streams.readFully(this, val);
                    if (intBytesForRange < 0) {
                        bigInteger = new BigInteger(val);
                    }
                    else {
                        bigInteger = BigIntegers.fromUnsignedByteArray(val);
                    }
                }
                else if (element.isLowerRangeZero()) {
                    val = this.allocateArray(this.readLength().intLength());
                    Streams.readFully(this, val);
                    if (val.length == 0) {
                        bigInteger = BigInteger.ZERO;
                    }
                    else {
                        bigInteger = new BigInteger(1, val);
                    }
                }
                else {
                    val = this.allocateArray(this.readLength().intLength());
                    Streams.readFully(this, val);
                    if (val.length == 0) {
                        bigInteger = BigInteger.ZERO;
                    }
                    else {
                        bigInteger = new BigInteger(val);
                    }
                }
                if (this.debugOutput != null) {
                    this.debugPrint(element + "INTEGER byteLen= " + val.length + " hex= " + bigInteger.toString(16) + ")");
                }
                return new ASN1Integer(bigInteger);
            }
            case OCTET_STRING: {
                int n3;
                if (element.getUpperBound() != null && element.getUpperBound().equals(element.getLowerBound())) {
                    n3 = element.getUpperBound().intValue();
                }
                else {
                    n3 = this.readLength().intLength();
                }
                final byte[] allocateArray3 = this.allocateArray(n3);
                if (Streams.readFully(this, allocateArray3) != n3) {
                    throw new IOException("did not read all of " + element.getLabel());
                }
                if (this.debugOutput != null) {
                    this.debugPrint(element + "OCTET STRING (" + allocateArray3.length + ") = " + Hex.toHexString(allocateArray3, 0, Math.min(allocateArray3.length, 32)) + " " + ((allocateArray3.length > 32) ? "..." : ""));
                }
                return new DEROctetString(allocateArray3);
            }
            case IA5String: {
                byte[] array;
                if (element.isFixedLength()) {
                    array = this.allocateArray(element.getUpperBound().intValue());
                }
                else {
                    array = this.allocateArray(this.readLength().intLength());
                }
                if (Streams.readFully(this, array) != array.length) {
                    throw new IOException("could not read all of IA5 string");
                }
                final String fromByteArray = Strings.fromByteArray(array);
                if (this.debugOutput != null) {
                    this.debugPrint(element.appendLabel("IA5 String (" + array.length + ") = " + fromByteArray));
                }
                return new DERIA5String(fromByteArray);
            }
            case UTF8_STRING: {
                final byte[] allocateArray4 = this.allocateArray(this.readLength().intLength());
                if (Streams.readFully(this, allocateArray4) != allocateArray4.length) {
                    throw new IOException("could not read all of utf 8 string");
                }
                final String fromUTF8ByteArray = Strings.fromUTF8ByteArray(allocateArray4);
                if (this.debugOutput != null) {
                    this.debugPrint(element + "UTF8 String (" + allocateArray4.length + ") = " + fromUTF8ByteArray);
                }
                return new DERUTF8String(fromUTF8ByteArray);
            }
            case BIT_STRING: {
                byte[] array2;
                if (element.isFixedLength()) {
                    array2 = new byte[element.getLowerBound().intValue() / 8];
                }
                else if (BigInteger.ZERO.compareTo(element.getUpperBound()) > 0) {
                    array2 = this.allocateArray(element.getUpperBound().intValue() / 8);
                }
                else {
                    array2 = this.allocateArray(this.readLength().intLength() / 8);
                }
                Streams.readFully(this, array2);
                if (this.debugOutput != null) {
                    final StringBuffer sb = new StringBuffer();
                    sb.append("BIT STRING(" + array2.length * 8 + ") = ");
                    for (int k = 0; k != array2.length; ++k) {
                        byte b = array2[k];
                        for (int l = 0; l < 8; ++l) {
                            sb.append(((b & 0x80) > 0) ? "1" : "0");
                            b <<= 1;
                        }
                    }
                    this.debugPrint(element + sb.toString());
                }
                return new DERBitString(array2);
            }
            case NULL: {
                this.debugPrint(element + "NULL");
                return DERNull.INSTANCE;
            }
            case EXTENSION: {
                final LengthInfo length = this.readLength();
                final byte[] array3 = new byte[length.intLength()];
                if (Streams.readFully(this, array3) != length.intLength()) {
                    throw new IOException("could not read all of count of open value in choice (...) ");
                }
                this.debugPrint("ext " + length.intLength() + " " + Hex.toHexString(array3));
                return new DEROctetString(array3);
            }
            case BOOLEAN: {
                if (this.read() == 0) {
                    return ASN1Boolean.FALSE;
                }
                return ASN1Boolean.TRUE;
            }
            default: {
                throw new IllegalStateException("Unhandled type " + element.getBaseType());
            }
        }
    }
    
    private ASN1Encodable absent(final Element obj) {
        this.debugPrint(obj + "Absent");
        return OEROptional.ABSENT;
    }
    
    private byte[] allocateArray(final int i) {
        if (i > this.maxByteAllocation) {
            throw new IllegalArgumentException("required byte array size " + i + " was greater than " + this.maxByteAllocation);
        }
        return new byte[i];
    }
    
    public BigInteger parseInt(final boolean b, final int n) throws Exception {
        final byte[] array = new byte[n];
        if (Streams.readFully(this, array) != array.length) {
            throw new IllegalStateException("integer not fully read");
        }
        return b ? new BigInteger(1, array) : new BigInteger(array);
    }
    
    public BigInteger uint8() throws Exception {
        return this.parseInt(true, 1);
    }
    
    public BigInteger uint16() throws Exception {
        return this.parseInt(true, 2);
    }
    
    public BigInteger uint32() throws Exception {
        return this.parseInt(true, 4);
    }
    
    public BigInteger uint64() throws Exception {
        return this.parseInt(false, 8);
    }
    
    public BigInteger int8() throws Exception {
        return this.parseInt(false, 1);
    }
    
    public BigInteger int16() throws Exception {
        return this.parseInt(false, 2);
    }
    
    public BigInteger int32() throws Exception {
        return this.parseInt(false, 4);
    }
    
    public BigInteger int64() throws Exception {
        return this.parseInt(false, 8);
    }
    
    public LengthInfo readLength() throws IOException {
        final int read = this.read();
        if (read == -1) {
            throw new EOFException("expecting length");
        }
        if ((read & 0x80) == 0x0) {
            this.debugPrint("Len (Short form): " + (read & 0x7F));
            return new LengthInfo(BigInteger.valueOf(read & 0x7F), true);
        }
        final byte[] array = new byte[read & 0x7F];
        if (Streams.readFully(this, array) != array.length) {
            throw new EOFException("did not read all bytes of length definition");
        }
        this.debugPrint("Len (Long Form): " + (read & 0x7F) + " actual len: " + Hex.toHexString(array));
        return new LengthInfo(BigIntegers.fromUnsignedByteArray(array), false);
    }
    
    public BigInteger enumeration() throws IOException {
        final int read = this.read();
        if (read == -1) {
            throw new EOFException("expecting prefix of enumeration");
        }
        if ((read & 0x80) != 0x80) {
            return BigInteger.valueOf(read);
        }
        final int n = read & 0x7F;
        if (n == 0) {
            return BigInteger.ZERO;
        }
        final byte[] magnitude = new byte[n];
        if (Streams.readFully(this, magnitude) != magnitude.length) {
            throw new EOFException("unable to fully read integer component of enumeration");
        }
        return new BigInteger(1, magnitude);
    }
    
    protected ASN1Encodable parseOpenType(final Element element) throws IOException {
        final byte[] allocateArray = this.allocateArray(this.readLength().intLength());
        if (Streams.readFully(this.in, allocateArray) != allocateArray.length) {
            throw new IOException("did not fully read open type as raw bytes");
        }
        OERInputStream oerInputStream = null;
        try {
            oerInputStream = new OERInputStream(new ByteArrayInputStream(allocateArray));
            return oerInputStream.parse(element);
        }
        finally {
            if (oerInputStream != null) {
                oerInputStream.close();
            }
        }
    }
    
    public Choice choice() throws IOException {
        return new Choice(this);
    }
    
    protected void debugPrint(final String csq) {
        if (this.debugOutput != null) {
            final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            int i = -1;
            for (int j = 0; j != stackTrace.length; ++j) {
                final StackTraceElement stackTraceElement = stackTrace[j];
                if (stackTraceElement.getMethodName().equals("debugPrint")) {
                    i = 0;
                }
                else if (stackTraceElement.getClassName().contains("OERInput")) {
                    ++i;
                }
            }
            while (i > 0) {
                this.debugOutput.append("    ");
                --i;
            }
            this.debugOutput.append(csq).append("\n");
            this.debugOutput.flush();
        }
    }
    
    static {
        bits = new int[] { 1, 2, 4, 8, 16, 32, 64, 128 };
        bitsR = new int[] { 128, 64, 32, 16, 8, 4, 2, 1 };
    }
    
    public static class Choice extends OERInputStream
    {
        final int preamble;
        final int tag;
        final int tagClass;
        
        public Choice(final InputStream inputStream) throws IOException {
            super(inputStream);
            this.preamble = this.read();
            if (this.preamble < 0) {
                throw new EOFException("expecting preamble byte of choice");
            }
            this.tagClass = (this.preamble & 0xC0);
            int tag = this.preamble & 0x3F;
            if (tag >= 63) {
                tag = 0;
                int read;
                do {
                    read = inputStream.read();
                    if (read < 0) {
                        throw new EOFException("expecting further tag bytes");
                    }
                    tag = (tag << 7 | (read & 0x7F));
                } while ((read & 0x80) != 0x0);
            }
            this.tag = tag;
        }
        
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("CHOICE(");
            switch (this.tagClass) {
                case 0: {
                    sb.append("Universal ");
                    break;
                }
                case 64: {
                    sb.append("Application ");
                    break;
                }
                case 192: {
                    sb.append("Private ");
                    break;
                }
                case 128: {
                    sb.append("ContextSpecific ");
                    break;
                }
            }
            sb.append("Tag = " + this.tag);
            sb.append(")");
            return sb.toString();
        }
        
        public int getTagClass() {
            return this.tagClass;
        }
        
        public int getTag() {
            return this.tag;
        }
        
        public boolean isContextSpecific() {
            return this.tagClass == 128;
        }
        
        public boolean isUniversalTagClass() {
            return this.tagClass == 0;
        }
        
        public boolean isApplicationTagClass() {
            return this.tagClass == 64;
        }
        
        public boolean isPrivateTagClass() {
            return this.tagClass == 192;
        }
    }
    
    private static final class LengthInfo
    {
        private final BigInteger length;
        private final boolean shortForm;
        
        public LengthInfo(final BigInteger length, final boolean shortForm) {
            this.length = length;
            this.shortForm = shortForm;
        }
        
        private int intLength() {
            return BigIntegers.intValueExact(this.length);
        }
    }
    
    public static class Sequence extends OERInputStream
    {
        private final int preamble;
        private final boolean[] valuePresent;
        private final boolean extensionFlagSet;
        
        public Sequence(final InputStream inputStream, final Element element) throws IOException {
            super(inputStream);
            if (!element.hasPopulatedExtension() && element.getOptionals() <= 0 && !element.hasDefaultChildren()) {
                this.preamble = 0;
                this.extensionFlagSet = false;
                this.valuePresent = null;
                return;
            }
            this.preamble = this.in.read();
            if (this.preamble < 0) {
                throw new EOFException("expecting preamble byte of sequence");
            }
            this.extensionFlagSet = (element.hasPopulatedExtension() && (this.preamble & 0x80) == 0x80);
            this.valuePresent = new boolean[element.getChildren().size()];
            final int n = 0;
            int n2 = element.hasPopulatedExtension() ? 6 : 7;
            int n3 = this.preamble;
            int n4 = 0;
            for (final Element element2 : element.getChildren()) {
                if (element2.getBaseType() == OERDefinition.BaseType.EXTENSION) {
                    continue;
                }
                if (element2.getBlock() != n) {
                    break;
                }
                if (element2.isExplicit()) {
                    this.valuePresent[n4++] = true;
                }
                else {
                    if (n2 < 0) {
                        n3 = inputStream.read();
                        if (n3 < 0) {
                            throw new EOFException("expecting mask byte sequence");
                        }
                        n2 = 7;
                    }
                    this.valuePresent[n4++] = ((n3 & OERInputStream.bits[n2]) > 0);
                    --n2;
                }
            }
        }
        
        public boolean hasOptional(final int n) {
            return this.valuePresent[n];
        }
        
        public boolean hasExtension() {
            return this.extensionFlagSet;
        }
        
        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder();
            sb.append("SEQ(");
            sb.append(this.hasExtension() ? "Ext " : "");
            if (this.valuePresent == null) {
                sb.append("*");
            }
            else {
                for (int i = 0; i < this.valuePresent.length; ++i) {
                    if (this.valuePresent[i]) {
                        sb.append("1");
                    }
                    else {
                        sb.append("0");
                    }
                }
            }
            sb.append(")");
            return sb.toString();
        }
    }
}
