package uk.co.nimp.scard;
import com.atolsystems.atolutilities.AStringUtilities;
import com.atolsystems.atolutilities.InputFileFormatException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import uk.co.nimp.scard.ScriptPlayer.DelayNs;
import uk.co.nimp.smartcard.Apdu;
/**
*
* @author Sebastien Riou
*/
public class StarScriptReader {
public static final String COMMENT = "CCMT";
public static final String COMMENT2 = "COMT";
public static final String C_STYLE_COMMENT = "//";
public static final String C_STYLE_MULTILINE_COMMENT_OPEN = "/*";
public static final String C_STYLE_MULTILINE_COMMENT_CLOSE = "*/";
public static final String VCC_SETTING = "SVCC";
public static final String POWER_OFF = "POFF";
public static final String POWER_ON = "ACTIVATION";//no pps on reader which support that (pcsc do not support it)
public static final String PCSC_POWER_ON = "PATR";//auto pps
public static final String COMMAND = "CCMD";
public static final String COMMAND_TIMEOUT_S = "CCMD_TIMEOUT";//in seconds
public static final String COMMAND_PUSH_TIMEOUT_S = "CCMD_PUSH_TIMEOUT";//in seconds
public static final String COMMAND_POP_TIMEOUT_S = "CCMD_POP_TIMEOUT";
public static final String SAVE_RESULT = "SAVE";
public static final String SAVE_IMMEDIATE_DATA = "SAVD";
public static final String COMPARE = "CCMP";
public static final String FIELD_ABSENT = "--";
public static final String AND_OP = "AND";
public static final String OR_OP = "OR";
public static final String XOR_OP = "XOR";
public static final String DELAY_MS = "DELY";
/*PATR: Power On
POFF: Power Off
CCMD: ISO 7816 Command
SAVE: Save output in buffer 'X'
CCMP: Compare buffer 'X' with buffer 'X'
SVCC: Select Voltage of VCC
FREQ: Vary Frequency
BREK: Break Point
SAVD: variable save
DELY: delay time after command execution(unit: ms)
SCUR: Start Current Measurement
GCUR: Get Average Current measured
*/
public static final String PCSC_POWER_ON_ARG_COLD = "COLD";
public static final String PCSC_POWER_ON_ARG_WARM = "WARM";
protected static final String SYNTAX_ERROR_HEADER = "Star script syntax error at line ";
protected static final String NO_ARG_ERROR = " does not take any argument so nothing else should be written on the same line.\n";
protected List<Object> cmds;
public StarScriptReader(File scriptFile) throws FileNotFoundException, IOException, InputFileFormatException {
cmds=new ArrayList<Object>();
parse(scriptFile);
}
protected void parseActivationParameter(String args, String activationCmd){
args=AStringUtilities.removeLeadingBlanks(args);
args=AStringUtilities.removeTrailingBlanks(args);
int protocol=GenericTerminal.PROTOCOL_ANY;
int activation=GenericTerminal.ACTIVATION_ANY;
if(false==args.isEmpty()){//arg is there, look for activation spec
if(args.startsWith(PCSC_POWER_ON_ARG_COLD)){
activation=GenericTerminal.ACTIVATION_FORCE_COLD;
args=args.substring(PCSC_POWER_ON_ARG_COLD.length());
} else if(args.startsWith(PCSC_POWER_ON_ARG_WARM)){
activation=GenericTerminal.ACTIVATION_FORCE_WARM;
args=args.substring(PCSC_POWER_ON_ARG_WARM.length());
}
args=AStringUtilities.removeLeadingBlanks(args);
}
if(false==args.isEmpty()){//arg is there, look for protocol spec
int match[]=AStringUtilities.findFirstRegExp2(args, "\\*|"+
GenericTerminal.PROTOCOL_NAME_T_0+"|"+
GenericTerminal.PROTOCOL_NAME_T_1+"|"+
GenericTerminal.PROTOCOL_NAME_ANY_STD_CL);
if(0==match[0]){//arg found
protocol=GenericTerminal.getProtocolCode(args.substring(0,match[1]));
args=args.substring(match[1]);
args=AStringUtilities.removeLeadingBlanks(args);
}
}
if(false==args.isEmpty()){//arg is there, look for frequency
int frequency=Integer.decode(args);
cmds.add(new ScriptPlayer.Frequency(frequency));
args="";
}
//no more args allowed here
if(false==args.isEmpty())
throw new RuntimeException(activationCmd+" take at most 3 arguments.");
if(activationCmd.equals(PCSC_POWER_ON))
cmds.add(new ScriptPlayer.PcscPowerOn(protocol, activation));
else
cmds.add(new ScriptPlayer.PowerOn(activation));
}
String removeTrailingBlanksAndComments(String in){
int i=in.indexOf("//");
String out;
if(-1!=i)
out=in.substring(0, i);
else
out=AStringUtilities.removeTrailingBlanks(in);
return out;
}
protected final void parse(File scriptFile) throws FileNotFoundException, IOException, InputFileFormatException{
FileReader fr=new FileReader(scriptFile);
try{
BufferedReader br=new BufferedReader(fr);
int lineNumber=0;
String line="";
String args="";
int multilineCommentLevel=0;
try{
while(null!=line){
line=AStringUtilities.removeLeadingBlanks(line);
if(!line.isEmpty()){
if(0==multilineCommentLevel){
if(line.startsWith(COMMENT) | line.startsWith(COMMENT2)){
String comment;
if(line.startsWith(COMMENT+" "))
comment=line.substring(COMMENT.length()+1);
else
comment=line.substring(COMMENT.length());
comment=AStringUtilities.removeTrailingBlanks(comment);
cmds.add(comment);
} else if(line.startsWith(C_STYLE_COMMENT)){
//C Style comments are ignore completely so the user can write comments which are really just
//for people who actually read the script file
/*String comment=line.substring(C_STYLE_COMMENT.length());
comment=AStringUtilities.removeTrailingBlanks(comment);
cmds.add(comment);*/
} else if(line.startsWith(VCC_SETTING)){
args=line.substring(VCC_SETTING.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
int voltage=Integer.parseInt(args);
cmds.add(new ScriptPlayer.PowerSupplyVoltage(voltage));
} else if(line.startsWith(POWER_OFF)){
args=line.substring(POWER_OFF.length());
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
//no args allowed here
if(false==args.isEmpty())
throw new RuntimeException(POWER_OFF+NO_ARG_ERROR);
cmds.add(new ScriptPlayer.PowerOff());
} else if(line.startsWith(PCSC_POWER_ON)){
args=line.substring(PCSC_POWER_ON.length());
parseActivationParameter(args, PCSC_POWER_ON);
} else if(line.startsWith(POWER_ON)){
args=line.substring(POWER_ON.length());
parseActivationParameter(args, POWER_ON);
} else if(line.startsWith(COMMAND_TIMEOUT_S)){
args=line.substring(COMMAND_TIMEOUT_S.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND_TIMEOUT_S+": timeout value not found");
String[] argArray = args.split("\\s");
if(1!=argArray.length)
throw new RuntimeException(COMMAND_TIMEOUT_S+" take 1 argument, but "+argArray.length+" arguments have been found.");
int commandTimeout=Integer.parseInt(argArray[0]);
cmds.add(new ScriptPlayer.ApduTimeout(commandTimeout*1000));
}else if(line.startsWith(COMMAND_PUSH_TIMEOUT_S)){
args=line.substring(COMMAND_PUSH_TIMEOUT_S.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND_PUSH_TIMEOUT_S+": timeout value not found");
String[] argArray = args.split("\\s");
if(1!=argArray.length)
throw new RuntimeException(COMMAND_PUSH_TIMEOUT_S+" take 1 argument, but "+argArray.length+" arguments have been found.");
int commandTimeout=Integer.parseInt(argArray[0]);
cmds.add(new ScriptPlayer.ApduTimeoutPush(commandTimeout*1000));
}else if(line.startsWith(COMMAND_POP_TIMEOUT_S)){
args=line.substring(COMMAND_POP_TIMEOUT_S.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(false==args.isEmpty())
throw new RuntimeException(COMMAND_POP_TIMEOUT_S+" take no argument.");
cmds.add(new ScriptPlayer.ApduTimeoutPop());
}else if(line.startsWith(COMMAND)){
args=line.substring(COMMAND.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND+": CLA byte not found");
int cla=Integer.parseInt(args.substring(0,2), 16);
args=args.substring(2);
args=AStringUtilities.removeLeadingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND+": INS byte not found");
int ins=Integer.parseInt(args.substring(0,2), 16);
args=args.substring(2);
args=AStringUtilities.removeLeadingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND+": P1 byte not found");
int p1=Integer.parseInt(args.substring(0,2), 16);
args=args.substring(2);
args=AStringUtilities.removeLeadingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND+": P2 byte not found");
int p2=Integer.parseInt(args.substring(0,2), 16);
args=args.substring(2);
args=AStringUtilities.removeLeadingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND+": LC FIELD not found");
int lc=0;
if(false==args.startsWith(FIELD_ABSENT))
lc=Integer.parseInt(args.substring(0,2), 16);
args=args.substring(2);
args=AStringUtilities.removeLeadingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMMAND+": LE FIELD not found");
int le=0;
if(false==args.startsWith(FIELD_ABSENT)){
le=Integer.parseInt(args.substring(0,2), 16);
if(0==le)
le=256;
}
args=args.substring(2);
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);
//args=args.replace(" ", "");//to allow formating of lc data
Apdu apdu=new Apdu(cla,ins,p1,p2,lc,le);
if(0<lc){//we should find some data here
apdu.setLcData(args, " ");
if(lc!=apdu.getLc())
throw new RuntimeException("LC field indicate "+lc+
" bytes but "+apdu.getLc()+" bytes of data have been found.");
args="";
}
//no args allowed here
if(false==args.isEmpty())
throw new RuntimeException(COMMAND+" take at most 7 arguments. This may due to a mismatch between Lc and Lc Data length");
cmds.add(apdu);
}else if(line.startsWith(SAVE_RESULT)){
args=line.substring(SAVE_RESULT.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(SAVE_RESULT+": target buffer ID not found");
String[] argArray = args.split("\\s");
if(1!=argArray.length)
throw new RuntimeException(SAVE_RESULT+" take 1 argument, but "+argArray.length+" arguments have been found.");
Integer id=Integer.parseInt(argArray[0]);
cmds.add(id);
} else if(line.startsWith(SAVE_IMMEDIATE_DATA)){
args=line.substring(SAVE_IMMEDIATE_DATA.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(SAVE_IMMEDIATE_DATA+": arguments not found");
String[] argArray = args.split("\\s");
if(2!=argArray.length)
throw new RuntimeException(SAVE_IMMEDIATE_DATA+" take 2 arguments, but "+argArray.length+" arguments have been found.");
int id=Integer.parseInt(argArray[0]);
byte leData[]=AStringUtilities.hexToBytes(argArray[1].substring(0,argArray[1].length()-4));
short statusWord=AStringUtilities.hexToShort(argArray[1].substring(argArray[1].length()-4));
ScriptPlayer.ExpectedApduResponse expectedApduResponse=new ScriptPlayer.ExpectedApduResponse(id,leData,statusWord);
cmds.add(expectedApduResponse);
} else if(line.startsWith(COMPARE)){
args=line.substring(COMPARE.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(COMPARE+": arguments not found");
String[] argArray = args.split("\\s");
if(2!=argArray.length)
throw new RuntimeException(COMPARE+" take 2 arguments, but "+argArray.length+" arguments have been found.");
int id1=Integer.parseInt(argArray[0]);
int id2=Integer.parseInt(argArray[1]);
ScriptPlayer.BufferOperation bufferOperation=new ScriptPlayer.BufferOperation(id1,id2,ScriptPlayer.BufferOperation.OP_FULL_COMPARISON);
cmds.add(bufferOperation);
} else if(line.startsWith(AND_OP)){
args=line.substring(AND_OP.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(AND_OP+": arguments not found");
String[] argArray = args.split("\\s");
if(2!=argArray.length)
throw new RuntimeException(AND_OP+" take 2 arguments, but "+argArray.length+" arguments have been found.");
int id1=Integer.parseInt(argArray[0]);
int id2=Integer.parseInt(argArray[1]);
ScriptPlayer.BufferOperation bufferOperation=new ScriptPlayer.BufferOperation(id1,id2,ScriptPlayer.BufferOperation.OP_AND);
cmds.add(bufferOperation);
} else if(line.startsWith(OR_OP)){
args=line.substring(OR_OP.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(OR_OP+": arguments not found");
String[] argArray = args.split("\\s");
if(2!=argArray.length)
throw new RuntimeException(OR_OP+" take 2 arguments, but "+argArray.length+" arguments have been found.");
int id1=Integer.parseInt(argArray[0]);
int id2=Integer.parseInt(argArray[1]);
ScriptPlayer.BufferOperation bufferOperation=new ScriptPlayer.BufferOperation(id1,id2,ScriptPlayer.BufferOperation.OP_OR);
cmds.add(bufferOperation);
} else if(line.startsWith(XOR_OP)){
args=line.substring(XOR_OP.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(XOR_OP+": arguments not found");
String[] argArray = args.split("\\s");
if(2!=argArray.length)
throw new RuntimeException(XOR_OP+" take 2 arguments, but "+argArray.length+" arguments have been found.");
int id1=Integer.parseInt(argArray[0]);
int id2=Integer.parseInt(argArray[1]);
ScriptPlayer.BufferOperation bufferOperation=new ScriptPlayer.BufferOperation(id1,id2,ScriptPlayer.BufferOperation.OP_XOR);
cmds.add(bufferOperation);
} else if(line.startsWith(DELAY_MS)){
args=line.substring(DELAY_MS.length());
args=AStringUtilities.removeLeadingBlanks(args);
args=removeTrailingBlanksAndComments(args);//AStringUtilities.removeTrailingBlanks(args);
if(args.isEmpty())
throw new RuntimeException(DELAY_MS+": arguments not found");
String[] argArray = args.split("\\s");
if(1!=argArray.length)
throw new RuntimeException(DELAY_MS+" take 1 argument, but "+argArray.length+" arguments have been found.");
long delayMs=Integer.parseInt(argArray[0]);
cmds.add(new DelayNs(delayMs*1000000));
} else if(line.startsWith(C_STYLE_MULTILINE_COMMENT_OPEN))
multilineCommentLevel++;
else if(line.startsWith(C_STYLE_MULTILINE_COMMENT_CLOSE))
throw new RuntimeException("C style multiline comment unmatched: multilineCommentLevel="+multilineCommentLevel);
else {
throw new RuntimeException("Unable to parse the line");
}
args="";
}else if(line.startsWith(C_STYLE_MULTILINE_COMMENT_OPEN))
multilineCommentLevel++;
else if(line.startsWith(C_STYLE_MULTILINE_COMMENT_CLOSE))
multilineCommentLevel--;
}
line=br.readLine();
lineNumber++;
}
} catch(RuntimeException e){
throw new InputFileFormatException(SYNTAX_ERROR_HEADER+lineNumber+"\n\n"+line+"\n\nArgument stack:["+args+"]\n",e);
} finally {
br.close();
}
}finally{
fr.close();
}
}
/**
* Get the value of cmds
*
* @return the value of cmds
*/
public List<? extends Object> getCmds() {
return cmds;
}
/**
* Set the value of cmds
*
* @param cmds new value of cmds
*/
public void setCmds(List<? extends Object> cmds) {
this.cmds = (List<Object>) cmds;
}
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
/**
* Add PropertyChangeListener.
*
* @param listener
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
/**
* Remove PropertyChangeListener.
*
* @param listener
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
}