/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package SimplePIN;

import java.util.List;
import javax.smartcardio.*;

/**
 *
 * @author xsvenda
 */
public class CardMngr {

    CardTerminal m_terminal = null;
    CardChannel m_channel = null;
    Card m_card = null;
    private final byte selectCM[] = {
        (byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x07, (byte) 0xa0, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x18, (byte) 0x43, (byte) 0x4d};
    private final byte selectApplet[] = {
        (byte) 0x00, (byte) 0xa4, (byte) 0x04, (byte) 0x00, (byte) 0x0a, (byte) 0x53, (byte) 0x69, (byte) 0x6d,
        (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x50, (byte) 0x49, (byte) 0x4e, (byte) 0x01};
    private final byte OFFSET_CLA = 0x00;
    private final byte OFFSET_INS = 0x01;
    private final byte OFFSET_P1 = 0x02;
    private final byte OFFSET_P2 = 0x03;
    private final byte OFFSET_LC = 0x04;
    private final byte OFFSET_DATA = 0x05;
    private final byte HEADER_LENGTH = 0x05;
    public final static short DATA_RECORD_LENGTH = (short) 0x80; // 128B per record
    public final static short NUMBER_OF_RECORDS = (short) 0x0a; // 10 records

    public boolean ConnectToCard() throws Exception {
        // TRY ALL READERS, FIND FIRST SELECTABLE
        List terminalList = GetReaderList();

        if (terminalList.size()==0)
            System.out.println("No terminals found");

        //List numbers of Card readers
        boolean cardFound = false;
        for (int i = 0; i < terminalList.size(); i++) {
            System.out.println(i + " : " + terminalList.get(i));
            m_terminal = (CardTerminal) terminalList.get(i);
            if (m_terminal.isCardPresent()) {
                m_card = m_terminal.connect("T=0");
                System.out.println("card: " + m_card);
                m_channel = m_card.getBasicChannel();

                //reset the card
                ATR atr = m_card.getATR();
                System.out.println(atr.toString());

                // SELECT APPLET
                ResponseAPDU resp = sendAPDU(selectApplet);
                if (resp.getSW() != 0x9000) {
                    System.out.println("No card found.");
                } else {
                    // CARD FOUND
                    cardFound = true;
                    break;
                }
            }
        }

        return cardFound;
    }

    public void DisconnectFromCard() throws Exception {
        if (m_card != null) {
            m_card.disconnect(false);
            m_card = null;
        }
    }

    public byte[] GetPublicData(byte dataSlot, byte dataLength) throws Exception {
        byte[] data = null;

        // GET PUBLIC DATA
        byte apdu[] = new byte[HEADER_LENGTH];
        apdu[OFFSET_CLA] = (byte) 0xB2;
        apdu[OFFSET_INS] = (byte) 0x30;
        apdu[OFFSET_P1] = dataSlot;
        apdu[OFFSET_P2] = dataLength;
        apdu[OFFSET_LC] = 0x00;

        ResponseAPDU resp = sendAPDU(apdu);
        if (resp.getSW() != 0x9000) {
            System.out.println("Fail to obtain data");
            return null;
        } else {
            // SET READ DATA
            byte temp[] = resp.getBytes();
            data = new byte[temp.length - 2];
            for (int i = 0; i < temp.length - 2; i++) {
                data[i] = temp[i];
            }
        }

        return data;
    }

    public boolean SetPublicData(byte dataSlot, byte[] data) throws Exception {
        boolean status = true;

        // SET PUBLIC DATA
        byte apdu[] = new byte[HEADER_LENGTH + data.length];
        apdu[OFFSET_CLA] = (byte) 0xB2;
        apdu[OFFSET_INS] = (byte) 0x31;
        apdu[OFFSET_P1] = dataSlot;
        apdu[OFFSET_P2] = 0x00;
        apdu[OFFSET_LC] = (byte) data.length;
        for (int i = 0; i < data.length; i++) {
            apdu[i + OFFSET_DATA] = data[i];
        }

        ResponseAPDU resp = sendAPDU(apdu);
        if (resp.getSW() != 0x9000) {
            System.out.println("Fail to set data");
            return false;
        }

        return status;
    }

    public byte[] GetPrivateData(byte dataSlot, byte dataLength) throws Exception {
        byte[] data = null;

        // GET PUBLIC DATA
        byte apdu[] = new byte[HEADER_LENGTH];
        apdu[OFFSET_CLA] = (byte) 0xB2;
        apdu[OFFSET_INS] = (byte) 0x32;
        apdu[OFFSET_P1] = dataSlot;
        apdu[OFFSET_P2] = dataLength;
        apdu[OFFSET_LC] = 0x00;

        ResponseAPDU resp = sendAPDU(apdu);
        if (resp.getSW() != 0x9000) {
            System.out.println("Fail to obtain data");
            return null;
        } else {
            // SET READ DATA
            byte temp[] = resp.getBytes();
            data = new byte[temp.length - 2];
            for (int i = 0; i < temp.length - 2; i++) {
                data[i] = temp[i];
            }
        }

        return data;
    }

    public boolean SetPrivateData(byte dataSlot, byte[] data) throws Exception {
        boolean status = true;

        // SET PUBLIC DATA
        byte apdu[] = new byte[HEADER_LENGTH + data.length];
        apdu[OFFSET_CLA] = (byte) 0xB2;
        apdu[OFFSET_INS] = (byte) 0x33;
        apdu[OFFSET_P1] = dataSlot;
        apdu[OFFSET_P2] = 0x00;
        apdu[OFFSET_LC] = (byte) data.length;
        for (int i = 0; i < data.length; i++) {
            apdu[i + OFFSET_DATA] = data[i];
        }

        ResponseAPDU resp = sendAPDU(apdu);
        if (resp.getSW() != 0x9000) {
            System.out.println("Fail to set data");
            return false;
        }

        return status;
    }

    public boolean SetPin(byte pin[]) throws Exception {
        boolean status = true;

        // VERIFY PIN
        byte apdu[] = new byte[HEADER_LENGTH + pin.length];
        apdu[OFFSET_CLA] = (byte) 0xB2;
        apdu[OFFSET_INS] = (byte) 0x34;
        apdu[OFFSET_P1] = 0x00;
        apdu[OFFSET_P2] = 0x00;
        apdu[OFFSET_LC] = (byte) pin.length;
        for (int i = 0; i < pin.length; i++) {
            apdu[i + OFFSET_DATA] = pin[i];
        }

        ResponseAPDU resp = sendAPDU(apdu);
        if (resp.getSW() != 0x9000) {
            System.out.println("Fail to set PIN");
            return false;
        }

        return status;
    }

    public boolean VerifyPin(byte pin[]) throws Exception {
        boolean status = true;

        // VERIFY PIN
        byte apdu[] = new byte[HEADER_LENGTH + pin.length];
        apdu[OFFSET_CLA] = (byte) 0xB2;
        apdu[OFFSET_INS] = (byte) 0x35;
        apdu[OFFSET_P1] = 0x00;
        apdu[OFFSET_P2] = 0x00;
        apdu[OFFSET_LC] = (byte) pin.length;
        for (int i = 0; i < pin.length; i++) {
            apdu[i + OFFSET_DATA] = pin[i];
        }

        ResponseAPDU resp = sendAPDU(apdu);
        if (resp.getSW() != 0x9000) {
            System.out.println("Fail to verify PIN");
            return false;
        }

        return status;
    }

    public List GetReaderList() {
        try {
            TerminalFactory factory = TerminalFactory.getDefault();
            List readersList = factory.terminals().list();
            return readersList;
        } catch (Exception ex) {
            System.out.println("Exception : " + ex);
            return null;
        }
    }

    private ResponseAPDU sendAPDU(byte apdu[]) throws Exception {
        CommandAPDU commandAPDU = new CommandAPDU(apdu);

        System.out.println(">>>>");
        System.out.println(commandAPDU);

        System.out.println(bytesToHex(commandAPDU.getBytes()));

        ResponseAPDU responseAPDU = m_channel.transmit(commandAPDU);

        System.out.println(responseAPDU);
        System.out.println(bytesToHex(responseAPDU.getBytes()));

        if (responseAPDU.getSW1() == (byte) 0x61) {
            CommandAPDU apduToSend = new CommandAPDU((byte) 0x00,
                    (byte) 0xC0, (byte) 0x00, (byte) 0x00,
                    (int) responseAPDU.getSW1());

            responseAPDU = m_channel.transmit(apduToSend);
            System.out.println(bytesToHex(responseAPDU.getBytes()));
        }

        System.out.println("<<<<");

        return (responseAPDU);
    }

    public String byteToHex(byte data) {
        StringBuffer buf = new StringBuffer();
        buf.append(toHexChar((data >>> 4) & 0x0F));
        buf.append(toHexChar(data & 0x0F));
        return buf.toString();
    }

    public char toHexChar(int i) {
        if ((0 <= i) && (i <= 9)) {
            return (char) ('0' + i);
        } else {
            return (char) ('a' + (i - 10));
        }
    }

    public String bytesToHex(byte[] data) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < data.length; i++) {
            buf.append(byteToHex(data[i]));
            buf.append(" ");
        }
        return (buf.toString());
    }
}
