/**
*
* @author Sebastien Riou
*/
package uk.co.nimp.scard;
import com.atolsystems.atolutilities.AStringUtilities;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.smartcardio.CardNotPresentException;
import uk.co.nimp.smartcard.*;
import com.atolsystems.atolutilities.StopRequestFromUserException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.TimerTask;
import javax.swing.JOptionPane;
public class ScriptPlayer implements StatusDisplayer{
/*public interface StatusDisplayer{
public void setText(String msg);
}*/
static String nl;
static{
nl=AStringUtilities.systemNewLine;
}
public abstract static class CallBack{
final static public int SUCCESS=0;
final static public int WARNING=1;
final static public int ERROR=2;
final static public int SYSTEM_ERROR=3;
public abstract int execute(GenericTerminal terminal) throws ScardException;
}
static public class PowerOff{//to end a communication or prepare a cold activation
}
static public class PowerOn{//just activation (cold or warm depending of previous state)
public final int activation;
public PowerOn(int activation) {
this.activation = activation;
}
}
static public class PcscPowerOn{//activation + PPS: always try to set the fastest speed
public final int protocol;
public final int activation;
public PcscPowerOn(int protocol, int activation) {
this.protocol = protocol;
this.activation = activation;
}
}
static public class Frequency{
public final int frequency;
public Frequency(int frequency) {
this.frequency = frequency;
}
}
static public class PowerSupplyVoltage{
public final int voltage;
public PowerSupplyVoltage(int voltage) {
this.voltage = voltage;
}
}
static public class Pps{
public final int fi;
public final int di;
public final int t;
public Pps(int fi, int di, int t) {
this.fi = fi;
this.di = di;
this.t = t;
}
}
static public class DelayNs{
public final long delayNs;
public DelayNs(long delayNs) {
this.delayNs = delayNs;
}
}
static public class ApduTimeout{
public final int timeoutMs;
public ApduTimeout(int timeoutMs) {
this.timeoutMs = timeoutMs;
}
}
static public class ApduTimeoutPush extends ApduTimeout{
public ApduTimeoutPush(int timeoutMs) {
super(timeoutMs);
}
}
static public class ApduTimeoutPop {
}
/*static public class ApduWithTimeout{
public final int timeBudgetMs;
public final Apdu apdu;
public ApduWithTimeout(Apdu apdu, int timeBudgetMs) {
this.apdu=apdu;
this.timeBudgetMs = timeBudgetMs;
}
}*/
static public class ExpectedApduResponse{
public final int id;
public final byte[] leData;
public final int statusWord;
public ExpectedApduResponse(int id, byte[] leData, short statusWord) {
this.id = id;
this.leData = leData;
this.statusWord = 0xFFFF & statusWord;
}
private ExpectedApduResponse(Apdu apdu) {
this.id=0;
this.leData=apdu.getLeData();
this.statusWord=apdu.getSw();
}
private ExpectedApduResponse(AnswerToReset answerToReset) {
this.id=0;
this.leData=new byte[answerToReset.getData().length-2];
System.arraycopy(answerToReset.getData(), 0, this.leData, 0, this.leData.length);
/*
* for(int i=0;i<this.leData.length;i++)
this.leData[i]=answerToReset.getData()[i];
*/
int temp=0xFF & answerToReset.getData()[answerToReset.getData().length-2];
temp=(temp<<8)+(0xFF & answerToReset.getData()[answerToReset.getData().length-1]);
this.statusWord=temp;
}
}
static public class BufferOperation{
public static final int OP_FULL_COMPARISON=1;
public static final int OP_AND=2;
public static final int OP_OR=3;
public static final int OP_XOR=4;
public static final int OP_INVALID=5;
public final int id1;
public final int id2;
public final int operationId;
public BufferOperation(int id1, int id2, int operationId) {
this.id1 = id1;
this.id2 = id2;
this.operationId = operationId;
if((operationId<=0) || (operationId>=OP_INVALID))
throw new RuntimeException("Invalid operation ID: "+ operationId);
}
public void perform(ScriptPlayer player) throws ScardException, StopRequestFromUserException, UnexpectedCardResponseException, CardNotPresentException{
Object o1=player.buffers.get(id1);
Object o2=player.buffers.get(id2);
if(null==o1)
throw new RuntimeException("Buffer "+id1+" is not defined.");
if(null==o1)
throw new RuntimeException("Buffer "+id2+" is not defined.");
ExpectedApduResponse response1;
if(o1.getClass().equals(ExpectedApduResponse.class)){
response1=(ExpectedApduResponse)o1;
} else if(o1.getClass().equals(Apdu.class)){
response1=new ExpectedApduResponse((Apdu)o1);
} else if(o1.getClass().equals(AnswerToReset.class)){
response1=new ExpectedApduResponse((AnswerToReset)o1);
} else
throw new RuntimeException("Cannot perform operation on class "+o1.getClass());
ExpectedApduResponse response2;
if(o2.getClass().equals(ExpectedApduResponse.class)){
response2=(ExpectedApduResponse)o2;
} else if(o2.getClass().equals(Apdu.class)){
response2=new ExpectedApduResponse((Apdu)o2);
} else if(o2.getClass().equals(AnswerToReset.class)){
response2=new ExpectedApduResponse((AnswerToReset)o2);
} else
throw new RuntimeException("Cannot perform operation on class "+o2.getClass());
switch(operationId){
case OP_AND:
case OP_OR:
case OP_XOR:
{
int compareLen=response1.leData.length;
byte out[]=new byte[response1.leData.length];
int outStatusWord;
if(response1.leData.length!=response2.leData.length)
throw new RuntimeException("Length of buffers do not match: response1.leData.length="+response1.leData.length+", response2.leData.length="+response2.leData.length);
else{
switch(operationId){
case OP_AND:
for(int i=0;i<compareLen;i++)
out[i] = (byte)(response1.leData[i] & response2.leData[i]);
outStatusWord=response1.statusWord & response2.statusWord;
break;
case OP_OR:
for(int i=0;i<compareLen;i++)
out[i] = (byte)(response1.leData[i] | response2.leData[i]);
outStatusWord=response1.statusWord | response2.statusWord;
break;
case OP_XOR:
for(int i=0;i<compareLen;i++)
out[i] = (byte)(response1.leData[i] ^ response2.leData[i]);
outStatusWord=response1.statusWord ^ response2.statusWord;
break;
default:
throw new RuntimeException("Invalid operation code: "+ operationId);
}
}
response1=new ExpectedApduResponse(id1, out, (short) outStatusWord);
player.buffers.put(response1.id, response1);
break;
}
case OP_FULL_COMPARISON:
{
if(false==player.checkOperation)
return;
//Object o1=player.buffers.get(id1);
//Object o2=player.buffers.get(id2);
/*ExpectedApduResponse response1;
if(o1.getClass().equals(ExpectedApduResponse.class)){
response1=(ExpectedApduResponse)o1;
} else if(o1.getClass().equals(Apdu.class)){
response1=new ExpectedApduResponse((Apdu)o1);
} else if(o1.getClass().equals(AnswerToReset.class)){
response1=new ExpectedApduResponse((AnswerToReset)o1);
} else
throw new RuntimeException("Cannot compare class "+o1.getClass());
ExpectedApduResponse response2;
if(o2.getClass().equals(ExpectedApduResponse.class)){
response2=(ExpectedApduResponse)o2;
} else if(o2.getClass().equals(Apdu.class)){
response2=new ExpectedApduResponse((Apdu)o2);
} else if(o2.getClass().equals(AnswerToReset.class)){
response2=new ExpectedApduResponse((AnswerToReset)o2);
} else
throw new RuntimeException("Cannot compare class "+o2.getClass());*/
//int compareLen=Math.min(response1.leData.length, response2.leData.length);
int compareLen=response1.leData.length;
boolean lengthError=false;
byte leDataError=0;
if(response1.leData.length!=response2.leData.length)
lengthError=true;
else{
int first;
int second;
/*if(null!=player.compareMask){
first=Math.min(compareLen, player.compareMask.length);
second=Math.max(compareLen-player.compareMask.length, 0);
for(int i=0;i<first;i++){
leDataError|=player.compareMask[i] & (response1.leData[i] ^ response2.leData[i]);
}
}else*/{
first=0;
second=compareLen;
}
for(int i=first;i<second;i++){
leDataError|=(response1.leData[i] ^ response2.leData[i]);
}
}
int swError=response1.statusWord ^ response2.statusWord;
if(lengthError || ((leDataError|swError)!=0)){
player.errorOccured=true;
String temp="Buffer comparison failed:"+nl;
String msg = temp+nl;
String logMsg=temp;
if(lengthError){
temp = "Le data size don't match:"+nl;
temp += nl+"First buffer length is "+response1.leData.length+" (0x"+Integer.toHexString(response1.leData.length)+")"+nl;
temp += nl+"Second buffer length is "+response2.leData.length+" (0x"+Integer.toHexString(response2.leData.length)+")"+nl;
msg+=temp;
logMsg+=temp;
}
if(leDataError!=0){
temp = "Error indexe(s) in \"Le data\": ";
int errCnt=0;
for(int i=0;i<compareLen;i++){
if(0!=(response1.leData[i] ^ response2.leData[i])){
if((i!=0) && (0!=errCnt))
temp+=", ";
temp+=i;
errCnt++;
}
}
msg+=AStringUtilities.limitSize(temp, 64, "...");
logMsg+=temp;
}
temp = nl+"First buffer";
msg+=temp;
logMsg+=temp;
if(response1.leData.length>0){
temp=nl+AStringUtilities.bytesToHex(response1.leData)+nl;
msg += AStringUtilities.limitSize(temp, 64, "...");
logMsg+=temp;
temp = "Status word: ";
}else
temp = "'s status word: ";
temp+= AStringUtilities.shortToHex(response1.statusWord)+nl;
temp+= nl+"Second buffer";
msg+=temp;
logMsg+=temp;
if(response2.leData.length>0){
temp=nl+AStringUtilities.bytesToHex(response2.leData)+nl;
msg += AStringUtilities.limitSize(temp, 64, "...");
logMsg+=temp;
temp = "Status word: ";
}else
temp = "'s status word: ";
temp += AStringUtilities.shortToHex(response2.statusWord)+nl+nl;
msg+=temp;
logMsg+=temp;
msg += "Do you want to continue to check card responses ? (press Cancel to stop sequence)";
UnexpectedCardResponseException ex=new UnexpectedCardResponseException(logMsg);
player.playTerminal.log(ex);
long start=System.nanoTime();
int choice = JOptionPane.showConfirmDialog(null, msg, "Buffer comparison failed", JOptionPane.YES_NO_CANCEL_OPTION);
player.deadTime+=System.nanoTime()-start;
player.playTerminal.playUnexpectedCardResponseScript();
if (JOptionPane.NO_OPTION == choice) {
player.checkOperation = false;
}
if (JOptionPane.CANCEL_OPTION == choice) {
player.userCancelled = true;
throw new StopRequestFromUserException("Operation cancelled by user.");
}
}
break;
}
default:
throw new RuntimeException("Invalid operation code: "+ operationId);
}
}
}
protected boolean errorOccured;
protected boolean checkOperation;
protected boolean userCancelled;
protected boolean reportUnsupportedError;
protected Map<Integer,Object> buffers;
protected GenericTerminal playTerminal;
protected boolean checkConnection;
protected boolean checkConnectionDone;
protected long nRun;
protected long runCnt;
protected StatusDisplayer statusDiplayer;
protected long deadTime;
//protected byte[] compareMask;
public void setText(String msg){
playTerminal.logLine(ScardLogHandler.LOG_INFO, "SCRIPT STATUS: " + msg);
}
private void init(StatusDisplayer statusDiplayer){
checkOperation = true;
errorOccured=false;
userCancelled=false;
reportUnsupportedError = true;
checkConnection = true;
checkConnectionDone=false;
nRun=1;
this.statusDiplayer=statusDiplayer;
deadTime=0;
}
public ScriptPlayer() {
init(this);
}
public ScriptPlayer(StatusDisplayer statusDiplayer) {
init(statusDiplayer);
}
public long getDeadTime() {
return deadTime;
}
public long getnRun() {
return nRun;
}
public void setnRun(long nRun) {
if(-1>nRun)
throw new IllegalArgumentException("nRun must be >= -1 and <= "+Long.MAX_VALUE+", but argument="+nRun);
this.nRun = nRun;
}
public boolean isCheckConnection() {
return checkConnection;
}
public void setCheckConnection(boolean checkConnection) {
this.checkConnection = checkConnection;
}
public boolean isUserCancelled() {
return userCancelled;
}
public boolean isCheckOperation() {
return checkOperation;
}
public void setCheckOperation(boolean checkOperation) {
this.checkOperation = checkOperation;
}
public boolean isErrorOccured() {
return errorOccured;
}
public void setErrorOccured(boolean errorOccured) {
this.errorOccured = errorOccured;
}
Long lastApduTotalTime;
public long getLastApduTotalTime(){
return lastApduTotalTime;
}
public Apdu playApdu(GenericTerminal terminal, Apdu apdu) throws ScardException, StopRequestFromUserException, IOException, UnexpectedCardResponseException, CardNotPresentException{
playTerminal=terminal;
playApdu(apdu);
return apdu;
}
public Apdu playApdu(Apdu apdu) throws ScardException, StopRequestFromUserException, IOException, UnexpectedCardResponseException, CardNotPresentException{
/* return playApdu(apdu,0);
}
public Apdu playApdu(Apdu apdu, int timeoutMs) throws ScardException, StopRequestFromUserException, IOException, UnexpectedCardResponseException, CardNotPresentException{
*/
if(false==checkConnectionDone){
checkConnectionDone=true;
playTerminal.checkConnection();
}
long end=-1;
long start=System.nanoTime();
try {
/*if(timeoutMs>0)
playTerminal.sendApdu(apdu,timeoutMs);
else*/
playTerminal.sendApdu(apdu);
end=System.nanoTime();
} catch (UnexpectedCardResponseException e) {
end=System.nanoTime();
errorOccured=true;
if (checkOperation) {
playTerminal.logLine(ScardLogHandler.LOG_ERROR, apdu.getResponseApduComparisonErrorMessage(Integer.MAX_VALUE));
String msg = "The card sent an unexpected response:"+nl+nl;
msg += "Error message: " + e.getMessage() + nl+nl;
msg += apdu.toString(64)+nl+nl;
msg += "Do you want to continue to check card responses ? (press Cancel to stop sequence)";
long startDead=System.nanoTime();
int choice = JOptionPane.showConfirmDialog(null, msg, "Unexpected response from card", JOptionPane.YES_NO_CANCEL_OPTION);
deadTime+=System.nanoTime()-startDead;
if (JOptionPane.NO_OPTION == choice) {
checkOperation = false;
}
if (JOptionPane.CANCEL_OPTION == choice) {
userCancelled=true;
throw new StopRequestFromUserException("Operation cancelled by user.");
}
}
}finally{
if(-1==end)
end=System.nanoTime();
lastApduTotalTime=new Long(end-start);
}
return apdu;
}
class ApduTimeoutTask extends TimerTask{
//final GenericTerminal terminal;
public boolean expired=false;
/*ApduTimeoutTask(GenericTerminal terminal){
this.terminal=terminal;
}*/
@Override
public void run() {
errorOccured=true;
expired=true;
try {
playTerminal.forceDisconnection();
} catch (ScardException ex) {
Logger.getLogger(ScriptPlayer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public void play(GenericTerminal terminal, List<? extends Object> cmds) throws ScardException, StopRequestFromUserException, IOException, UnexpectedCardResponseException, CardNotPresentException {
if(null==cmds)
return;
playTerminal=terminal;
userCancelled=false;
buffers=new HashMap<Integer,Object>();
Apdu lastApdu=null;
AnswerToReset lastAtr=null;
checkConnectionDone=!checkConnection;
Stack<Integer> terminalTimeoutStack=new Stack<Integer>();
runCnt=0;
while((-1==nRun) || (runCnt<nRun)){
StringBuilder sb=new StringBuilder("iteration ");
sb.append(runCnt+1);
statusDiplayer.setText(sb.toString());
for (int i = 0; i < cmds.size(); i++) {
if(Thread.interrupted())
throw new StopRequestFromUserException("Operation cancelled by user.");
if (cmds.get(i).getClass().equals(Apdu.class)) {
lastApdu=playApdu((Apdu) cmds.get(i));
lastAtr=null;
}else if (cmds.get(i).getClass().equals(Integer.class)) {
Integer bufferId = (Integer) cmds.get(i);
if(null!=lastApdu)
buffers.put(bufferId, lastApdu);
else if(null!=lastAtr)
buffers.put(bufferId, lastAtr);
else
throw new ScardException("'Save to buffer' invoked but no ATR nor APDU to save");
} else if (cmds.get(i).getClass().equals(BufferOperation.class)) {
BufferOperation bufferOperation = (BufferOperation) cmds.get(i);
bufferOperation.perform(this);
} else if (cmds.get(i).getClass().equals(Frequency.class)) {
Frequency frequency = (Frequency) cmds.get(i);
if(playTerminal instanceof FrequencySetter){
FrequencySetter fs=(FrequencySetter) playTerminal;
fs.setFrequency(frequency.frequency);
}else
terminal.logLine(ScardLogHandler.LOG_WARNING,"Frequency specified in script but frequency setting is not supported with this terminal");
} else if (cmds.get(i).getClass().equals(PowerSupplyVoltage.class)) {
PowerSupplyVoltage power = (PowerSupplyVoltage) cmds.get(i);
if(playTerminal instanceof PowerSupplyVoltageSetter){
PowerSupplyVoltageSetter pss=(PowerSupplyVoltageSetter) playTerminal;
pss.setVoltage(power.voltage);
}else
terminal.logLine(ScardLogHandler.LOG_WARNING,"Voltage specified in script but voltage setting is not supported with this terminal");
} else if (cmds.get(i).getClass().equals(PcscPowerOn.class)) {
PcscPowerOn pcscPowerOn = (PcscPowerOn) cmds.get(i);
terminal.setNegociateComSpeed(true);
terminal.connect(pcscPowerOn.protocol, pcscPowerOn.activation);
lastAtr=terminal.getAtr();
lastApdu=null;
} else if (cmds.get(i).getClass().equals(PowerOn.class)) {
PowerOn powerOn = (PowerOn) cmds.get(i);
terminal.setNegociateComSpeed(false);
terminal.connect(powerOn.activation);
lastAtr=terminal.getAtr();
lastApdu=null;
} else if (cmds.get(i).getClass().equals(PowerOff.class)) {
terminal.disconnect();
} else if (cmds.get(i) instanceof CallBack) {
CallBack callBack = (CallBack) cmds.get(i);
callBack.execute(terminal);
lastAtr=terminal.getAtr();
lastApdu=null;
} else if (cmds.get(i).getClass().equals(DelayNs.class)) {
DelayNs delayNs=(DelayNs)cmds.get(i);
terminal.delay(delayNs.delayNs);
} else if (cmds.get(i).getClass().equals(ApduTimeoutPush.class)) {
ApduTimeoutPush timeout=(ApduTimeoutPush)cmds.get(i);
terminalTimeoutStack.push(terminal.getApduTimeout());
terminal.setApduTimeout(timeout.timeoutMs);
} else if (cmds.get(i).getClass().equals(ApduTimeout.class)) {
ApduTimeout timeout=(ApduTimeout)cmds.get(i);
terminal.setApduTimeout(timeout.timeoutMs);
} else if (cmds.get(i).getClass().equals(ApduTimeoutPop.class)) {
terminal.setApduTimeout(terminalTimeoutStack.pop());
} else if (cmds.get(i).getClass().equals(String.class)) {
String comment = (String) cmds.get(i);
terminal.logLine(ScardLogHandler.LOG_COMMENT,comment);
} else if (cmds.get(i).getClass().equals(ExpectedApduResponse.class)) {
ExpectedApduResponse expectedApduResponse = (ExpectedApduResponse) cmds.get(i);
buffers.put(expectedApduResponse.id, expectedApduResponse);
} else {
if (reportUnsupportedError) {
errorOccured=true;
String msg = "Runtime error: object ";
msg += cmds.get(i).getClass().getCanonicalName();
msg += " not supported. Action not performed."+nl+nl;
msg += "Ignore that kind of error ? (press Cancel to stop sequence)";
long start=System.nanoTime();
int choice = JOptionPane.showConfirmDialog(null, msg, "Runtime error", JOptionPane.YES_NO_CANCEL_OPTION);
deadTime+=System.nanoTime()-start;
if (JOptionPane.YES_OPTION == choice) {
reportUnsupportedError = false;
}
if (JOptionPane.CANCEL_OPTION == choice) {
userCancelled=false;
throw new StopRequestFromUserException("Operation cancelled by user.");
}
}
}
}
runCnt++;
}
}
}