/*
 * Package name
Package aid: 54 69 73 53 43
Applet aid: 54 69 73 53 43 00 01
 */
package SimplePIN;

/*
 * Imported packages
 */
// specific import for Javacard API access
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;

public class SimplePINApplet extends javacard.framework.Applet {
    // MAIN INSTRUCTION CLASS

    final static byte INS_CLA_APPLET = (byte) 0xB2;
    // INSTRUCTIONS
    final static byte INS_GET_PUBLIC = (byte) 0x30;
    final static byte INS_SET_PUBLIC = (byte) 0x31;
    final static byte INS_GET_PRIVATE = (byte) 0x32;
    final static byte INS_SET_PRIVATE = (byte) 0x33;
    final static byte INS_SET_PIN = (byte) 0x34;
    final static byte INS_VERIFY_PIN = (byte) 0x35;
    final static short RAM_ARRAY_LENGTH = (short) 0xff;
    final static short DATA_RECORD_LENGTH = (short) 0x80; // 128B per record
    final static short NUMBER_OF_RECORDS = (short) 0x0a; // 10 records
    final static short DATA_ARRAY_LENGTH = (short) (DATA_RECORD_LENGTH * NUMBER_OF_RECORDS);
    private static byte DEFAULT_USER_PIN[] = {(byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30};
    private OwnerPIN m_pin = null;
    // PERSISTENT ARRAY IN EEPROM
    private byte m_publicDataArray[] = null;
    private byte m_privateDataArray[] = null;

    /**
     * TisSCApplet default constructor
     * Only this class's install method should create the applet object.
     */
    protected SimplePINApplet(byte[] buffer, short offset, byte length) {
        // data offset is used for application specific parameter.
        // initialization with default offset (AID offset).
        short dataOffset = offset;
        boolean isOP2 = false;

        if (length > 9) {
            // Install parameter detail. Compliant with OP 2.0.1.

            // | size | content
            // |------|---------------------------
            // |  1   | [AID_Length]
            // | 5-16 | [AID_Bytes]
            // |  1   | [Privilege_Length]
            // | 1-n  | [Privilege_Bytes] (normally 1Byte)
            // |  1   | [Application_Proprietary_Length]
            // | 0-m  | [Application_Proprietary_Bytes]

            // shift to privilege offset
            dataOffset += (short) (1 + buffer[offset]);
            // finally shift to Application specific offset
            dataOffset += (short) (1 + buffer[dataOffset]);

            // <IF NECESSARY, USE COMMENTS TO CHECK LENGTH >
            // // checks wrong data length
            // if(buffer[dataOffset] !=  <PUT YOUR PARAMETERS LENGTH> )
            //     // return received proprietary data length in the reason
            //     ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH + offset + length - dataOffset));

            // go to proprietary data
            dataOffset++;

            m_publicDataArray = new byte[DATA_ARRAY_LENGTH];
            Util.arrayFillNonAtomic(m_publicDataArray, (short) 0, DATA_ARRAY_LENGTH, (byte) 0);
            m_privateDataArray = new byte[DATA_ARRAY_LENGTH];
            Util.arrayFillNonAtomic(m_privateDataArray, (short) 0, DATA_ARRAY_LENGTH, (byte) 0);

            m_pin = new OwnerPIN((byte) 5, (byte) 4);
            m_pin.update(DEFAULT_USER_PIN, (byte) 0, (byte) 4);

            // update flag
            isOP2 = true;

        } else {
            // <IF NECESSARY, USE COMMENTS TO CHECK LENGTH >
            // if(length != <PUT YOUR PARAMETERS LENGTH> )
            //     ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH + length));
        }

        // <PUT YOUR CREATION ACTION HERE>

        // register this instance
        if (isOP2) {
            register(buffer, (short) (offset + 1), (byte) buffer[offset]);
        } else {
            register();
        }
    }

    /**
     * Method installing the applet.
     * @param bArray the array constaining installation parameters
     * @param bOffset the starting offset in bArray
     * @param bLength the length in bytes of the data parameter in bArray
     */
    public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException {
        /* applet  instance creation */
        new SimplePINApplet(bArray, bOffset, (byte) bLength);
    }

    /**
     * Select method returns true if applet selection is supported.
     * @return boolean status of selection.
     */
    public boolean select() {
        // <PUT YOUR SELECTION ACTION HERE>

        // return status of selection
        return true;
    }

    /**
     * Deselect method called by the system in the deselection process.
     */
    public void deselect() {

        // <PUT YOUR DESELECTION ACTION HERE>

        return;
    }

    /**
     * Method processing an incoming APDU.
     * @see APDU
     * @param apdu the incoming APDU
     * @exception ISOException with the response bytes defined by ISO 7816-4
     */
    public void process(APDU apdu) throws ISOException {
        // get the APDU buffer
        byte[] apduBuffer = apdu.getBuffer();

        // ignore the applet select command dispached to the process
        if (selectingApplet()) {
            return;
        }

        // APDU instruction parser
        if (apduBuffer[ISO7816.OFFSET_CLA] == INS_CLA_APPLET) {
            switch (apduBuffer[ISO7816.OFFSET_INS]) {
                case INS_GET_PUBLIC:
                    GetPublicData(apdu);
                    break;
                case INS_SET_PUBLIC:
                    SetPublicData(apdu);
                    break;
                case INS_GET_PRIVATE:
                    GetPrivateData(apdu);
                    break;
                case INS_SET_PRIVATE:
                    SetPrivateData(apdu);
                    break;
                case INS_SET_PIN:
                    SetPin(apdu);
                    break;
                case INS_VERIFY_PIN:
                    VerifyPIN(apdu);
                    break;

                default:
                    // The INS code is not supported by the dispatcher
                    ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
                    break;

            }
        } else {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }
    }

    void SetPublicData(APDU apdu) {
        byte[] apdubuf = apdu.getBuffer();
        short dataLen = apdu.setIncomingAndReceive();

        if (apdubuf[ISO7816.OFFSET_P1] > NUMBER_OF_RECORDS) {
            ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
        }

        // SET DATA
        Util.arrayCopyNonAtomic(apdubuf, ISO7816.OFFSET_CDATA, m_publicDataArray, (short) (ISO7816.OFFSET_P1 * DATA_RECORD_LENGTH), dataLen);
    }

    void SetPrivateData(APDU apdu) {
        byte[] apdubuf = apdu.getBuffer();
        short dataLen = apdu.setIncomingAndReceive();

        if (apdubuf[ISO7816.OFFSET_P1] > NUMBER_OF_RECORDS) {
            ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
        }
        if (m_pin.isValidated()) {
            // SET DATA
            Util.arrayCopyNonAtomic(apdubuf, ISO7816.OFFSET_CDATA, m_privateDataArray, (short) (ISO7816.OFFSET_P1 * DATA_RECORD_LENGTH), dataLen);
        } else {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
    }

    void GetPublicData(APDU apdu) {
        byte[] apdubuf = apdu.getBuffer();
        short dataLen = apdu.setIncomingAndReceive();

        if (apdubuf[ISO7816.OFFSET_P1] > NUMBER_OF_RECORDS) {
            ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
        }

        // RETURN DATA
        Util.arrayCopyNonAtomic(m_publicDataArray, (short) (ISO7816.OFFSET_P1 * DATA_RECORD_LENGTH), apdubuf, ISO7816.OFFSET_CDATA, apdubuf[ISO7816.OFFSET_P2]);
        apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, apdubuf[ISO7816.OFFSET_P2]);
    }

    void GetPrivateData(APDU apdu) {
        byte[] apdubuf = apdu.getBuffer();
        short dataLen = apdu.setIncomingAndReceive();

        if (apdubuf[ISO7816.OFFSET_P1] > NUMBER_OF_RECORDS) {
            ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
        }
        // RETURN DATA
        if (m_pin.isValidated()) {
            // GET DATA
            Util.arrayCopyNonAtomic(m_privateDataArray, (short) (ISO7816.OFFSET_P1 * DATA_RECORD_LENGTH), apdubuf, ISO7816.OFFSET_CDATA, apdubuf[ISO7816.OFFSET_P2]);
            apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, apdubuf[ISO7816.OFFSET_P2]);
        } else {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
    }

    // VERIFY PIN
    void VerifyPIN(APDU apdu) {
        byte[] apdubuf = apdu.getBuffer();
        short dataLen = apdu.setIncomingAndReceive();

        // VERIFY PIN
        if (m_pin.check(apdubuf, ISO7816.OFFSET_CDATA, (byte) dataLen) == false) {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
    }

    // SET PIN
    void SetPin(APDU apdu) {
        byte[] apdubuf = apdu.getBuffer();
        short dataLen = apdu.setIncomingAndReceive();

        // CHECK IF VERIFIED
        if (m_pin.isValidated()) {
            // SET NEW PIN
            m_pin.update(apdubuf, ISO7816.OFFSET_CDATA, (byte) dataLen);
        } else {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
    }
}
