package com.simplytapp.cardagent;
import java.io.IOException;
import javacard.framework.APDU;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import com.simplytapp.virtualcard.Agent;
import com.simplytapp.virtualcard.CardAgentConnector;
import com.simplytapp.virtualcard.TransceiveData;
public class SwipeYoursAgent extends Agent {
private static final long serialVersionUID = 1L;
private final static byte sentApdu = 0x00;
private final static byte sendingRrApdu = 0x01;
private final static byte sendingGpoApdu = 0x02;
private final static byte sendingSelectApdu = 0x03;
private final static byte sendingApdu = 0x04;
transient boolean selected = false;
transient boolean transactionFailed = false;
transient byte state = sentApdu;
transient Thread tLoadCache = null;
boolean newCache = false;
Cache cache = null;
public SwipeYoursAgent() {
allowSoftTransactions();
allowNfcTransactions();
denySocketTransactions();
}
public static void install(CardAgentConnector cardAgentConnector){
new SwipeYoursAgent().register(cardAgentConnector);
}
private void loadCache()
{
if(tLoadCache!=null)
return;
tLoadCache = new Thread(new Runnable(){
public void run()
{
boolean busy = false;
boolean connected = false;
boolean doTransaction = false;
try {
doTransaction = getDoTransactionFlag();
} catch (IOException e) {
}
if(doTransaction || cache==null)
{
cache = new Cache();
//create a cache from data
TransceiveData apdus = new TransceiveData(TransceiveData.NFC_CHANNEL);
apdus.setTimeout((short)15000);
apdus.packApdu(new byte[]{0x00,(byte)0xA4,0x04,0x00,0x0E,0x32,0x50,0x41,0x59,0x2E,0x53,0x59,0x53,0x2E,0x44,0x44,0x46,0x30,0x31,0x00}, true);
apdus.packApdu(new byte[]{0x00,(byte)0xA4,0x04,0x00,0x07,(byte)0xA0,0x00,0x00,0x00,0x03,0x10,0x10,0x00}, true);
apdus.packApdu(new byte[]{(byte)0x80,(byte)0xA8,0x00,0x00,0x04,(byte)0x83,0x02,(byte)0x80,0x00,0x00}, true);
apdus.packApdu(new byte[]{0x00,(byte)0xB2,0x01,0x0C,0x00}, true);
try {
setBusy();
busy = true;
connect();
connected = true;
transceive(apdus);
disconnect();
connected = false;
clearBusy();
busy = false;
} catch (IOException e) {
if(connected)
{
try {
disconnect();
connected = false;
} catch (IOException e1) {
}
}
if(busy)
{
try {
clearBusy();
busy = false;
} catch (IOException e1) {
}
}
tLoadCache = null;
try {Thread.sleep(2000);} catch (InterruptedException e1) {}
cache=null;
loadCache();
return;
}
for(short i=0;i<4;i++)
{
byte[] rsp = apdus.getNextResponse();
if(rsp==null || rsp.length<2)
continue;
else if(rsp[rsp.length-2]!=0x90 && rsp[rsp.length-1]!=0x00)
continue;
//don't store the SW in the cache
byte[] tmp = new byte[rsp.length-2];
for(short j=0;j<tmp.length;j++)
tmp[j] = rsp[j];
rsp = tmp;
byte[] cmd = null;
if(i==0)
cmd = new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,0x32,0x50,0x41,0x59,0x2E};
else if(i==1)
cmd = new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,(byte)0xA0,0x00,0x00,0x00,0x03};
else if(i==2)
cmd = new byte[]{(byte)0x80,(byte)0xA8,0x00,0x00,0x00};
else if(i==3)
cmd = new byte[]{0x00,(byte)0xB2,0x01,0x0C,0x00};
cache.addCmd(cmd, rsp);
}
try {
if(getDoTransactionFlag())
clearDoTransactionFlag();
} catch (IOException e) {
}
//save the state of the class now
try {
saveState();
} catch (IOException e1) {
}
}
tLoadCache=null;
}
});
tLoadCache.start();
}
@Override
public void create() {
loadCache();
}
@Override
public void activated(){ //this happens when the card is activated
if(cache==null)
loadCache();
}
@Override
public void deactivated(){ //this happens when the card is deactivated
}
@Override
public void disconnected(){
}
@Override
public void transactionStarted()
{
}
@Override
public void transactionFinished()
{
selected = false;
state = sentApdu;
transactionFailed = false;
if(newCache)
{
newCache = false;
cache = null;
//update the state of the class
try {
saveState();
} catch (IOException e1) {
}
try {
setDoTransactionFlag();
} catch (IOException e) {
}
}
if(cache==null)
loadCache();
}
@Override
public void sentApdu()
{
switch(state)
{
case sendingRrApdu:
selected = false;
if(newCache)
{
newCache = false;
cache = null;
try {
saveState();
} catch (IOException e1) {
}
try {
setDoTransactionFlag();
} catch (IOException e) {
}
}
break;
case sendingGpoApdu:
newCache = true;
break;
default:
break;
}
state = sentApdu;
}
void sendApduCFailure() throws ISOException
{
state = sendingApdu;
try {transactionFailure();} catch (IOException e) {}
transactionFailed = true;
throw new ISOException(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
@Override
public void process(APDU apdu) throws ISOException {
while(state!=sentApdu) //wait for previous one to complete (thread safe)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
try {
if(getTransactionFinished())
{
state = sendingApdu;
throw new ISOException(ISO7816.SW_UNKNOWN);
}
} catch (IOException e) {
}
}
if(transactionFailed)
{
state = sendingApdu;
throw new ISOException(ISO7816.SW_UNKNOWN);
}
if((short)(APDU.getProtocol()&0xFF)!=(short)(0xFF&APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A) &&
(short)(APDU.getProtocol()&0xFF)!=(short)(0xFF&APDU.PROTOCOL_MEDIA_SOCKET) &&
(short)(APDU.getProtocol()&0xFF)!=(short)(0xFF&APDU.PROTOCOL_MEDIA_SOFT))
sendApduCFailure();
//receive APDU-C
short len = apdu.setIncomingAndReceive();
len+=5;
//validate command format
if((short)(apdu.getBuffer()[ISO7816.OFFSET_LC]&0xFF)+5!=len)
throw new ISOException(ISO7816.SW_WRONG_LENGTH);
//check the cache for a response
byte[] cmd = new byte[len];
for(short i=0;i<len;i++)
cmd[i] = apdu.getBuffer()[i];
byte[] rsp = null;
if(cache!=null)
{
rsp = cache.getRsp(cmd);
if(rsp==null)
sendApduCFailure();
}
else
sendApduCFailure();
//respond to this APDU-C
switch(apdu.getBuffer()[ISO7816.OFFSET_INS])
{
case (byte) 0xA4: //select
if(apdu.getBuffer()[ISO7816.OFFSET_LC]>4 && apdu.getBuffer()[ISO7816.OFFSET_LC+1]==(byte)0xA0 &&
apdu.getBuffer()[ISO7816.OFFSET_LC+2]==(byte)0x00 && apdu.getBuffer()[ISO7816.OFFSET_LC+3]==(byte)0x00 &&
apdu.getBuffer()[ISO7816.OFFSET_LC+4]==(byte)0x00 && apdu.getBuffer()[ISO7816.OFFSET_LC+5]==(byte)0x03)
selected = true;
else
selected = false;
for(short i=0;i<rsp.length;i++)
apdu.getBuffer()[i] = rsp[i];
state = sendingSelectApdu;
apdu.setOutgoingAndSend((short)0, (short)rsp.length);
break;
case (byte) 0xA8: //gpo
if(selected)
{
for(short i=0;i<rsp.length;i++)
apdu.getBuffer()[i] = rsp[i];
state = sendingGpoApdu; //success triggers cache clearing and get new cache after transaction is over
apdu.setOutgoingAndSend((short)0, (short)rsp.length);
}
else
sendApduCFailure();
break;
case (byte) 0xB2: //read record
if(selected)
{
for(short i=0;i<rsp.length;i++)
apdu.getBuffer()[i] = rsp[i];
state = sendingRrApdu; //success triggers a successful transaction
apdu.setTransactionSuccess();
apdu.setOutgoingAndSend((short)0, (short)rsp.length);
}
else
sendApduCFailure();
break;
default:
sendApduCFailure();
}
}
}