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 PayPassAgent 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 sendingAcApdu = 0x03;
private final static byte sendingSelectApdu = 0x04;
private final static byte sendingApdu = 0x05;
private final static byte RR = 0x00;
private final static byte GPO = 0x01;
transient boolean selected = false;
transient boolean transactionFailed = false;
transient byte state = sentApdu;
transient Thread tLoadCache = null;
transient Thread connectTimer = null;
private Cache cache = new Cache();
public PayPassAgent() {
allowSoftTransactions();
allowNfcTransactions();
denySocketTransactions();
}
public static void install(CardAgentConnector cardAgentConnector) {
new PayPassAgent().register(cardAgentConnector);
}
private void loadLocalCache()
{
//add static cmd/rsp if needed
if(cache.getRsp(new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,(byte)0x32,0x50,0x41,0x59,(byte)0x2E})==null)
cache.addCmd( new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,(byte)0x32,0x50,0x41,0x59,(byte)0x2E},
new byte[]{0x6F, 0x23, (byte)0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46,
0x30, 0x31, (byte)0xA5, 0x11, (byte)0xBF, 0x0C, 0x0E, 0x61, 0x0C, 0x4F, 0x07, (byte)0xA0, 0x00, 0x00, 0x00, 0x04,
0x10, 0x10, (byte)0x87, 0x01, 0x01}); //PPSE
}
private void loadCache(final byte[] apduc, final byte flag)
{
if(tLoadCache!=null)
return;
tLoadCache = new Thread(new Runnable(){
public void run()
{
//create a cache from data
TransceiveData apdus = new TransceiveData(TransceiveData.NFC_CHANNEL);
apdus.setTimeout((short)5000);
//read record cache!
boolean rrCache = true;
if(flag==GPO || flag==RR)
{
if(flag==GPO)
apdus.packApdu(apduc, false);
if( cache.getRsp(new byte[]{0x00,(byte)0xB2,0x01,0x0C,0x00})==null)
{
rrCache = false;
apdus.packApdu(new byte[]{0x00,(byte)0xB2,0x01,0x0C,0x00}, true);
}
}
try {
transceive(apdus);
} catch (IOException e) {
tLoadCache = null;
return;
}
if(rrCache && (flag==GPO || flag==RR))
{
tLoadCache = null;
return;
}
for(short i=0;i<1;i++)
{
byte[] rsp = apdus.getNextResponse();
if(rsp==null || rsp.length<2)
continue;
else if((short)(rsp[rsp.length-2]&0xFF)!=(short)(0x90&0xFF) || 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)0xB2,0x01,0x0C,0x00};
cache.addCmd(cmd, rsp);
}
tLoadCache=null;
}
});
tLoadCache.start();
}
@Override
public void create() {
}
@Override
public void activated(){ //this happens when the card is activated
try {
setBusy();
connect();
} catch (IOException e) {
try {
clearBusy();
postMessage("No Connection Available!",false,null);
deactivate();
} catch (IOException e1) {
}
return;
}
connectTimer = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(120000);
} catch (InterruptedException e) {
}
try {
disconnect();
} catch (IOException e) {
}
connectTimer = null;
}});
connectTimer.start();
try {
clearBusy();
} catch (IOException e) {
}
}
@Override
public void deactivated(){ //this happens when the card is deactivated
if(connectTimer!=null)
connectTimer.interrupt();
try {
disconnect();
} catch (IOException e) {
}
}
@Override
public void disconnected(){
}
@Override
public void transactionStarted()
{
}
@Override
public void transactionFinished()
{
selected = false;
state = sentApdu;
transactionFailed = false;
//update the state of the class
try {
saveState();
} catch (IOException e1) {
}
}
@Override
public void sentApdu()
{
switch(state)
{
case sendingAcApdu:
selected = false;
break;
case sendingGpoApdu:
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);
}
private byte[] queryCache(APDU apdu, short len)
{
//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();
return rsp;
}
@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);
//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)0x04)
{
byte[] cmd = new byte[len];
for(short i=0;i<len;i++)
cmd[i] = apdu.getBuffer()[i];
try {
connect();
TransceiveData reset = new TransceiveData(TransceiveData.NFC_CHANNEL);
reset.packCardReset(false);
byte[] rsp = cache.getRsp(cmd);
if(rsp==null)
{
reset.packApdu(cmd, true);
try{
transceive(reset);
rsp = reset.getNextResponse();
if(rsp!=null && rsp.length>1 && (short)(rsp[rsp.length-2]&0xFF)==(short)(0x90&0xFF) && rsp[rsp.length-1]==0x00)
{
//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;
cache.addCmd( new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,(byte)0xA0,0x00,0x00,0x00,0x04},
rsp); //AID
}
} catch (IOException e){
}
}
else
{
reset.packApdu(cmd, false);
try {
transceive(reset);
} catch (IOException e){
}
}
} catch (IOException e) {
if(e.getMessage().equals("ALREADY_CONNECTED"))
{
TransceiveData reset = new TransceiveData(TransceiveData.NFC_CHANNEL);
reset.packCardReset(false);
byte[] rsp = cache.getRsp(cmd);
if(rsp==null)
{
reset.packApdu(cmd, true);
try {
transceive(reset);
rsp = reset.getNextResponse();
if(rsp!=null && rsp.length>1 && (short)(rsp[rsp.length-2]&0xFF)==(short)(0x90&0xFF) && rsp[rsp.length-1]==0x00)
{
//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;
cache.addCmd( new byte[]{0x00,(byte)0xA4,0x04,0x00,0x05,(byte)0xA0,0x00,0x00,0x00,0x04},
rsp); //AID
}
} catch (IOException e1) {
}
}
else
{
reset.packApdu(cmd, false);
try {
transceive(reset);
} catch (IOException e1) {
}
}
}
}
selected = true;
}
else
selected = false;
loadLocalCache();
byte[] rsp = queryCache(apdu,len);
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)
{
byte[] cmd = new byte[len];
for(short i=0;i<len;i++)
cmd[i] = apdu.getBuffer()[i];
rsp = cache.getRsp(cmd);
if(rsp==null)
{
TransceiveData gpo = new TransceiveData(TransceiveData.NFC_CHANNEL);
gpo.packApdu(cmd, true);
try {
transceive(gpo);
rsp = gpo.getNextResponse();
if(rsp!=null && rsp.length>1 && (short)(rsp[rsp.length-2]&0xFF)==(short)(0x90&0xFF) && rsp[rsp.length-1]==0x00)
{
//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;
cache.addCmd( new byte[]{(byte)0x80,(byte)0xA8,0x00,0x00,0x00},
rsp); //GPO
}
loadCache(cmd,RR);
} catch (IOException e) {
}
}
else
{
loadCache(cmd,GPO);
}
rsp = queryCache(apdu,len);
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)
{
byte[] cmd = new byte[len];
for(short i=0;i<len;i++)
cmd[i] = apdu.getBuffer()[i];
rsp = cache.getRsp(cmd);
while(rsp==null && tLoadCache!=null)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
rsp = queryCache(apdu,len);
for(short i=0;i<rsp.length;i++)
apdu.getBuffer()[i] = rsp[i];
state = sendingRrApdu;
apdu.setOutgoingAndSend((short)0, (short)rsp.length);
}
else
sendApduCFailure();
break;
case (byte) 0x2A: //application cryptogram
if(selected)
{
while(tLoadCache!=null)
{
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
byte[] cmd = new byte[len];
for(short i=0;i<len;i++)
cmd[i] = apdu.getBuffer()[i];
TransceiveData genAc = new TransceiveData(TransceiveData.NFC_CHANNEL);
genAc.packApdu(cmd, true);
try {
transceive(genAc);
} catch (IOException e){
}
rsp = genAc.getNextResponse();
if(rsp!=null && rsp.length>1 && (short)(rsp[rsp.length-2]&0xFF)==(short)(0x90&0xFF) && rsp[rsp.length-1]==0x00)
{
//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;
}
else
sendApduCFailure();
for(short i=0;i<rsp.length;i++)
apdu.getBuffer()[i] = rsp[i];
state = sendingAcApdu; //success triggers a successful transaction
apdu.setTransactionSuccess();
apdu.setOutgoingAndSend((short)0, (short)rsp.length);
}
else
sendApduCFailure();
break;
default:
sendApduCFailure();
}
}
}