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

JavaPurse

public class JavaPurse extends Applet
This class is intended to demonstrate how an electronic cash application might be developed using Java Card

See Java Card(TM) Development Kit User's Guide for details.

author
Vadim Temkin

Fields Summary
static final byte
VERIFY
static final byte
READ
static final byte
INITIALIZE_TRANSACTION
static final byte
COMPLETE_TRANSACTION
static final byte
INITIALIZE_UPDATE
static final byte
COMPLETE_UPDATE
static final byte
CREDIT
static final byte
DEBIT
static final byte
MASTER_PIN
static final byte
USER_PIN
static final short
SW_CREDIT_TOO_HIGH
static final short
SW_NOT_ENOUGH_FUNDS
static final short
SW_AMOUNT_TOO_HIGH
static final short
SW_COMMAND_OUT_OF_SEQUENCE
static final short
SW_WRONG_SIGNATURE
static final short
SW_PIN_FAILED
static final byte
LC_IT
static final byte
LC_CT
static final byte
LC_CU_MIN
static final byte
CAD_ID_OFFSET
static final short
DATE_LENGTH
static final short
DATETIME_LENGTH
static final short
ID_LENGTH
static final short
SHORT_LENGTH
static final short
START
static final short
SIGNATURE_LENGTH
static final short
MAX_LOYALTY
static final byte
TN_IX
static final byte
NEW_BALANCE_IX
static final byte
CURRENT_BALANCE_IX
static final byte
AMOUNT_IX
static final byte
TRANSACTION_TYPE_IX
static final byte
SELECTED_FILE_IX
static final byte
NUM_TRANSIENT_SHORTS
static final byte
TRANSACTION_INITIALIZED
static final byte
UPDATE_INITIALIZED
static final byte
NUM_TRANSIENT_BOOLS
private static final byte
FCI_TEMPLATE_TAG
private static final byte
FCI_AID_TAG
private static byte[]
FCI_PROPERIETARY
private ParametersFile
parametersFile
private CyclicFile
transactionLogFile
private short
TN
private short
PUN
private boolean
isPersonalized
static final short
PARAMETERS_FID
static final short
TRANSACTION_LOG_FID
static final short
BALANCES_FID
static final byte
FID_BYTE
static final byte
TRANSACTION_RECORD_LENGTH
static final byte
TRANSACTION_RECORD_NUMBER
static final byte
BALANCES_RECORD_LENGTH
static final byte
BALANCES_RECORD_NUMBER
static final byte
PARAMETERS_RECORD_NUMBER
static final byte
OFFSET_BAL_CURRENT
static final byte
OFFSET_BAL_MAX
static final byte
OFFSET_AMOUNT_MAX
static final byte
NUMBER_OF_FILES
private OwnerPIN
masterPIN
private OwnerPIN
userPIN
static final byte
MASTER_PIN_UPDATE
static final byte
USER_PIN_UPDATE
static final byte
EXP_DATE_UPDATE
static final byte
PURSE_ID_UPDATE
static final byte
MAX_BAL_UPDATE
static final byte
MAX_M_UPDATE
static final byte
VERSION_UPDATE
static final byte
LOYALTY1_UPDATE
static final byte
LOYALTY2_UPDATE
static final byte
LOYALTY3_UPDATE
static final byte
LOYALTY4_UPDATE
static final short
TLV_OFFSET
private byte[]
CAD_ID_array
private byte[]
byteArray8
private short[]
transientShorts
private boolean[]
transientBools
private byte[]
ID_Purse
private byte[]
ExpDate
private byte[]
balancesRecord
private short[]
loyaltyCAD
private com.sun.javacard.samples.SampleLibrary.JavaLoyaltyInterface[]
loyaltySIO
Constructors Summary
protected JavaPurse(byte[] bArray, short bOffset, byte bLength)
Performs memory allocations, initialization, and applet registration.

param
bArray received by install.
param
bOffset received by install.
param
bLength received by install.

        
        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);
        }
    
Methods Summary
private shortcheckTransactionValues(byte transactionType, short amount)
Verifies numerical limitations on Transaction Amount.

NOTE: 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

        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;
    
public voiddeselect()
Performs the session finalization.

        userPIN.reset();
        masterPIN.reset();
    
public static voidinstall(byte[] bArray, short bOffset, byte bLength)
Installs Java Purse applet.

param
bArray install parameter array.
param
bOffset where install data begins.
param
bLength install parameter data length.

    
                              
               
        new JavaPurse(bArray, bOffset, bLength);
    
public voidprocess(APDU apdu)
Dispatches APDU commands.

param
apdu APDU object

        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);
          }
    
private voidprocessCompleteTransaction(APDU apdu)
Handles Complete Transaction APDU.

param
apdu APDU object

        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;
    
private voidprocessCompleteUpdate(APDU apdu)
Handles Complete Parameter Update APDU.

param
apdu APDU object

        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;
    
private voidprocessInitializeTransaction(APDU apdu)
Handles Initialize Transaction APDU.

See Java Card(TM) Development Kit User's Guide for details.

param
apdu APDU object

        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;
    
private voidprocessInitializeUpdate(APDU apdu)
Handles Initialize Parameter Update APDU.

NOTE: 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

        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;
    
private voidprocessReadRecord(APDU apdu)
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

    
        // 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);
    
    
private voidprocessSelectFile(APDU apdu)
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

        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);
    
private voidprocessSelectPurse(APDU apdu)
Handles Select Purse APDU.

param
apdu APDU object

        //
        // 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);
    
private voidprocessVerifyPIN(APDU apdu)
Handles Verify Pin APDU.

param
apdu APDU object

        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);
        }
    
private voidsetIsPersonalized()
Set JavaPurse in personalized state. It happens only once - after the first update of masterPIN

        if (!isPersonalized) isPersonalized = true;//happens only once
    
private voidupdateBalanceValue(APDU apdu, short offset)
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

        byte[] buffer = apdu.getBuffer();
        Util.arrayCopyNonAtomic(buffer, (short)(TLV_OFFSET + 2), 
            balancesRecord, offset, SHORT_LENGTH);
    
private voidupdateLoyaltyProgram(APDU apdu, byte loyaltyIndex)
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

        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);
    
private voidupdatePIN(APDU apdu, OwnerPIN PIN)
Updates PIN.

param
apdu APDU object
param
PIN OwnerPIN object (masterPIN or userPIN)

        byte[] buffer = apdu.getBuffer();
        PIN.update(buffer, (short)(TLV_OFFSET + 2),	buffer[TLV_OFFSET + 1]);
    
private voidupdateParameterValue(APDU apdu, byte[] value)
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

        byte[] buffer = apdu.getBuffer();
        Util.arrayCopyNonAtomic(buffer, (short)(TLV_OFFSET + 2), value, 
            START, buffer[TLV_OFFSET + 1]);
        updateParametersFile(apdu);
    
private voidupdateParametersFile(APDU apdu)
Updates record in Parameters File.

param
apdu APDU object

        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));
        }