/*
 *   
 *
 * 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 java.io.IOException;
import java.io.InterruptedIOException;
import javax.microedition.io.ConnectionNotFoundException;
import com.sun.cardreader.*;
import com.sun.satsa.acl.*;

/**
 * The Cad class maintains the context for the client (terminal) side
 * of the terminal CAD connection. This class is responsible for sending
 * APDUs to the card, receiving back the responses and automatically
 * receiving a response if status '61 XX' is  received from the
 * card.
 */
class Cad {
   
    /**
     * Since at any time once channel always has to be open with the card
     * and that channel is generally the basic channel. If the basic
     * channel is not in use, the APDU manager can select the next
     * application on that channel.
     * This variable shows if basic channel is in use currently or not.
     */
    private boolean basicChannelInUse;

    /** 
     * Indicates if slot is the SAT slot and a SIM is installed on it.
     * This flag is set in Cad.selectApplication.
     * It is cleared when Cad.isAlive detects the death of connection or
     * Cad.getATR is not able to reset the card.
     */
    boolean SIMPresent;

    /** APDU for logical channel allocation. */
    private byte[] getChannelAPDU;

    /** APDU for closing a logical channel. */
    private byte[] closeChannelAPDU;

    /** APDU for GET RESPONSE command. */
    private byte[] getResponseAPDU;

    /** APDU for ISO7816-4 'case 2' command. Length is 5 bytes. */
    private byte[] case2APDU;

    /** FCI information received from selected applications. */
    private byte[] FCI;

    /** CardSlot created for Cad. */
    private CardSlot cardSlot;

    /** Stuff buffer for received data. */
    private TempByteArray resp_buffer = new TempByteArray();
    
    /** Stuff buffer for output data. */
    private TempByteArray output_buffer = new TempByteArray();

    /** Slot number */
    private int slotN;

    /**
     * Constructor for CADClient.
     * @param slot Global slot number
     * @param securityToken Security token for this class
     * @throws IOException if there any problems in opening the socket or
     * streams or powering up the card.
     */
    Cad(int slot, SecurityToken securityToken) throws IOException {
        
        try {
            cardSlot = SlotFactory.getCardSlot(slot, securityToken);
            if (cardSlot == null) {
                throw new IOException("Slot factory could not create a slot");
            }
        }
        catch (CardDeviceException e) { 
            throw new IOException("Config error: " + e.getMessage());
        }
        slotN = slot;
        getChannelAPDU = new byte[] {0, 0x70, 0, 0, 1};
        closeChannelAPDU = new byte[] {0, 0x70, (byte) 0x80, 0};
        getResponseAPDU = new byte[] {0, (byte)0xC0, 0, 0, 0};
        case2APDU = new byte[5];
    }
    
    /**
     * Exchange an Apdu with a CAD.
     * @param h Handle of the connection.
     * @param commandAPDU APDU data in byte array form.
     * @return response APDU data in byte array form.
     * @exception InterruptedIOException if connection was closed in the
     * other thread.
     * @exception IOException if a communication error happens while
     * communicating with the CAD.
     */
    byte[] exchangeApdu(Handle h, byte[] commandAPDU) throws IOException {
        byte[] result;
        int result_length = 0;
        
        int Lc, Le;
        
        Lc = 0;
        Le = commandAPDU.length == 4 ? -1 : commandAPDU[4] & 0xFF; 

        if (commandAPDU.length > 5) {

            Lc = Le;

            if (5 + Lc > commandAPDU.length) {
                throw new IllegalArgumentException("Malformed APDU");
            }

            Le = 5 + Lc == commandAPDU.length ?
                    -1 : commandAPDU[5 + Lc] & 0xFF;
        }
        
        if (Le == 0) {
            Le = 256;
        }
        if (Le == -1) {
            Le = 0; 
        }
        
        int cla = commandAPDU[0] & 0xf0;
        int channel = cla != 0 && (cla < 0x80 || cla > 0xA0) ?
                      0 : commandAPDU[0] & 3;

        cardSlot.lockSlot();
        if (Lc == 0 && commandAPDU.length > 5) { // (case 4 & Lc==0) ==> case 2
            System.arraycopy(commandAPDU, 0, case2APDU, 0, 4);
            case2APDU[4] = commandAPDU[5];
            commandAPDU = case2APDU;
        }
        while (true) {
            int bytes_read;
            int sw1, sw2;
            
            bytes_read = 
                cardSlot.xferData(commandAPDU, resp_buffer.ensure(Le + 2));
            if (bytes_read < 2) {
                cardSlot.unlockSlot();
                throw new IOException("Bad response");
            }
            if (h != null && !h.opened) {
                cardSlot.unlockSlot();
                throw new InterruptedIOException("Connection closed");
            }
            sw1 = resp_buffer.val(bytes_read - 2) & 0xFF;
            sw2 = resp_buffer.val(bytes_read - 1) & 0xFF;
            
            // Correct wrong Le
             /* IMPL_NOTE: We shell make sure that Le!=sw2 but that is out of spec */
            if (bytes_read == 2 &&  
                    sw1 == 0x6C && sw2 != 0x00 &&
                    Le != 0) {
                
                Le = sw2;
                if (commandAPDU.length == 4) { // case 1
                    System.arraycopy(commandAPDU, 0, case2APDU, 0, 
                                     commandAPDU.length);
                    case2APDU[4] = (byte)(Le & 0xFF);
                    commandAPDU = case2APDU;
                } else 
                if (commandAPDU.length == 5) { // case 2
                    if (commandAPDU != case2APDU) {
                        System.arraycopy(commandAPDU, 0, case2APDU, 0, 4);
                        commandAPDU = case2APDU;
                    }
                    case2APDU[4] = (byte)(Le & 0xFF);
                } else {
                    if (commandAPDU.length == 5 + Lc) { // case 3
                        byte[] temp = new byte[5 + Lc + 1];
                        System.arraycopy(commandAPDU, 0, temp, 0, 5 + Lc);
                        commandAPDU = temp;
                    }
                    commandAPDU[5 + Lc] = (byte)(Le & 0xFF);
                }
                result_length = 0;
                continue;
            }
            if (result_length >= 2) {
                result_length -= 2; // Delete previous status bytes
            }
            System.arraycopy(resp_buffer.data(), 0, 
                output_buffer.expand(result_length + bytes_read), 
                    result_length, bytes_read);
            result_length += bytes_read;
            
            if (Le == 0 || (sw1 != 0x61 &&
                (channel != 0 || !SIMPresent ||
                 (sw1 != 0x62 && sw1 != 0x63 &&
                  sw1 != 0x9E && sw1 != 0x9F)))) {
                break;
            }

            Le = sw1 == 0x62 || sw1 == 0x63 ? 0 : sw2;
            
            commandAPDU = getResponseAPDU;
            commandAPDU[0] = (byte) channel;
            commandAPDU[4] = (byte)Le;
            if (Le == 0) {
                Le = 256;
            }
        }
        cardSlot.unlockSlot();
        result = new byte[result_length];
        System.arraycopy(output_buffer.data(), 0, result, 0, result_length);
        return result;
    }
    
    /**
     * This method returns the ATR received from the card that this
     * CadClient object is used to communicate with.
     * @return ATR information received from the card at startup or
     * reset. In case of I/O troubles returns null.
     */
    byte[] getATR() {
        byte[] result;
        
        if (!isAlive()) {
            result = null;
        } else {
            result = cardSlot.getATR();
        }
        if (result == null) {
            SIMPresent = false;
        }
        return result;
    }

    /**
     * This method returns the FCI received from the card.
     * @return FCI information received from the card at 
     * selectApplication.
     */
    byte[] getFCI() {
        return FCI;
    }

    /**
     * Returns the card session identifier.
     * This number is different for different card sessions.
     * @return the card session identifier
     */
    public int getCardSessionId() {
        return cardSlot.getCardSessionId();
    }
    
    /**
     * This method is called when there is a connection creation is
     * in progress
     * and specifically card application selection is required.
     * This method also does the channel management part. If basic channel
     * is available for use, this method tries to select the target
     * application
     * on the basic channel. Otherwise, it requests the card for the channel
     * and attempts to select the application on that particular channel.
     *
     * If the selection is
     * successful, this method returns the logical channel reserved for
     * communicating with the selected card application.
     * @param forSAT true if selection is made for SAT connection
     * @param selectAPDU byte encoded selection APDU
     * @exception ConnectionNotFoundException when selection is not successful
     * @exception IOException if no channel is available for communication
     * establishment or there are communication problems.
     * @exception IllegalArgumentException if bad APDU provided.
     * @return response apdu from the select application request
     */
    int selectApplication(boolean forSAT, byte[] selectAPDU) 
            throws IOException {

        int channel;

        // Test if 'POWER UP' is needed or a card was changed
        if (!isAlive()) {
            if (cardSlot.isSAT()) {
                throw new ConnectionNotFoundException("SIM not found");
            } else {
                throw new ConnectionNotFoundException("SmartCard not found");
            }
        }

        if (cardSlot.initConnection()) {
            clean();
        }
        if (cardSlot.isSAT()) {
            SIMPresent = true;
        } else {
            SIMPresent = false;
        }
        if (!forSAT && (basicChannelInUse || SIMPresent)) {
            // get another channel for communication.
            byte[] response = exchangeApdu(null, getChannelAPDU);
            if (response.length  == 2) {
                // just got back the status word
                throw new IOException("No logical channel available");
            }
            // new channel number is in the first byte of response
            channel = response[0];
        } else {
            basicChannelInUse = true;
            channel = 0;
        }
        
        selectAPDU[0] = (byte)((selectAPDU[0] & 0xFC) | channel);

        byte[] result = exchangeApdu(null, selectAPDU);

        int sw1 = result[result.length - 2] & 0xFF;
        int sw2 = result[result.length - 1] & 0xFF;
        if ((sw1 << 8) + sw2 != 0x9000) {
            closeChannel(channel);
            throw new ConnectionNotFoundException(
                    "Card application selection failed");
        }
        FCI = result;
        return channel;
    }

    /**
     * This method is used to close connection with the card. If the
     * channel number passed to this method is for basic channel then
     * the basic channel is marked as available and nothing is
     * communicated with the card.
     * If the channel is not the basic channel, a request to close the
     * channel is sent to the card.
     * @param channel channel number
     * @exception IOException if a communication error happens
     */
    void closeChannel(int channel) throws IOException {
        
        if (channel == 0) {
            basicChannelInUse = false;
            return;
        }

        try {
            closeChannelAPDU[3] = (byte) channel;
            exchangeApdu(null, closeChannelAPDU);
        } catch (IOException ioException) {
            throw new IOException("Error closing connection");
        }
    }

    /**
     * Checks if the the connection is still live or not.
     *
     * @return <code>true</code> if the connection is alive
     */
    boolean isAlive() {
        if (cardSlot.isAlive()) {
            return true;
        } else {
            SIMPresent = false;
            return false;
        }
    }
    
    /**
     * Initializes ACL for the slot.
     */
    void initACL() {
        cardSlot.initACL();
    }
    
    /**
     * Frees up the resource that was used by this slot.
     * This method is used only in case of error.
     * IOException is ignored.
     */
    void freeSystemResources() {
        try {
            cardSlot.closeSlot();
        }
        catch (IOException e) {}
        
    }
    
    /**
     * Does cleaning of the object.
     */
    void clean() {
        basicChannelInUse = false;
        SIMPresent = false;
        FCI = null;
    }
    
    /**
     * The TempByteArray class contains temporary byte array for  
     * output buffer during APDUExchange.
     */
    class TempByteArray {
        /**
         * The byte array.
         */
        private byte[] value;
        
        /**
         * The constructor.
         * @param size Initial size.
         */
        TempByteArray(int size) {
            value = new byte[size];
        }
        
        /**
         * Default constructor.
         */
        TempByteArray() {
            value = new byte[256 + 2];
        }
        
        /**
         * Gets value of byte by index.
         * @param i Index.
         * @return Value for i.
         */
        byte val(int i) {
            return value[i];
        }
        
        /**
         * Gets all data as byte array.
         * @return Buffer as byte array.
         */
        byte[] data() {
            return value;
        }
        
        /**
         * Expands the buffer when it too small to hold newsize bytes.
         * If buffer contains data they can be lost.
         * @param newsize Minimum size of buffer.
         * @return Buffer as byte array.
         */
        byte[] ensure(int newsize) {
            if (value.length < newsize) {
                value = new byte[newsize];
            }
            return value;
        }
        
        /**
         * Expands the buffer when it too small to hold newsize bytes.
         * @param newsize Minimum size of buffer.
         * @return Buffer as byte array.
         */
        byte[] expand(int newsize) {
            if (value.length < newsize) {
                byte[] temp = new byte[newsize];
                System.arraycopy(value, 0, temp, 0, value.length);
                value = temp;
            }
            return value;
        }
    }
}
