/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed 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.
 */
/*
 * Copyright (c) 2015-2017, The Linux Foundation.
 */

/*
 * Copyright (C) 2011 Deutsche Telekom, A.G.
 *
 * Licensed 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 com.android.se.security.arf;

import android.util.Log;

import com.android.se.security.arf.pkcs15.PKCS15Exception;

import java.util.Arrays;

/** Base class for parsing PKCS#15 files */
public class DERParser {
    public final String mTag = "SecureElement-DERParser";
    // DER parameters
    private byte[] mDERBuffer;
    private short mDERSize, mDERIndex, mTLVDataSize = 0;

    public DERParser(byte[] buffer) throws PKCS15Exception {
        mDERBuffer = buffer;
        mDERIndex = 0;
        mDERSize = 0;
        if (mDERBuffer == null) return;
        mDERSize = (short) mDERBuffer.length;
        mTLVDataSize = mDERSize;

        if (mDERSize == 0) return;
        // Remove padding
        if (mDERBuffer[mDERIndex] == ASN1.TAG_Padding) {
            mTLVDataSize = 0;
            while (++mDERIndex < mDERSize) {
                if (mDERBuffer[mDERIndex] != ASN1.TAG_Padding) {
                    throw new PKCS15Exception("[Parser] Incorrect file format");
                }
            }
        }
    }

    /**
     * Returns "Base 128" encoded integer
     *
     * @return Converted integer
     */
    private int readIntBase128() {
        int value = 0;
        // If the MSb is set to 0, it is the last byte
        do {
            value = (value << 7) + (mDERBuffer[mDERIndex] & 0x7F);
        } while ((mDERBuffer[mDERIndex++] & 0x80) != 0);
        return value;
    }

    /**
     * Returns size of the TLV encoded value
     *
     * @return Size of the TLV
     */
    private short getTLVSize() throws PKCS15Exception {
        int size, TLVSize = 0;

        if (isEndofBuffer()) throw new PKCS15Exception("[Parser] Cannot retreive size");
        // Determine data size
        if ((TLVSize = (mDERBuffer[mDERIndex++] & 0xff)) >= 128) {
            size = TLVSize - 128;
            for (TLVSize = 0; size > 0; size--) {
                if (!isEndofBuffer()) {
                    TLVSize = (TLVSize << 8) + (mDERBuffer[mDERIndex++] & 0xff);
                } else {
                    throw new PKCS15Exception("[Parser] Cannot retreive size");
                }
            }
        }

        // Check if the buffer contains enough data
        if ((mDERIndex + TLVSize) > mDERSize) throw new PKCS15Exception("[Parser] Not enough data");
        return (short) TLVSize;
    }

    /**
     * Returns type of the TLV encoded value
     *
     * @return Type of the TLV
     */
    private byte getTLVType() throws PKCS15Exception {
        if (isEndofBuffer()) throw new PKCS15Exception("[Parser] Cannot retreive type");
        return mDERBuffer[mDERIndex++];
    }

    /**
     * Determines if we reached the end of the buffer
     *
     * @return True if end of buffer is reached; False otherwise
     */
    public boolean isEndofBuffer() throws PKCS15Exception {
        if (mDERIndex == mDERSize) return true;
        if (mDERBuffer[mDERIndex] == ASN1.TAG_Padding) {
            // Remove padding
            while (++mDERIndex < mDERSize) {
                if (mDERBuffer[mDERIndex] != ASN1.TAG_Padding) {
                    throw new PKCS15Exception("[Parser] Incorrect file format");
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Parses TLV from current index
     *
     * @return Type of TLV structure
     */
    public byte parseTLV() throws PKCS15Exception {
        byte type = getTLVType();
        mTLVDataSize = getTLVSize();
        return type;
    }

    /**
     * Parses TLV from current index and check if type is correct
     *
     * @param type Type required
     * @return Length of TLV data structure
     */
    public short parseTLV(byte type) throws PKCS15Exception {
        byte typeInBuffer = getTLVType();
        if (typeInBuffer == type) {
            mTLVDataSize = getTLVSize();
        } else {
            Log.e(mTag, "parseTLV expected: " + type + " got:" + typeInBuffer);
            Log.e(mTag, "parseTLV mDERIndex: " + mDERIndex + " mDERSize:" + mDERSize);
            throw new PKCS15Exception("[Parser] Unexpected type");
        }
        return mTLVDataSize;
    }

    /** Skips data of the current TLV structure */
    public void skipTLVData() {
        mDERIndex += mTLVDataSize;
    }

    /**
     * Returns data of the current TLV structure
     *
     * @return Data of current TLV structure
     */
    public byte[] getTLVData() {
        byte[] data = Arrays.copyOfRange(mDERBuffer, mDERIndex, mDERIndex + mTLVDataSize);
        mDERIndex += mTLVDataSize;
        return data;
    }

    /**
     * Takes snaptshot of the current context
     *
     * @return Saved context
     */
    public short[] saveContext() {
        short[] context = new short[2];
        context[0] = mDERIndex;
        context[1] = mTLVDataSize;
        return context;
    }

    /**
     * Restores a context from a snapshot previously saved
     *
     * @param context Context snapshot
     */
    public void restoreContext(short[] context) throws PKCS15Exception {
        if ((context == null) || (context.length != 2)) {
            throw new PKCS15Exception("[Parser] Invalid context");
        }
        if ((context[0] < 0) || (context[0] > mDERSize)) {
            throw new PKCS15Exception("[Parser] Index out of bound");
        }
        mDERIndex = context[0];
        mTLVDataSize = context[1];
    }

    /**
     * Parses standardized OID
     *
     * @return String containing OID
     */
    public String parseOID() throws PKCS15Exception {
        if (parseTLV(ASN1.TAG_OID) == 0) throw new PKCS15Exception("[Parser] OID Length is null");

        int end = mDERIndex + mTLVDataSize;
        StringBuffer oid = new StringBuffer();

        // First subidentifier
        int subid = readIntBase128();
        // The first subidentifier contains the first two OID components
        // X.Y is encoded as (X*40)+Y (0<=X<=2 and 0<=Y<=39 for X=0 or X=1)
        if (subid <= 79) {
            oid.append(subid / 40).append('.').append(subid % 40);
        } else {
            oid.append("2.").append(subid - 80);
        }

        while (mDERIndex < end) oid.append('.').append(readIntBase128());
        Log.i(mTag, "Found OID: " + oid.toString());
        return oid.toString();
    }

    /**
     * Parses PKCS#15 path attribute
     *
     * @return Path retreived from the attribute
     */
    public byte[] parsePathAttributes() throws PKCS15Exception {
        parseTLV(ASN1.TAG_Sequence);
        parseTLV(ASN1.TAG_OctetString);
        return getTLVData();
    }
}
