FileDocCategorySizeDatePackage
JavaPurse.javaAPI DocJava Card36380Wed Mar 22 21:07:24 GMT 2006package com.sun.javacard.samples.JavaPurse

JavaPurse.java

/*
 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 * @(#)JavaPurse.java	1.17 06/01/04
 */

package	com.sun.javacard.samples.JavaPurse;

import javacard.framework.*;
import com.sun.javacard.samples.SampleLibrary.JavaLoyaltyInterface;

/**
 * This class is intended to demonstrate how an electronic cash application
 * might be developed using Java Card
 * <p>See <em>Java Card(TM) Development Kit User's Guide</em> 
 * for details.
 *
 * @author Vadim Temkin
 */
public class JavaPurse extends Applet  {
    /*
     *
     * Constants
     *
     */
    final static byte  VERIFY = (byte)0x20; //INS value for ISO 7816-4 VERIFY command
    final static byte  READ = (byte)0xB2;	//INS value for ISO 7816-4 READ RECORD command
    final static byte  INITIALIZE_TRANSACTION = (byte)0x20; //INS byte for Initialize Transaction
    final static byte  COMPLETE_TRANSACTION = (byte)0x22; //INS byte for Complete Transaction
    final static byte  INITIALIZE_UPDATE = (byte)0x24; //INS byte for Initialize Parameter Update
    final static byte  COMPLETE_UPDATE = (byte)0x26; //INS byte for Complete Parameter Update
    final static byte  CREDIT = (byte)0x01;	//P1 byte for Credit in Initialize Transaction
    final static byte  DEBIT = (byte)0x02;	//P1 byte for Debit in Initialize Transaction
    final static byte  MASTER_PIN = (byte)0x81; //P2 byte for Master PIN in Verify
    final static byte  USER_PIN = (byte)0x82; //P2 byte for User PIN in Verify
    
    final static short	SW_CREDIT_TOO_HIGH = (short)0x9101;//SW bytes for Credit Too High condition
    final static short	SW_NOT_ENOUGH_FUNDS = (short)0x9102;//SW bytes for Not Enough Funds	condition
    final static short	SW_AMOUNT_TOO_HIGH = (short)0x9103;//SW bytes for Amount Too High condition
    final static short	SW_COMMAND_OUT_OF_SEQUENCE = (short)0x9104;//SW bytes for Command out of Sequence
    final static short	SW_WRONG_SIGNATURE = (short)0x9105;//SW bytes for Wrong Signature condition
    final static short 	SW_PIN_FAILED = (short)0x69C0;//SW bytes for PIN Failed condition
    //The last nibble is replaced with the
    //number of remaining tries
    
    final static byte  	LC_IT	 	= 10;	//Lc byte for Initialize Transaction
    final static byte  	LC_CT	 	= 13;	//Lc byte for Complete Transaction
    final static byte  	LC_CU_MIN 	= 18;	//Lc byte for Complete Update
    final static byte 	CAD_ID_OFFSET   = 7;	//Offset for CAD ID in Process Initialize Transaction
    final static short	DATE_LENGTH = 3;	//Length of ExpDate array
    final static short	DATETIME_LENGTH = 5;//Length of DateTime array
    final static short	ID_LENGTH	= 4;	//Length for CAD ID and Purse ID arrays
    final static short	SHORT_LENGTH= 2;	//Length of a short value for offset computations
    final static short	START		= 0;	//For offset computations
    final static short	SIGNATURE_LENGTH
    = 8;	//Length of signatures
    final static short	MAX_LOYALTY = 4;	//Max number of loyalty applets
    
    // transientShorts array indices
    final static byte  	TN_IX = 0;
    final static byte  	NEW_BALANCE_IX=(byte)TN_IX+1;
    final static byte 	CURRENT_BALANCE_IX=(byte)NEW_BALANCE_IX+1;
    final static byte 	AMOUNT_IX=(byte)CURRENT_BALANCE_IX+1;
    final static byte   TRANSACTION_TYPE_IX=(byte)AMOUNT_IX+1;
    final static byte	SELECTED_FILE_IX=(byte)TRANSACTION_TYPE_IX+1;
    final static byte   NUM_TRANSIENT_SHORTS=(byte)SELECTED_FILE_IX+1;
    
    // transientBools array indices
    final static byte  	TRANSACTION_INITIALIZED=0;
    final static byte  	UPDATE_INITIALIZED=(byte)TRANSACTION_INITIALIZED+1;
    final static byte   NUM_TRANSIENT_BOOLS=(byte)UPDATE_INITIALIZED+1;
    
    // constants for response to applet SELECT command
    private final static byte FCI_TEMPLATE_TAG = (byte)0x6F;
    private final static byte FCI_AID_TAG = (byte)0x84;
    private static byte[] FCI_PROPERIETARY = {
        (byte)0xA5,   // tag
        (byte)0x01,   // length
        (byte)0x42 }; // value 
    
    private ParametersFile 		parametersFile;
    private CyclicFile 			transactionLogFile;
    private short 	TN;
    private short 	PUN;
    private boolean isPersonalized; //set to true when Master PIN is updated first time
    /*
     * File System emulation constants
     */
    final static short PARAMETERS_FID =	(short) 0x9102;
    final static short TRANSACTION_LOG_FID = (short) 0x9103;
    final static short BALANCES_FID	= (short) 0x9104;
    final static byte  FID_BYTE = (byte) 0x91;
    final static byte  TRANSACTION_RECORD_LENGTH = 18;
    final static byte  TRANSACTION_RECORD_NUMBER = 10;
    final static byte  BALANCES_RECORD_LENGTH = 6;
    final static byte  BALANCES_RECORD_NUMBER = 1;
    final static byte  PARAMETERS_RECORD_NUMBER = 11;
    final static byte  OFFSET_BAL_CURRENT =	0;
    final static byte  OFFSET_BAL_MAX =	2;
    final static byte  OFFSET_AMOUNT_MAX = 4;
    final static byte  NUMBER_OF_FILES = 3;
    
    
    private OwnerPIN masterPIN;
    private OwnerPIN userPIN;
    
    /*
     * Tags for TLV records in Complete Parameter Update C-APDU.
     */
    final static byte MASTER_PIN_UPDATE 	= (byte)0xC1;
    final static byte USER_PIN_UPDATE 		= (byte)0xC2;
    final static byte EXP_DATE_UPDATE 		= (byte)0xC5;
    final static byte PURSE_ID_UPDATE 		= (byte)0xC6;
    final static byte MAX_BAL_UPDATE 		= (byte)0xC7;
    final static byte MAX_M_UPDATE 			= (byte)0xC8;
    final static byte VERSION_UPDATE 		= (byte)0xC9;
    final static byte LOYALTY1_UPDATE 		= (byte)0xCA;
    final static byte LOYALTY2_UPDATE 		= (byte)0xCB;
    final static byte LOYALTY3_UPDATE 		= (byte)0xCC;
    final static byte LOYALTY4_UPDATE 		= (byte)0xCD;
    
    final static short TLV_OFFSET = 13; //Offset of TLV in Complete Parameter Update
    
    
    /*
     *
     * Various and sundry byte arrays
     *
     */
    
    private byte[] CAD_ID_array;
    private byte[] byteArray8;
    private short[] transientShorts;
    private boolean[] transientBools;
    private byte[] ID_Purse;
    private byte[] ExpDate;
    private byte[] balancesRecord;
    
    /*
     * The two associated arrays to represent Loyalty Applets.
     * If value of loyaltyCAD matches first 2 bytes of CAD ID for transaction
     * the grantPoints method of corresponding loyalty Shareable Interface Object
     * is called. These arrays are populated by Parameter Update APDU commands.
     */
    private short[] loyaltyCAD;
    private JavaLoyaltyInterface[] loyaltySIO;
    
    /**
     * Installs Java Purse applet.
     * @param bArray install parameter array.
     * @param bOffset where install data begins.
     * @param bLength install parameter data length.
     */
    public static void install( byte[] bArray, short bOffset, byte bLength ) {
        new JavaPurse(bArray, bOffset, bLength);
    }
    
    /**
     * Performs memory allocations, initialization, and applet registration.
     *
     * @param bArray received by install.
     * @param bOffset received by install.
     * @param bLength received by install.
     */
    protected JavaPurse(byte[] bArray, short bOffset, byte bLength) {        
        ID_Purse = new byte[ID_LENGTH];
        ExpDate = new byte[DATE_LENGTH];
        ExpDate[0] = (byte)12; ExpDate[1] = (byte)31; ExpDate[2] = (byte)99;
        balancesRecord = new byte[BALANCES_RECORD_LENGTH];
        loyaltyCAD = new short[MAX_LOYALTY];
        loyaltySIO = new JavaLoyaltyInterface[MAX_LOYALTY];
        
        TN = 0;
        PUN = 0;
        isPersonalized = false;
        
        //Create transient objects.
        transientShorts = JCSystem.makeTransientShortArray( NUM_TRANSIENT_SHORTS,
            JCSystem.CLEAR_ON_DESELECT);
        transientBools = JCSystem.makeTransientBooleanArray( NUM_TRANSIENT_BOOLS,
            JCSystem.CLEAR_ON_DESELECT);
        
        CAD_ID_array = JCSystem.makeTransientByteArray( (short)4,
            JCSystem.CLEAR_ON_DESELECT);
        byteArray8 = JCSystem.makeTransientByteArray( (short)8,
            JCSystem.CLEAR_ON_DESELECT);
        
        masterPIN = new OwnerPIN ((byte)1, (byte)8);
        //There is only one try - it's not supposed to be done by human operator
        userPIN = new OwnerPIN ((byte)5, (byte)8);
        
        parametersFile = new ParametersFile(PARAMETERS_RECORD_NUMBER);
        
        transactionLogFile = new CyclicFile(TRANSACTION_RECORD_NUMBER,
            TRANSACTION_RECORD_LENGTH);
        Util.setShort(balancesRecord, OFFSET_BAL_CURRENT, (short)0);
        
        /*
         * if AID length is not zero register Java Loyalty
         * applet with specified AID
         *
         * NOTE: all the memory allocations should be performed before register()
         */
        
        byte aidLen = bArray[bOffset];
        if (aidLen== (byte)0){
            register();
        } else {
            register(bArray, (short)(bOffset+1), aidLen);
        }
    }
    
    
    /**
     * Performs the session finalization.
     */
    public void deselect() {
        userPIN.reset();
        masterPIN.reset();
    }
    
    
    /**
     * Dispatches APDU commands.
     * @param apdu APDU object
     */
    public void process(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        
        if (!apdu.isISOInterindustryCLA()) {
            switch (buffer[ISO7816.OFFSET_INS])	{
            case INITIALIZE_TRANSACTION:
                processInitializeTransaction(apdu); break;
            case COMPLETE_TRANSACTION:
                processCompleteTransaction(apdu); break;
            case INITIALIZE_UPDATE:
                processInitializeUpdate(apdu);	break;
            case COMPLETE_UPDATE:
                processCompleteUpdate(apdu); break;
            default:
                ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
            }
        } else {
            if (buffer[ISO7816.OFFSET_INS] == VERIFY) {
                processVerifyPIN(apdu);
            } else if (buffer[ISO7816.OFFSET_INS] == ISO7816.INS_SELECT) {
                if (selectingApplet())
                    processSelectPurse(apdu);
                else 
                    processSelectFile(apdu);
              } else if (buffer[ISO7816.OFFSET_INS] == READ) {
                  processReadRecord(apdu);
              } else 
                  ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
          }
    }
    
    /**
     * Handles Select Purse APDU.
     * @param apdu APDU object
     */
    private void processSelectPurse(APDU apdu) {
        //
        // There might be multiple instances of Java Purse on the card with
        // different AID values. This method returns the FCI here identifying its 
        // particular instance.
        //
        
        // Format of response data
        //  FCI tag, FCI length,
        //     AID tag, length, value,
        //     PROPRIETARY tag, value.
        //
        
        // TEMPLATE
        byte[] buffer = apdu.getBuffer();
        buffer[0] = FCI_TEMPLATE_TAG;
        // buffer[1] set later
        
        // AID
        buffer[2] = FCI_AID_TAG;
        buffer[3] = JCSystem.getAID().getBytes(buffer, (short)4);
        short offset=(short)(3+buffer[3]);
        
        // PROPRIETARY DATA 
        buffer[offset++] = (byte)FCI_PROPERIETARY.length; 
        offset = Util.arrayCopyNonAtomic(FCI_PROPERIETARY, (short)0,
            buffer, offset, (short)FCI_PROPERIETARY.length);
        
        // FCI template length
        buffer[1] = (byte)(offset-(short)2);
        
        apdu.setOutgoingAndSend((short)0, offset);
    }
    
    /**
     * Handles Initialize Transaction APDU.
     * <p>See <em>Java Card(TM) Development Kit User's Guide</em> for details.
     *
     * @param apdu APDU object
     */
    private void processInitializeTransaction(APDU apdu) {
        if (transientBools[TRANSACTION_INITIALIZED])
            ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
        if (!userPIN.isValidated())
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        byte[] buffer = apdu.getBuffer();
        if (buffer[ISO7816.OFFSET_LC] != LC_IT)
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        if (buffer[ISO7816.OFFSET_P2] != 0)
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        apdu.setIncomingAndReceive();    // get expected data
        byte transactionType = buffer[ISO7816.OFFSET_P1];
        transientShorts[TRANSACTION_TYPE_IX] = transactionType;
        short amount = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
        transientShorts[AMOUNT_IX] = amount;
        
        short balance = checkTransactionValues(transactionType, amount);
        
        // Increment TN in Transient Memory & compute signature
        short newTN = (short)(TN + 1);
        transientShorts[TN_IX] = newTN;
        Util.arrayCopyNonAtomic(buffer, CAD_ID_OFFSET, CAD_ID_array, START, ID_LENGTH);
        
        // The crypto processing could be done here
        Util.arrayFillNonAtomic(byteArray8, (short)0, (short) byteArray8.length, (byte)0);
        
        // Send	R-APDU
        short offset = Util.arrayCopyNonAtomic(ID_Purse, START, buffer, START, ID_LENGTH);
        offset = Util.arrayCopyNonAtomic(ExpDate, START, buffer, offset, DATE_LENGTH);
        offset = Util.setShort(buffer, offset, balance);
        offset = Util.setShort(buffer, offset, newTN);
        offset = Util.arrayCopyNonAtomic(byteArray8, START, buffer, offset, SIGNATURE_LENGTH);
        
        apdu.setOutgoingAndSend(START, (short)(offset - START));
        transientBools[TRANSACTION_INITIALIZED] = true;
    }
    
    /**
     * Handles Complete Transaction APDU.
     * @param apdu APDU object
     */
    private void processCompleteTransaction(APDU apdu) {
        if (!transientBools[TRANSACTION_INITIALIZED])
            ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
        byte[] buffer = apdu.getBuffer();
        if (buffer[ISO7816.OFFSET_LC] != LC_CT)
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        if ((buffer[ISO7816.OFFSET_P1] != 0) || (buffer[ISO7816.OFFSET_P2] != 0))
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        
        apdu.setIncomingAndReceive();    // get expected data
        
        //restore transaction data from transient
        short newTN = transientShorts[TN_IX];
        short amount = transientShorts[AMOUNT_IX];
        short newBalance = transientShorts[NEW_BALANCE_IX];
        //The signature verification could be here
        Util.arrayFillNonAtomic(byteArray8, (short)0, (short) byteArray8.length,(byte)0);
        boolean signatureOK = (0 == Util.arrayCompare(buffer,
            (short)ISO7816.OFFSET_CDATA, byteArray8, START, SIGNATURE_LENGTH));
        
        //prepare transaction record in APDU buffer
        short offset = Util.setShort(buffer, START, newTN);
        buffer[offset] = (byte)transientShorts[TRANSACTION_TYPE_IX];
        offset++;
        offset = Util.setShort(buffer, offset, amount);
        //CAD ID was left in this array from Initialize Transaction
        offset = Util.arrayCopyNonAtomic(CAD_ID_array, START, buffer, offset, ID_LENGTH);
        
        //Date and time are copied in APDU buffer to where they should go
        //in the transaction record.
        short balanceOffset = offset =
            Util.arrayCopyNonAtomic(buffer, (short)(ISO7816.OFFSET_CDATA + 8),
            buffer, offset, DATETIME_LENGTH);
        //Balance and SW will be added to transactionRecord	later
        if (!signatureOK) {
            //Branch for unsuccessful transaction. Balance is not updated,
            //otherwise transactionLog is recorded the same way as in successful transaction
            offset = Util.setShort(buffer, offset, transientShorts[CURRENT_BALANCE_IX]); // old balance
            Util.setShort(buffer, offset, SW_WRONG_SIGNATURE);
            //done with preparing transaction record
            
            byte[] theRecord = transactionLogFile.getNewLogRecord();
            //The following	few	steps have to be performed atomically!
            JCSystem.beginTransaction();
            TN = newTN;
            Util.arrayCopy(buffer, START,
                 theRecord, START, TRANSACTION_RECORD_LENGTH);
            transactionLogFile.updateNewLogRecord();
            JCSystem.commitTransaction();
            
            //Now we can throw exception
            transientBools[TRANSACTION_INITIALIZED] = false;
            ISOException.throwIt(SW_WRONG_SIGNATURE);
        } else {
            //Branch for successful transaction.
            offset = Util.setShort(buffer, offset, transientShorts[NEW_BALANCE_IX]);
            Util.setShort(buffer, offset, ISO7816.SW_NO_ERROR);
            // done with preparing transaction record
            
            byte[] theRecord = transactionLogFile.getNewLogRecord();
            //The following few steps have to be performed atomically!
            JCSystem.beginTransaction();
            TN = transientShorts[TN_IX];
            //Update balance
            Util.setShort(balancesRecord, START, newBalance);
            Util.arrayCopy(buffer, START,
                 theRecord, START, TRANSACTION_RECORD_LENGTH);
            transactionLogFile.updateNewLogRecord();
            JCSystem.commitTransaction();
        }
        
        // Do loyalty work
        // We have all the information in the buffer, the loyalty applets shouldn't
        // know transaction number and the balance of purse - so we zero out these
        // fields first
        Util.setShort(buffer, START, (short)0);
        Util.setShort(buffer, balanceOffset, (short)0);
        short loyaltyCADValue = Util.getShort(CAD_ID_array, START);
        for (byte loyaltyIndex = 0; loyaltyIndex < MAX_LOYALTY; loyaltyIndex++) {
            if (loyaltyCAD[loyaltyIndex] == loyaltyCADValue) {
                loyaltySIO[loyaltyIndex].grantPoints (buffer);
                break;
            }
        }
        //Put all '0's in signature3 (there could be crypto calculations here)
        Util.arrayFillNonAtomic(byteArray8, (short)0, (short) byteArray8.length, (byte)0);
        
        //send R-APDU
        offset = Util.setShort(buffer, START, newBalance);
        offset = Util.arrayCopyNonAtomic(byteArray8, START, buffer, offset, SIGNATURE_LENGTH);
        apdu.setOutgoingAndSend(START, (short)(offset - START));
        
        transientBools[TRANSACTION_INITIALIZED] = false;
    }
    
    
    /**
     * Handles Initialize Parameter Update APDU.
     *
     * <p><em>NOTE:</em> In this sample implementation we assume that all the
     * Parameter Updates are performed in somewhat secured facility and therefor
     * the tearing of the card is not an issue. That's why we don't do any
     * transactional protection while processing Initialize and Complete
     * Parameter Update APDU commands.
     *
     * @param apdu APDU object
     */
    private void processInitializeUpdate(APDU apdu) {
        if (transientBools[UPDATE_INITIALIZED])
            ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
        if (!masterPIN.isValidated() && isPersonalized)
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        
        byte[] buffer = apdu.getBuffer();
        if ((buffer[ISO7816.OFFSET_P1] != 0) || (buffer[ISO7816.OFFSET_P2] != 0))
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        
        // Because this is a case 2 command (outgoing data only), the contents of P3
        //  are undefined. In T=0, P3 is Le. In T=1, P3 is Lc. Therefore, we don't
        //  bother to test the contents of buffer[ISO7816.OFFSET_LC].
        
        
        PUN++; //Increment parameter Update Number
        
        // Send R-APDU
        short offset = Util.arrayCopyNonAtomic(ID_Purse, START, buffer, START, ID_LENGTH);
        offset = Util.arrayCopyNonAtomic(ExpDate, START, buffer, offset, DATE_LENGTH);
        offset = Util.setShort(buffer, offset, PUN);
        apdu.setOutgoingAndSend(START, (short)(offset - START));
        
        transientBools[UPDATE_INITIALIZED] = true;
    }
    
    /**
     * Handles Complete Parameter Update APDU.
     * @param apdu APDU object
     */
    private void processCompleteUpdate(APDU apdu) {
        if (!transientBools[UPDATE_INITIALIZED])
            ISOException.throwIt(SW_COMMAND_OUT_OF_SEQUENCE);
        byte[] buffer = apdu.getBuffer();
        if ((buffer[ISO7816.OFFSET_P1] != 0) || (buffer[ISO7816.OFFSET_P2] != 0))
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        short count = apdu.setIncomingAndReceive();    // get expected data
        byte lc = buffer[ISO7816.OFFSET_LC];
        //Put all '0's in byteArray8 and compare with buffer
        //(there could be crypto calculations here)
        Util.arrayFillNonAtomic(byteArray8, (short)0, (short) byteArray8.length, (byte)0);
        if (0 != Util.arrayCompare(byteArray8, START, buffer,
            (short)(ISO7816.OFFSET_CDATA + lc - SIGNATURE_LENGTH),
            SIGNATURE_LENGTH))
            ISOException.throwIt(SW_WRONG_SIGNATURE);
            
        switch (buffer[TLV_OFFSET]) {
        case MASTER_PIN_UPDATE:	
            updatePIN(apdu, masterPIN); setIsPersonalized(); break;
        case USER_PIN_UPDATE: 	
            updatePIN(apdu, userPIN); break;
        case EXP_DATE_UPDATE: 	
            updateParameterValue(apdu, ExpDate); break;
        case PURSE_ID_UPDATE: 	
            updateParameterValue(apdu, ID_Purse); break;
        case MAX_BAL_UPDATE: 	
            updateBalanceValue(apdu, OFFSET_BAL_MAX); break;
        case MAX_M_UPDATE: 		
            updateBalanceValue(apdu, OFFSET_AMOUNT_MAX); break;
        case VERSION_UPDATE: 	
            updateParametersFile(apdu); break;
        case LOYALTY1_UPDATE:	
            updateLoyaltyProgram(apdu, (byte)0); break;
        case LOYALTY2_UPDATE:	
            updateLoyaltyProgram(apdu, (byte)1); break;
        case LOYALTY3_UPDATE:	
            updateLoyaltyProgram(apdu, (byte)2); break;
        case LOYALTY4_UPDATE:	
            updateLoyaltyProgram(apdu, (byte)3); break;
        default: 
            ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
        }
        
        // The crypto processing could be done here
        Util.arrayFillNonAtomic(byteArray8, (short)0, (short) byteArray8.length, (byte)0);
        Util.arrayCopyNonAtomic(byteArray8, START, buffer, START, SIGNATURE_LENGTH);
        apdu.setOutgoingAndSend(START, SIGNATURE_LENGTH);
        
        transientBools[UPDATE_INITIALIZED] = false;
    }
    
    /**
     * Handles Verify Pin APDU.
     * @param apdu APDU object
     */
    private void processVerifyPIN(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        byte pinLength = buffer[ISO7816.OFFSET_LC];
        byte triesRemaining	= (byte)0;
        short count = apdu.setIncomingAndReceive();    // get expected data
        if (count < pinLength) 
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        byte pinType = buffer[ISO7816.OFFSET_P2];
        switch (pinType) {
        case MASTER_PIN:
            if (!masterPIN.check(buffer, ISO7816.OFFSET_CDATA, pinLength)) {
                triesRemaining = masterPIN.getTriesRemaining();
                //The last nibble of return	code is	number of remaining	tries
                ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining));
            }
            break;
        case USER_PIN:
            if (!userPIN.check(buffer, ISO7816.OFFSET_CDATA, pinLength)) {
                triesRemaining = userPIN.getTriesRemaining();
                //The last nibble of return	code is	number of remaining	tries
                ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining));
            }
            break;
        default: 
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
    }
    /**
     * Verifies numerical limitations on Transaction Amount.
     *
    * <p><em>NOTE:</em> With some values of maxBalance and maxAmount the logic
     * in this method might become somewhat unrealistic. It's the result of using
     * short arithmetic on values which might be too big to fit in short variables.
     *
     * @param transactionType type of transaction.
     * @param amount transaction amount.
     * @return new balance
     */
    private short checkTransactionValues(byte transactionType, short amount) {
        short newBalance;
        short currentBalance = Util.getShort(balancesRecord, OFFSET_BAL_CURRENT);
        short maxBalance = Util.getShort(balancesRecord, OFFSET_BAL_MAX);
        short maxAmount = Util.getShort(balancesRecord, OFFSET_AMOUNT_MAX);
        switch (transactionType) {
        case CREDIT : {
            newBalance = (short)(currentBalance + amount);
            transientShorts[NEW_BALANCE_IX] = newBalance;
            if (newBalance > maxBalance || newBalance < 0) 	//to prevent rollover
            ISOException.throwIt(SW_CREDIT_TOO_HIGH);
            break;
        }
        case DEBIT : {
            if (amount > maxAmount) ISOException.throwIt(SW_AMOUNT_TOO_HIGH);
            newBalance = (short)(currentBalance - amount);
            transientShorts[NEW_BALANCE_IX] = newBalance;
            if (newBalance < 0)ISOException.throwIt(SW_NOT_ENOUGH_FUNDS);
            break;
        }
        default	: 
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        transientShorts[CURRENT_BALANCE_IX] = currentBalance;
        return currentBalance;
    }
    
    /**
     * Updates PIN.
     * @param apdu APDU object
     * @param PIN OwnerPIN object (masterPIN or userPIN)
     */
    
    private void updatePIN(APDU apdu, OwnerPIN PIN) {
        byte[] buffer = apdu.getBuffer();
        PIN.update(buffer, (short)(TLV_OFFSET + 2),	buffer[TLV_OFFSET + 1]);
    }
    
    /**
     * Set JavaPurse in personalized state.
     * It happens only once - after the first update of masterPIN
     */
    
    private void setIsPersonalized() {
        if (!isPersonalized) isPersonalized = true;//happens only once
    }
    
    /**
     * Update value of a Expiration Date or ID_Purse. Also updates corresponding
     * records in Parameters File
     * @param apdu APDU object
     * @param value the byte array to be updated
     */
    
    private void updateParameterValue(APDU apdu, byte[] value){
        byte[] buffer = apdu.getBuffer();
        Util.arrayCopyNonAtomic(buffer, (short)(TLV_OFFSET + 2), value, 
            START, buffer[TLV_OFFSET + 1]);
        updateParametersFile(apdu);
    }
    
    /**
     * Updates values of maximum balance or maximum amount for transaction in the
     * balancesRecord.
     * @param apdu APDU object
     * @param offset the offset in balancesRecord to be updated
     */
    
    private void updateBalanceValue(APDU apdu, short offset) {
        byte[] buffer = apdu.getBuffer();
        Util.arrayCopyNonAtomic(buffer, (short)(TLV_OFFSET + 2), 
            balancesRecord, offset, SHORT_LENGTH);
    }
    
    /**
     * Updates record in Parameters File.
     * @param apdu APDU object
     */
    
    private void updateParametersFile(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        byte recordNumber = parametersFile.findRecord(buffer[TLV_OFFSET]);//match tag
        
        if (recordNumber == (byte)0) {
            /*
             * The record is not found. We have to create a new record.
             * NOTE: This is an example that a requirement to perform all memory
             * allocations (all "new") in class constructor is not an absolute one.
             */
            byte[] newRecord = new byte[buffer[TLV_OFFSET + 1] + 2];
            Util.arrayCopyNonAtomic(buffer, TLV_OFFSET, newRecord, START,
                (short)(buffer[TLV_OFFSET + 1] + 2));
            parametersFile.addRecord(newRecord);
        } else {
            byte[] theRecord = parametersFile.getRecord(recordNumber);
            Util.arrayCopyNonAtomic(buffer, TLV_OFFSET, theRecord, START,
                (short)(buffer[TLV_OFFSET + 1] + 2));
        }
    }
    
    /**
     * Selects file by FID according to ISO7816-4.
     * This implementation doesn't support all variations of SELECT command
     * in the standard, but provides reasonable subset for selection by FID
     * (P1==2).
     * @param apdu APDU object
     */
    
    private void processSelectFile(APDU apdu) {
        byte[] buffer = apdu.getBuffer();
        
        // get the apdu data
        apdu.setIncomingAndReceive();
        
        if (buffer[ISO7816.OFFSET_P1] == (byte)2) {
            //  select file by FID
            if ( buffer[ISO7816.OFFSET_LC] != (byte)2)
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            short fid = Util.getShort(buffer, ISO7816.OFFSET_CDATA);
            switch (fid) {
            case PARAMETERS_FID:
            case TRANSACTION_LOG_FID:
            case BALANCES_FID:
                transientShorts[SELECTED_FILE_IX] = fid;
                break;
            default:  
                ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
            }
        } else 
            ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
    }
    
    /**
     * Reads a record by the record number or reads the first occurrence
     * of the record by the record identifier.
     * This implementation doesn't support all variations of READ RECORD command
     * in the ISO 7816-4 standard, but provides reasonable subset of it.	It is
     * here to demonstrate that even without "built-in" support for ISO 7816
     * File System an Applet can have the behavior prescribed by the standard.
     *
     * @param apdu APDU object
     */
    
    private void processReadRecord(APDU apdu) {
    
        // used to hold the record read
        byte record[] = null;
        short fid = 0;
        
        // get the APDU buffer and fields in the APDU header
        byte buffer[] = apdu.getBuffer();
        byte P1 = buffer[ISO7816.OFFSET_P1];
        byte P2 = buffer[ISO7816.OFFSET_P2];
        
        // process file selection here  according to ISO 7816-4, 6.5.3
        // P2 = xxxxxyyy
        // if xxxxx = 0, use the current selected file
        //    xxxxx(not all equal) = other value, select EF by SFI as
        //         specified in xxxxx
        
        if ( (P2 >> 3) == 0 ) {
            if (transientShorts[SELECTED_FILE_IX] == (short)0)
                ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
            else 
                fid = transientShorts[SELECTED_FILE_IX];
        } else {
            // Short file identifier
            byte sfi = (byte) ( ( P2 >> 3 ) & 0x1F );
            fid = Util.makeShort(FID_BYTE, sfi);
            switch (fid) {
            case PARAMETERS_FID:
            case TRANSACTION_LOG_FID:
            case BALANCES_FID:
                transientShorts[SELECTED_FILE_IX] = fid;
            break;
            default:  
                ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
            }
        }
        
        // check for security status (validated PIN)
        switch (fid) {
        case TRANSACTION_LOG_FID:
        case BALANCES_FID:
            if (!userPIN.isValidated())
                ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
            break;
        case PARAMETERS_FID:
            if (!masterPIN.isValidated())
                ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
        
        // get the last three bits of P2
        P2 = (byte) (P2 & 0x07);
        
        // select the record by record number
        if ( ( P2 & 0x04 ) != 0 ) {
          
            if ( P2 == 0x04 ) {
            // P1 = 00 indicates in ISO 7816-4 the current record: we don't
            // support it in this implementation
            if (P1 == 0)
                ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
            switch (fid) {
            case BALANCES_FID:
                // There is only one record in balancesRecord
                if (P1 == 1)
                record = balancesRecord;
                else ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                break;
            case TRANSACTION_LOG_FID:
                record = transactionLogFile.getRecord(P1);
                break;
            case PARAMETERS_FID:
                record = parametersFile.getRecord(P1);
            }
            if (record == null)
                ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
            } else
                ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
        } else {
            // select record by record identifier (first byte of the record)
            
            // read the first occurrence
            if ( P2 == 0) {
                switch (fid) {
                case BALANCES_FID:
                // There is only one record in balancesRecords
                    if (balancesRecord[0] == P1)
                        record = balancesRecord;
                    else 
                        ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                    break;
                case TRANSACTION_LOG_FID:
                    P1 = transactionLogFile.findRecord(P1);
                    if  (P1 == 0 )
                        ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                    else
                        record = transactionLogFile.getRecord(P1);
                    break;
                case PARAMETERS_FID:
                    P1 = parametersFile.findRecord(P1);
                    if  (P1 == 0 )
                        ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                    else
                        record = parametersFile.getRecord(P1);
                }
            } else {
                // function not supported, when P2 = 1, 2, 3
                ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
            }
        }
        
        // set the data transfer direction to outbound
        short Le = apdu.setOutgoing();
        
        // if Lr < Le, set Le == Lr
        // otherwise send exactly Le bytes back
        if (record.length < Le) {
            Le = (short)record.length;
        }
        
        apdu.setOutgoingLength(Le);
        apdu.sendBytesLong(record, (short)0, Le);
    
    }
    
    /**
     * Updates loyalty program.
     * It takes standard TLV record for Parameter Update, interprets first
     * two bytes as loyaltyCAD (to be compared with first two bytes of CAD ID
     * in a transaction), the rest of record as AID for loyalty applet.
     * In case of successful retrieval of Shareable Interface Object with this AID
     * it is stored as an element of array, and method grantPoints of this loyalty
     * applet will be called in transaction to grant loyalty points. If SIO is not
     * returned, or loyaltyCAD in parameter is 0 the corresponding elements in
     * loyaltyCAD  and loyaltySIO arrays are cleared. The Parameters File is updated.
     *
     * @param apdu APDU object
     * @param loyaltyIndex index to loyaltyCAD and loyaltySIO arrays
     * @see com.sun.javacard.JavaLoyalty.JavaLoyalty
     */
    
    private void updateLoyaltyProgram(APDU apdu, byte loyaltyIndex) {
        byte[] buffer = apdu.getBuffer();
        loyaltyCAD[loyaltyIndex] = Util.getShort(buffer,(short) (TLV_OFFSET+2));
        if (loyaltyCAD[loyaltyIndex] != (short)0) {
            AID loyaltyAID =  JCSystem.lookupAID(buffer, (short) (TLV_OFFSET+4),
                (byte)(buffer[TLV_OFFSET+1]-2));
            if (loyaltyAID != null) {
                loyaltySIO[loyaltyIndex] = (JavaLoyaltyInterface)
                JCSystem.getAppletShareableInterfaceObject(loyaltyAID, (byte)0);
                if (loyaltySIO[loyaltyIndex] == null)
                loyaltyCAD[loyaltyIndex] = (short)0;
            } else
                loyaltyCAD[loyaltyIndex] = (short)0;
        }
        if (loyaltyCAD[loyaltyIndex] == (short)0) {
          // clean-up
          buffer[TLV_OFFSET+1] = (byte)2;
          Util.arrayFillNonAtomic (buffer,(short) (TLV_OFFSET+2),
            (short) (buffer.length - TLV_OFFSET - 2), (byte)0);
        }
        updateParametersFile(apdu);
    }
  
}