//******************************************************************/
//*   PGMID.        GENERIC SMARTCARD LOGGER.                      */
//*   AUTHOR.       BERND R. FIX   >Y<                             */
//*   DATE WRITTEN. 06/08/10.                                      */
//*   COPYRIGHT.    (C) BY BERND R. FIX. ALL RIGHTS RESERVED.      */
//*                 LICENSED MATERIAL - PROGRAM PROPERTY OF THE    */
//*                 AUTHOR. REFER TO COPYRIGHT INSTRUCTIONS.       */
//*   REMARKS.                                                     */
//******************************************************************/

package y67.smartcard.cardlets.logger;

///////////////////////////////////////////////////////////////////////////////
//import external declarations.
import javacard.framework.*;


///////////////////////////////////////////////////////////////////////////////
/**
 * Logger cardlet: This logger allows to log communication between a terminal
 * application and a smartcard. The ATR of the "original card" (set externally
 * with "ChangeJavaATR" during deployment of the logger card) and the
 * class code of the "original" smartcard application (to prevent clashes with
 * the logger class code) are mostly known prior to logging.<p>
 * 
 * Timing behaviour of the "original" smartcard is not reproduced an can
 * eventually lead to incorrect behaviour of the logger.<p>
 * 
 * The commands are:<p>
 *     <table>
 *       <tr>
 *      <td>CLA</td><td>INS</td><td>ADDR</td><td>LEN<</td><td>DATA</td>
 *      <td>Remarks</td>
 *    </tr>
 *       <tr>
 *      <td>BF</td><td>B0</td><td>addr</td><td>len</td><td> </td>
 *      <td>LOGGER: Read data</td>
 *    </tr>
 *       <tr>
 *      <td>BF</td><td>D6</td><td>addr</td><td>len</td><td>data</td>
 *      <td>LOGGER: write data</td>
 *    </tr>
 *       <tr>
 *      <td>*</td><td>*</td><td>*</td><td>*</td><td>*</td>
 *      <td>Simulate and log communication</td>
 *    </tr>
 *     </table><p>
 * 
 * The request/response list is limited to 255 pairs; so the overall number
 * of command APDUs in a single session should not exceed this limit.<p>
 * 
 * @author    Bernd R. Fix   >Y<
 * @version   1.0
 */
public class Logger extends Applet {

    //=========================================================================
    //    Constants
    //=========================================================================

    static final byte SC_CLA    = (byte) 0xF0;    // smartcard OS class byte
    static final byte LOG_CLA   = (byte) 0xBF;    // logger class code

    //-------------------------------------------------------------------------
    /**
     * codes of INS bytes in the APDU (logger commands)<p>
     */
    static final byte INS_READ  = (byte) 0xB0;    // read buffer
    static final byte INS_WRITE = (byte) 0xD6;    // write buffer

    //-------------------------------------------------------------------------
    /**
     * Length of data buffers used to track communication.<p>
     */
    static final short LENGTH_DATA = 2048;
    
    //=========================================================================
    //    Data buffers for request, response and the last received unknown APDU
    //=========================================================================
    
    /**
     * byte array representing the available request data buffer
     * that is organized according the following scheme:<p>
     * The buffer represents a plain list of elements. Each element starts
     * with an index byte; the index of the last element is 0 (list
     * termination).<p>
     * 
     * The index is follwoed by two state bytes. This allows stateful lookup
     * of requests and to set a new state after the request is processed. The
     * first byte is the state we must be in for successful lookup; a state
     * of "0" means "any state". The second state byte is the state we switch
     * to after request processing; a next value of "FF" means "no state change".<p> 
     * 
     * Following the state bytes is a byte length field that gives the number
     * of data bytes in the follwoing APDU.The rest of each element is a standard
     * APDU. On receiving an APDU, the applet looks up the APDU in this buffer; if
     * a match is found the index of the found entry is used to lookup the correct
     * response and to send it out.<p>
     * 
     * <pre>
     * reqData := {
     *     index   BYTE,   [0]
     *     state   BYTE,   [1]
     *     next    BYTE,   [2]
     *     length  BYTE,   [3]
     *     data    APDU    [4..n]
     * }
     * </pre>
     */
    byte[] reqData;
    
    //-------------------------------------------------------------------------
    /**
     * Length of additional data send along with an request APDU
     * based on INS code.<p> 
     */
    byte[] lcData;
    
    //-------------------------------------------------------------------------
    /**
     * byte array representing the available response data buffer
     * that is organized according the following scheme:<p>
     * The buffer represents a plain list of elements. Each element starts
     * with an index byte; the index of the last element is 0 (list
     * termination).<p>
     * 
     * The index byte is followed by a type byte specifying the
     * meaning of the next word: type 1 means Status word,
     * type 2 means length of following binary data.<p>
     * 
     * A status word of 0000 is interpreted as "throw exception"<p>
     * 
     * <pre>
     *  respData := {
     *      index       BYTE,    [0]
     *      type        BYTE,    [1]
     *      status/len  WORD,    [2]
     *      data        BYTE[]   [4..n]
     *  }
     *  </pre>
     */
    byte[] respData;

    //-------------------------------------------------------------------------
    /**
     * Additional ATR bytes (applet identifier). If not set (= 0),
     * the default value SW_NO_ERROR is returned.<p>
     */
    static short atrAppId = 0;
    
    //-------------------------------------------------------------------------
    /**
     * APDU buffer reference.<p>
     */
    byte[] buffer;
    
    //-------------------------------------------------------------------------
    /*
     * Transient attributes (between method calls)
     */
    byte   state;        // state we are in (reset at power-up/select)
    short  pos;          // index into list
    short  count;        // counter (size)
    short  n;            // loop index
    short  sw;           // status word (usually ISO 7816 status)
    byte[] target;       // target buffer
    
    //=========================================================================
    /**
     * Only this class's install method should create the applet object.<p>
     */
    private Logger () {
        
        // allocate a new byte arrays as data storage.
        reqData  = new byte[LENGTH_DATA];    reqData[0]  = 0;    // known requests
        respData = new byte[LENGTH_DATA];    respData[0] = 0;    // known responses
        lcData   = new byte[258];                                // LC table

        // register the applet.
        register();
    }

    //=========================================================================
    /**
     * Hybrid cardlet: Applet with a 'main()' method.<p>
     * This start-up code is responsible for sending out
     * additional ATR bytes. If no special applet identifier
     * is defined, the value SW_NO_ERROR is returned.<p>
     */
    public static void main (String args[]) {
        
        // send additional ATR bytes (applet identifier).
        ISOException.throwIt (atrAppId != 0 ? atrAppId : ISO7816.SW_NO_ERROR);
    }

    //=========================================================================
    /**
     * Processes an incoming APDU.<p>
     * @param apdu APDU - the incoming APDU
     * @exception ISOException - with the response bytes per ISO 7816-4
     * @see APDU
     */
    public void process (APDU apdu) {
        
        // get access to the APDU data.
        buffer  = apdu.getBuffer();
        byte cla = buffer[ISO7816.OFFSET_CLA];
        byte ins = buffer[ISO7816.OFFSET_INS];

        // check if this is a commmand of the card operating system
        if (cla == SC_CLA) {
            if (ins == ISO7816.INS_SELECT)
                // behave gracefully.
                ISOException.throwIt (ISO7816.SW_NO_ERROR);
            return;
        }

        // decide on class code what commands are handled.
        switch (cla) {
        
            //-----------------------------------------------------------------
            //    Logging applet commands.
            //-----------------------------------------------------------------
            case LOG_CLA:
                switch (ins) {
                    // read request/response list.
                    case INS_READ:
                        readData (apdu);
                        return;
                        
                    // write request/response list.
                    case INS_WRITE:
                        apdu.setIncomingAndReceive();
                        writeData (buffer);
                        return;
                }
                break;
                
            //-----------------------------------------------------------------
            //    "Original" applet behaviour simulation.
            //-----------------------------------------------------------------
            default:
                // lookup expected length of additional data
                count = (short)(lcData [buffer[ISO7816.OFFSET_INS] & 0xFF] &0xFF);
                // special length (=255)?
                if (count == 255)
                    // yes: get length from LC field
                    count = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
                if (count > 0)
                    // receive any additional data
                    apdu.setIncomingAndReceive();
                
                // the following procedure may take some time,
                // so ask the terminal for some extra wait time.
                APDU.waitExtension();
            
                // lookup APDU in list of known requests.
                byte idx = lookupRequest ();
                // request found?
                if (idx == 0)
                    // no: append request to list
                    appendRequest ();
                else {
                    // yes: send response.
                    sw = sendResponse (idx, apdu);
                    if (sw != ISO7816.SW_NO_ERROR)
                        ISOException.throwIt (sw);
                }
                return;
        }
        // throw an exception if the instruction is unknown (not handled yet).
        ISOException.throwIt (ISO7816.SW_INS_NOT_SUPPORTED);
    }
    
    //=========================================================================
    //    Behaviour simulation
    //=========================================================================
    
    /**
     * Lookup request in list of known requests.<p>
     * Global param buffer byte[] - incoming request
     * @return byte - index of request (or 0 if not found) 
     */
    private byte lookupRequest () {
        
        // traverse list to find request entry.
        pos = 0;
        while (reqData [pos] != 0) {

            // get size of entry
            short size = (short)((reqData[pos+3] & 0xFF) + 5);
            
            // are we in correct state?
            if (reqData[pos+1] == 0 || state == reqData[pos+1]) {
            
                // check for identical APDUs
                sw = 0;
                for (n = 0; n < size; n++)
                    if (buffer[n] != reqData[(short)(pos+n+4)]) {
                        sw = size;
                        break;
                    }
                // do APDUs match?
                if (sw == 0) {
                    // yes: set next state.
                    if (reqData[pos+2] != (byte)(0xFF))
                        state = reqData[pos+2];
                    // return index
                    return reqData [pos];
                }
            }
            // try next element
            pos += (size+4);
        }
        // report lookup failure (unknown request)
        return 0;
    }

    //-------------------------------------------------------------------------
    /**
     * Add new request to the end of the list.
     * Global param buffer byte[] - buffer holding incoming APDU
     * Global param count short - number of additional data bytes
     */
    private void appendRequest () {

        // we are adding to the end of the list,
        // so find end of current list first.
           pos = 0;
        byte idx = 0;
        while (reqData [pos] != 0) {
            // remember last valid index found.
            idx = reqData [pos];
            // total size of entry is (9 + datalength);
            // 9 = sizeof(Hdr) + sizeof(APDU)
            pos += (short) ((reqData[pos+3] & 0xFF) + 9);
        }

        // fill in header data
        reqData [pos] = ++idx;          // next available index
        reqData [pos+1] = state;        // current state
        reqData [pos+2] = (byte)0xFF;   // "no state change"
        reqData [pos+3] = (byte)count;  // size of additional data
        
        // save content
        for (n = 0; n < count+5; n++)
            reqData [(short)(pos+n+4)] = buffer[n];
        pos += (count + 9);
        reqData [pos] = 0;
    }
    
    //-------------------------------------------------------------------------
    /**
     * Send response (status, data).<p>
     * @param idx byte - index of response
     * @param apdu APDU - the incoming APDU
     * @return short - status code
     */
    private short sendResponse (byte idx, APDU apdu) {
        
        // traverse list to find response entry.
        pos = 0;
        while (respData [pos] != 0) {
            
            // get type of entry and next word
            byte type = respData [pos+1]; 
            n = getWord (respData, (short)(pos+2));
            count = (type == 1 ? 4 : (short)(n+4));
            
            // index found?
            if (respData [pos] == idx) {
                
                // check type of entry.
                if (type == 1) {
                    if (n == 0) {
                        // provoke exception
                        apdu.setIncomingAndReceive();
                        apdu.setIncomingAndReceive();
                    }
                    // return word as status.
                    return n;
                }
                
                // send out response.
                apdu.setOutgoing();
                apdu.setOutgoingLength (n);
                apdu.sendBytesLong (respData, (short)(pos+4), n);
                
                // report success.
                return ISO7816.SW_NO_ERROR;
            }
            // advance to next entry
            pos += count;
        }
        // report failure (no response found).
           return ISO7816.SW_DATA_INVALID;
    }

    //=========================================================================
    //    Logger command buffer access.
    //=========================================================================

    /**
     * Read request/response data.<p>
     * @param apdu APDU - incoming APDU
     */
    private void readData (APDU apdu) {

        // get addr(offset) into buffer and length
        // of data block to be read.
        buffer = apdu.getBuffer();
        pos    = getWord (buffer, ISO7816.OFFSET_P1);
        count  = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
        if (count == 0) count = 256;
        
        // decide on address which target buffer to use.
        prepareTarget();
        if (target == null) {
            // special treatment if we are reading 'atrAppId'.
            pos    = (short)256;
            count  = (short)2;
            target = lcData;
            lcData[256] = (byte)((atrAppId >> 8) & 0xFF);
            lcData[257] = (byte)( atrAppId       & 0xFF);
        }

        // send data
        apdu.setOutgoing();
        apdu.setOutgoingLength (count);
        apdu.sendBytesLong (target, pos, count);
    }

    //-------------------------------------------------------------------------
    /**
     * Write request/response data.<p>!Cyberflex
     * @param buffer byte[] - incoming command APDU
     */
    private void writeData (byte[] buffer) {
        
        // get addr(offset) into buffer and length
        // of data block to be stored.
        pos = getWord (buffer, ISO7816.OFFSET_P1);
        count  = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF);
        if (count == 0) count = 256;
        
        // decide on address which target buffer to use.
        prepareTarget();
        if (target == null) {
            atrAppId = getWord (buffer, ISO7816.OFFSET_CDATA);
            return;
        }
        
        // transfer data
        for (n = 0; n < count; n++)
            target [(short)(pos + n)] = buffer [(short)(ISO7816.OFFSET_CDATA + n)];
    }

    //-------------------------------------------------------------------------
    /**
     * Set 'target' and 'pos' based on initial index value.<p>
     */
    private void prepareTarget () {
        
        target = reqData;                // [0000 - 07FF]  Requests
        if (pos > LENGTH_DATA-1) {
            pos -= LENGTH_DATA;
            target = respData;          // [0800 - 0FFF]  Responses
            if (pos > LENGTH_DATA-1) {
                pos -= LENGTH_DATA;
                target = lcData;        // [1000 - 10FF]  LC table
                if (pos > 256) {
                    pos -= 256;
                    target = null;      // [1100 - 1101]  LC table
                }
            }
        }
    }

    //=========================================================================
    //    Cardlet control methods
    //=========================================================================
    
    //-------------------------------------------------------------------------
    /**
     * Installs this applet.<p>
     * @param apdu APDU - install APDU
     * @param bOffset short - the starting offset in bArray
     * @param bLength byte - the length in bytes of the parameter data in bArray
     */
    public static void install (APDU apdu) {
        new Logger ();
    }

    //-------------------------------------------------------------------------
    /**
     * select the applet for execution.<p>
     * @return boolean - true, if applet can be selected for execution.
     */
    public boolean select () {
        
        // reset state 
        state = (byte)0;
        // report success
        return true;
    }

    //-------------------------------------------------------------------------
    /**
     * de-select the applet.<p>
     */
    public void deselect () {
    }

    //=========================================================================
    //    Helper methods.
    //=========================================================================
    
    //-------------------------------------------------------------------------
    /**
     * Get a short value from data buffer.<p>
     * @param buffer byte[] - data buffer
     * @param pos short - offset to word
     * @return short - value read
     */
    private static short getWord (byte[] buffer, short pos) {
        short v1 = (short)(buffer[pos  ] & 0xFF);
        short v2 = (short)(buffer[pos+1] & 0xFF);
        return (short)((v1 << 8) | v2);
    }
}