/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.io.j2me.apdu;

import com.sun.midp.security.SecurityToken;
import javax.microedition.io.*;
import com.sun.cardreader.*;
import com.sun.midp.main.Configuration;

import java.io.*;

/**
 * This class provides an interface to the low level APDU protocol 
 * details as described below:
 * 
 * 1. Manage Card power up/reset. 
 * 2. APDU scheduler-Can assign priorities to APDUs to synchronize card 
 * access. Synchronization is required between APDUs being sent by native 
 * telecom software and APDUs sent by J2ME application. The reference 
 * implementation will only have APDUs sent by the J2ME application which 
 * would all have the same priority.  
 * 3. APDU Dispatcher-Support APDU exchange with the card. It has transaction 
 * capabilities to receive the APDUs from applications on the consumer device 
 * and return the responses to the corresponding application. 
 * 4. J2ME applications are oblivious of the logical channel information. 
 * Getting the logical channel for communicating with the smart card 
 * application is the responsibility of the APDUManager which sends 
 * appropriate APDUs to the card to get the logical channels 
 * assigned for a new connection or close channels when the connection is 
 * closed.
 */
public class APDUManager {

    /** SAT selection APDU for testing purposes. */
    private static final String SAT_APDU_PROP = 
        "com.sun.midp.io.j2me.apdu.satselectapdu";
    
    /** Contains references to all CADs or slots. */
    static Cad[] cads;

    /** Objects used to synchronize access to CADs. */
    private static Object[] sync;
    
    /** Saved handle of open SAT connection. */
    private static Handle satHandle = null;
    
    /**
     * This method is called when there is SAT connection creation in 
     * progress. This method checks if the the required CAD objects have
     * been created or not and creates them. It then checks if the SIM
     * application has been selected and can be communicated with.
     * If it is not present, this method throws ConnectionNotFound
     * exception. If SAT connection is in use this method throws
     * IOException.
     *
     * @param slot Slot number
     * @param securityToken Security token for this class
     * @return new connection handle
     * @exception IOException when SIM is not present or
     * connection cannot be established with the card.
     */
    static Handle openSATConnection(int slot, SecurityToken securityToken)
        throws IOException {
        
        Handle h = null;
        
        init();
        
        /*
         * IMPL_NOTE: SATSA spec does not require checking for existence.
         * JDTS test does not close old connection and tries open new one.
         */
if (false) { 
        // Check if old SAT connection still alive
        if (satHandle != null &&
                satHandle.opened &&
                satHandle.cad.getCardSessionId() != -1 &&
                satHandle.cardSessionId == satHandle.cad.getCardSessionId()) {
            throw new IOException("SAT already open");
        }
}
        synchronized (sync[slot]) {
            try {
                if (cads[slot] == null) {
                    cads[slot] = new Cad(slot, securityToken);
                }
            } catch (IOException e) {
                freeResources(slot);
                throw new ConnectionNotFoundException("" + e);
            }
            String satAPDU = Configuration.getProperty(SAT_APDU_PROP);
            if (satAPDU != null) {
                byte[] apdu = new byte[24];
                boolean ok;
                try {
                    int len = parseDottedBytes(satAPDU, apdu, 0);
                    ok = len >= 10 && len <= 22; // 5 bytes hdr + AID + Le
                    int Lc = (apdu[4] & 0xFF);
                    if (ok && (len < Lc + 5 || 
                               len > Lc + 5 + 1 ||
                               apdu[0] != (byte)0x00 ||
                               apdu[1] != (byte)0xA4 ||
                               apdu[2] != (byte)0x04)) {
                        ok = false;
                    }
                    if (ok && len == Lc + 5) {
                        apdu[len] = 0x7F;
                    }
                } catch (NullPointerException npe) {
                    ok = false;
                } catch (IndexOutOfBoundsException iobe) {
                    ok = false;
                } catch (IllegalArgumentException iae) {
                    ok = false;
                }
    
                if (ok) {
                    try {
                        h = new Handle(slot,
                            cads[slot].selectApplication(true, apdu),
                            securityToken);
                    } catch (IOException e) {
                        if (cads[slot].isAlive()) {
                            cads[slot].clean();
                            throw e;
                        }
                        // at this point we need to close socket
                        freeResources(slot);
                        throw e;
                    }
                }
            }
            if (h == null) {
                if (!cads[slot].isAlive()) {
                    throw new ConnectionNotFoundException("SIM not found");
                }
                h = new Handle(slot, 0, securityToken);
            }
            satHandle = h; // Save handle created for SAT connection
            return h;
        }
    }
    
    
    /**
     * Opens a connection to a smart card for reading of ACL.
     * This method is called from <code>initACL</code>
     * method, so it does not need <code>synchronized</code>
     * statement.
     *
     * @param apdu The APDU that will be used for opening
     * @param slot Slot number
     * @param securityToken Security token for this class
     * @return new connection handle
     * @exception IOException when a card is not present or
     * connection cannot be established with the card.
     */
    public static Handle openACLConnection(byte[] apdu, int slot, 
                SecurityToken securityToken)
        throws IOException {
        
        Handle h = null;
        
        init();
        
        try {
            if (cads[slot] == null) {
                cads[slot] = new Cad(slot, securityToken);
            }
        } catch (IOException e) {
            freeResources(slot);
            throw new ConnectionNotFoundException("" + e);
        }
        try {
            return new Handle(slot,
                   cads[slot].selectApplication(true, apdu),
                   securityToken);
        } catch (IOException e) {
            if (cads[slot].isAlive()) {
                cads[slot].clean();
                throw e;
            }
            // at this point we need to close slot
            freeResources(slot);
            throw e;
        }
    }
    
    /**
     * This method is called when there is a connection creation is in 
     * progress
     * and specifically card application selection is required. This method
     * checks if the the required CAD objects have been created or not and
     * creates them. It then calls the selectApplication method on the CAD to
     * select the application. If the card application selection is successful
     * this method gets the channel information from the CAD which it returns
     * to the APDUConnection object.
     *
     * @param selectAPDU byte encoded selection APDU
     * @param slot contains the information regarding the slot the has
     * the card containing the application with which connection needs
     * to be established
     * @param securityToken Security token for this class
     * @return new connection handle
     * @exception IOException when selection is not successful
     */
    public static Handle selectApplication(byte[] selectAPDU, int slot,
                  SecurityToken securityToken) throws IOException {

        init();
        synchronized (sync[slot]) {
            try {
                if (cads[slot] == null) {
                    cads[slot] = new Cad(slot, securityToken);
                }
            } catch (IOException e) {
                freeResources(slot);
                throw new ConnectionNotFoundException("" + e);
            }
            try {
                return new Handle(slot,
                        cads[slot].selectApplication(false, selectAPDU),
                        securityToken);
            } catch (IOException e) {
                if (cads[slot].isAlive()) {
                    cads[slot].clean();
                    throw e;
                }
                // at this point we need to close slot
                freeResources(slot);
                throw e;
            }
        }
    }

    /**
     * Initializes ACL for the slot.
     * @param slot The slot number
     * @param securityToken Security token for this class
     */
    public static void initACL(int slot, SecurityToken securityToken) {
        try {
            init();
            synchronized (sync[slot]) {
                if (cads[slot] == null) {
                    cads[slot] = new Cad(slot, securityToken);
                }
                cads[slot].initACL();
            }
        } catch (IOException e) {} // ignored
    }

    /**
     * Verifies that slot number is correct. Invokes init method
     * if necessary.
     * @param slot the slot number
     * @throws ConnectionNotFoundException if slot number is wrong
     */
    public static void checkSlotNumber(int slot) throws
            ConnectionNotFoundException {

        try {
            init();
        } catch (IOException e) {
            throw new ConnectionNotFoundException(
                "Invalid configuration");
        }
        if (slot < 0 || slot >= getSlotCount()) {
            throw new ConnectionNotFoundException(
                    "Invalid slot identifier: " + slot);
        }
    }
    
    /**
     * Checks if this slot is SAT slot.
     * @param slot the slot number
     * @return SAT check result
     * @throws IOException If an error occured.
     */
    public static boolean isSatSlot(int slot) throws IOException {
        init();
        return SlotFactory.isSatSlot(slot);
    }

    /**
     * Returns the number of slots.
     * @return the number of slots. If error occured it returns 0.
     */
    public static int getSlotCount() {
        try {
            init();
        }
        catch (IOException e) {
            return 0;
        }
        return cads.length;
    }


    /**
     * This method reads the configuration file for number of slots
     * and their parameters and performs necessary initialization.
     * @exception IOException if there are any config problem
     */
    synchronized private static void init() throws IOException {

        if (cads != null) {
            return;
        }
        try {
            SlotFactory.init();
        }
        catch (CardDeviceException e) {
            throw new IOException("Config error: " + e.getMessage());
        }
        int slots = SlotFactory.getCardSlotCount();

        sync = new Object[slots];
        for (int i = 0; i < slots; i++) {
            sync[i] = new Object();
        }

        cads = new Cad[slots];
    }

    /**
     * This method takes in the command APDU in the form of a byte array, 
     * converts it into an Apdu object which facilitates the processing 
     * of APDU data and then calls the exchangeAPDU method in CadClient 
     * to send the APDU to the card. If there are no errors, this method
     * gets the response APDU data from the apdu object and returns that.
     * @param h connection handle
     * @param apduData APDU data in byte array form
     * @return response APDU data in byte array form
     * @exception IOException if there are any IO problems
     */
    public static byte[] exchangeAPDU(Handle h, byte[] apduData)
            throws IOException {

        synchronized (sync[h.slot]) {

            if (cads[h.slot] == null) {
                try {
                    cads[h.slot] = new Cad(h.slot, h.token);
                } catch (IOException e) {
                    throw e;
                }
            }

            if (h.getCardSessionId() == -1) {
                throw new IOException("Card removed");
            }
            if (h.cardSessionId != h.getCardSessionId()) {
                throw new InterruptedIOException();
            }
            if (!h.opened) {
                throw new InterruptedIOException("Connection closed");
            }

            try {

                byte[] response = h.cad.exchangeApdu(h, apduData);

                if (!h.opened) {
                    throw new InterruptedIOException("Connection closed");
                }
                return response;
            } catch (InterruptedIOException ie) {
                throw ie;
            } catch (IOException e) {
                freeResources(h.slot);
                throw e;
            }
        }
    }
    
    /**
     * Closes the connection.
     * @param h connection handle
     * @exception IOException if there are any IO problems
     */
    public static void closeConnection(Handle h) throws IOException {

        h.opened = false;
        synchronized (sync[h.slot]) {
            if (h.cardSessionId != h.getCardSessionId()) {
                throw new IOException();
            }
            h.cad.closeChannel(h.channel);
        }
    }
    
    /**
     * Frees up connection resources if connection was closed because
     * of an error.
     * @param slotNumber the selected slot
     */
    private static void freeResources(int slotNumber) {
    	if (cads[slotNumber] != null) {
                cads[slotNumber].freeSystemResources(); 
    	}
    	cads[slotNumber] = null;
    }
    
    /**
     * Parses string that contains hexadecimal byte values separated by
     * dots. May throw runtime exceptions.
     * @param src source string
     * @param dest destination array
     * @param offset target offset
     * @return number of bytes parsed
     */
    public static int parseDottedBytes(String src, byte[] dest,
                                       int offset) {

        int i = 0;
        int len = 0;
        int j;

        while (i != src.length() + 1) {

            if ((j = src.indexOf('.', i)) == -1) {
                j = src.length();
            }

            int l = Integer.parseInt(src.substring(i, j), 16);
            if (l != (l & 0xff)) {
                throw new IllegalArgumentException();
            }
            dest[offset + len++] = (byte) l;
            i = j + 1;
        }
        return len;
    }
}
