package de.persosim.simulator;
import static org.globaltester.logging.BasicLogger.ERROR;
import static org.globaltester.logging.BasicLogger.INFO;
import static org.globaltester.logging.BasicLogger.WARN;
import static org.globaltester.logging.BasicLogger.log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.JAXBException;
import org.eclipse.core.runtime.FileLocator;
import org.globaltester.simulator.Simulator;
import org.osgi.framework.Bundle;
import de.persosim.simulator.perso.Personalization;
import de.persosim.simulator.perso.PersonalizationFactory;
import de.persosim.simulator.utils.HexString;
/**
* This class provides methods that parse console commands for the control of
* the Simulator and calls the corresponding methods of the {@link Simulator}
* interface.
*
* @author mboonk
*
*/
public class CommandParser {
public static final int DEFAULT_SIM_PORT = 9876;
public static final String DEFAULT_SIM_HOST = "localhost";
public static final String CMD_START = "start";
public static final String CMD_RESTART = "restart";
public static final String CMD_STOP = "stop";
public static final String CMD_EXIT = "exit";
public static final String ARG_SET_PORT = "-port";
public static final String CMD_LOAD_PERSONALIZATION = "loadperso";
public static final String ARG_LOAD_PERSONALIZATION = "-perso";
public static final String CMD_SEND_APDU = "sendapdu";
public static final String CMD_HELP = "help";
public static final String ARG_HELP = "-h";
public static final String CMD_CONSOLE_ONLY = "--consoleOnly";
public static final String LOG_UNKNOWN_ARG = "unknown argument";
public static final String LOG_NO_OPERATION = "nothing to process";
private static boolean processingCommandLineArguments = false;
public static final String PERSO_PATH = "personalization/profiles/";
public static final String PERSO_FILE_PREFIX = "Profile";
public static final String PERSO_FILE_POSTFIX = ".perso";
/**
* This method processes the command for starting the simulator.
* @param args arguments that may contain a start command
* @return whether instantiation and starting was successful
*/
public static boolean cmdStartSimulator(List<String> args) {
if((args != null) && (args.size() >= 1)) {
String cmd = args.get(0);
if(cmd.equals(CMD_START)) {
args.remove(0);
de.persosim.simulator.Activator.getDefault().enableService();
if (getPersoSim() == null) {
log(CommandParser.class, "Enabling the PersoSimService failed", ERROR);
}
return getPersoSim().startSimulator();
}
}
return false;
}
/**
* This method processes the command for stopping the simulator.
* @param args arguments that may contain a stop command
* @return whether stopping was successful
*/
public static boolean cmdStopSimulator(List<String> args) {
if((args != null) && (args.size() >= 1)) {
String cmd = args.get(0);
if(cmd.equals(CMD_STOP)) {
args.remove(0);
if(getPersoSim() != null) {
de.persosim.simulator.Activator.getDefault().disableService();
return true;
}
else {
log(CommandParser.class, "No running PersoSimService found", WARN);
}
}
}
return false;
}
/**
* This method processes the command for restarting the simulator.
* @param args arguments that may contain a restart command
* @return whether restarting was successful
*/
public static boolean cmdRestartSimulator(List<String> args) {
if((args != null) && (args.size() >= 1)) {
String cmd = args.get(0);
if(cmd.equals(CMD_RESTART)) {
args.remove(0);
if(getPersoSim() != null)
return getPersoSim().restartSimulator();
else
log(CommandParser.class, "No running PersoSimService found", WARN);
}
}
return false;
}
/**
* This method processes the send APDU command according to the provided arguments.
* @param args the arguments provided for processing
* @return whether processing has been successful
*/
public static String cmdSendApdu(List<String> args) {
if((args != null) && (args.size() >= 2)) {
String cmd = args.get(0);
if(cmd.equals(CMD_SEND_APDU)) {
String result;
if(getPersoSim() != null) {
try{
PersoSim sim = getPersoSim();
result = sendCmdApdu(sim, "sendApdu " + args.get(1));
args.remove(0);
args.remove(0);
return result;
} catch(RuntimeException e) {
result = "unable to send APDU, reason is: " + e.getMessage();
args.remove(0);
return result;
}
} else {
log(CommandParser.class, "Please enable the PersoSimService before sending apdus", WARN);
return "";
}
} else{
return "no send APDU command";
}
} else{
return "missing parameter for APDU content";
}
}
/**
* This method prints the help menu to the command line.
*/
private static void printHelpArgs() {
log(CommandParser.class, "Available commands:", INFO);
log(CommandParser.class, ARG_LOAD_PERSONALIZATION + " <file name>", INFO);
log(CommandParser.class, ARG_SET_PORT + " <port number>", INFO);
log(CommandParser.class, ARG_HELP, INFO);
}
/**
* This method prints the help menu to the user command line.
*/
private static void printHelpCmd() {
log(CommandParser.class, "Available commands:", INFO);
log(CommandParser.class, CMD_SEND_APDU + " <hexstring>", INFO);
log(CommandParser.class, CMD_LOAD_PERSONALIZATION + " <file name>", INFO);
log(CommandParser.class, CMD_START, INFO);
log(CommandParser.class, CMD_RESTART, INFO);
log(CommandParser.class, CMD_STOP, INFO);
log(CommandParser.class, CMD_HELP, INFO);
}
/**
* This method processes the load personalization command according to the provided arguments.
* @param args the arguments provided for processing the load personalization command
* @return whether processing of the load personalization command has been successful
*/
public static boolean cmdLoadPersonalization(List<String> args) {
if((args != null) && (args.size() >= 2)) {
String cmd = args.get(0);
if(cmd.equals(CMD_LOAD_PERSONALIZATION) || cmd.equals(ARG_LOAD_PERSONALIZATION)) {
String arg = args.get(1);
args.remove(0);
args.remove(0);
Personalization perso = getPerso(arg);
if (perso != null) {
PersoSim sim = getPersoSim();
if (sim != null) {
if (sim.loadPersonalization(perso)){
return true;
}
} else {
log(CommandParser.class, "Please enable the PersoSimService before loading a personalization", WARN);
}
}
}
}
return false;
}
/**
* This method parses the given identifier and loads the personalization
* @param identifier
* @return a personalization object
*/
private static Personalization getPerso(String identifier){
String filePath = "";
int personalizationNumber = 0;
File xmlFile = new File(identifier);
if (xmlFile.exists() && xmlFile.isFile()) {
filePath = identifier;
} else {
//try to parse the given identifier as profile number
try {
personalizationNumber = Integer.parseInt(identifier);
} catch (NumberFormatException e) {
log(CommandParser.class, "identifier is no valid path or profile number!", ERROR);
return null;
}
if(personalizationNumber > 10) {
log(CommandParser.class, "personalization profile no: " + personalizationNumber + " does not exist", INFO);
return null;
}
log(CommandParser.class, "trying to load personalization profile no: " + personalizationNumber, INFO);
Bundle plugin = Activator.getContext().getBundle();
URL url = plugin.getEntry (PERSO_PATH);
URL resolvedUrl;
File folder = null;
try {
resolvedUrl = FileLocator.resolve(url);
folder = new File(resolvedUrl.getFile());
} catch (IOException e) {
log(CommandParser.class, e.getMessage(), ERROR);
}
if (personalizationNumber < 10) {
identifier = "0" + personalizationNumber;
}
filePath = folder.getAbsolutePath() + File.separator + PERSO_FILE_PREFIX + identifier + PERSO_FILE_POSTFIX;
}
//actually load perso from the identified file
try{
return parsePersonalization(filePath);
} catch(FileNotFoundException e) {
log(CommandParser.class, "unable to set personalization, reason is: " + e.getMessage(), ERROR);
log(CommandParser.class, "simulation is stopped", ERROR);
return null;
}
}
/**
* This method parses a {@link Personalization} object from a file identified by its name.
* @param persoFileName the name of the file to contain the personalization
* @return the parsed personalization
* @throws FileNotFoundException
* @throws JAXBException if parsing of personalization not successful
*/
public static Personalization parsePersonalization(String persoFileName) throws FileNotFoundException {
log(CommandParser.class, "Parsing personalization from file " + persoFileName, INFO);
return (Personalization) PersonalizationFactory.unmarshal(persoFileName);
}
public static void executeUserCommands(String... args) {
if((args == null) || (args.length == 0)) {log(CommandParser.class, LOG_NO_OPERATION, INFO); return;}
ArrayList<String> currentArgs = new ArrayList<String>(Arrays.asList(args)); // plain return value of Arrays.asList() does not support required remove operation
for(int i = currentArgs.size() - 1; i >= 0; i--) {
if(currentArgs.get(i) == null) {
currentArgs.remove(i);
}
}
if(currentArgs.size() == 0) {log(CommandParser.class, LOG_NO_OPERATION, INFO); return;}
int noOfArgsWhenCheckedLast;
while(currentArgs.size() > 0) {
noOfArgsWhenCheckedLast = currentArgs.size();
cmdLoadPersonalization(currentArgs);
cmdSendApdu(currentArgs);
cmdStartSimulator(currentArgs);
cmdRestartSimulator(currentArgs);
cmdStopSimulator(currentArgs);
cmdHelp(currentArgs);
if(noOfArgsWhenCheckedLast == currentArgs.size()) {
//first command in queue has not been processed
String currentArgument = currentArgs.get(0);
log(CommandParser.class, LOG_UNKNOWN_ARG + " \"" + currentArgument + "\" will be ignored, processing of arguments stopped", WARN);
currentArgs.remove(0);
printHelpCmd();
break;
}
}
}
/**
* This method implements the execution of commands initiated by command line arguments.
* @param args the parsed commands and arguments
*/
public static void handleArgs(Simulator sim, String... args) {
if((args == null) || (args.length == 0)) {log(CommandParser.class, LOG_NO_OPERATION, INFO); return;}
processingCommandLineArguments = true;
List<String> currentArgs = Arrays.asList(args);
// the list returned by Arrays.asList() does not support optional but required remove operation
currentArgs = new ArrayList<String>(currentArgs);
for(int i = currentArgs.size() - 1; i >= 0; i--) {
if(currentArgs.get(i) == null) {
currentArgs.remove(i);
}
}
if(currentArgs.size() == 0) {log(CommandParser.class, LOG_NO_OPERATION, INFO); return;}
int noOfArgsWhenCheckedLast;
while(currentArgs.size() > 0) {
noOfArgsWhenCheckedLast = currentArgs.size();
cmdLoadPersonalization(currentArgs);
cmdHelp(currentArgs);
if(currentArgs.size() > 0) {
if(currentArgs.get(0).equals(CMD_CONSOLE_ONLY)) {
// do no actual processing, i.e. prevent simulator from logging unknown command error as command has already been processed
// command is passed on as part of unprocessed original command line arguments
currentArgs.remove(0);
}
}
if(noOfArgsWhenCheckedLast == currentArgs.size()) {
//first command in queue has not been processed
String currentArgument = currentArgs.get(0);
log(CommandParser.class, LOG_UNKNOWN_ARG + " \"" + currentArgument + "\" will be ignored, processing of arguments stopped", ERROR);
currentArgs.remove(0);
printHelpCmd();
break;
}
}
processingCommandLineArguments = false;
}
public static boolean cmdHelp(List<String> args) {
if((args != null) && (args.size() >= 1)) {
String cmd = args.get(0);
if(cmd.equals(CMD_HELP) || cmd.equals(ARG_HELP)) {
args.remove(0);
if(processingCommandLineArguments) {
printHelpArgs();
} else{
printHelpCmd();
}
return true;
}
}
return false;
}
/**
* Transmit an APDU to the card
*
* @param cmd
* string containing the command
* @return the response
*/
private static String sendCmdApdu(Simulator sim, String cmd) {
cmd = cmd.trim();
Pattern cmdSendApduPattern = Pattern
.compile("^send[aA]pdu ([0-9a-fA-F\\s]+)$");
Matcher matcher = cmdSendApduPattern.matcher(cmd);
if (!matcher.matches()) {
throw new RuntimeException("invalid arguments to sendApdu");
}
String apdu = matcher.group(1);
return exchangeApdu(sim, apdu);
}
/**
* Transmit the given APDU to the simulator where it will be processed
* and answered by a response. The response APDU is received from the
* simulator and returned to the caller as HexString.
*
* @param cmdApdu HexString containing the CommandAPDU
* @return the response
*/
private static String exchangeApdu(Simulator sim, String cmdApdu) {
cmdApdu = cmdApdu.replaceAll("\\s", ""); // remove any whitespace
String respApdu = HexString.dump(sim.processCommand(HexString.toByteArray(cmdApdu)));
log(CommandParser.class, "> " + cmdApdu, INFO);
log(CommandParser.class, "< " + respApdu, INFO);
return respApdu;
}
/**
* This method parses the provided String object for commands and possible
* arguments. First the provided String is trimmed. If the String is empty,
* the returned array will be of length 0. If the String does not contain at
* least one space character ' ', the whole String will be returned as first
* and only element of an array of length 1. If the String does contain at
* least one space character ' ', the substring up to but not including the
* position of the first occurrence will be the first element of the
* returned array. The rest of the String will be trimmed and, if not of
* length 0, form the second array element.
*
* IMPL extend to parse for multiple arguments add recognition of "
* characters as indication of file names allowing white spaces in between.
*
* @param args
* the argument String to be parsed
* @return the parsed arguments
*/
public static String[] parseCommand(String args) {
String argsInput = args.trim();
int index = argsInput.indexOf(" ");
if(index >= 0) {
String cmd = argsInput.substring(0, index);
String params = argsInput.substring(index).trim();
return new String[]{cmd, params};
} else{
if(argsInput.length() > 0) {
return new String[]{argsInput};
} else{
return new String[0];
}
}
}
public static void executeUserCommands(String cmd) {
String trimmedCmd = cmd.trim();
String[] args = parseCommand(trimmedCmd);
executeUserCommands(args);
}
public static void showExceptionToUser(Exception e) {
log(CommandParser.class, "Exception: " + e.getMessage(), INFO);
e.printStackTrace();
}
private static PersoSim getPersoSim() {
return de.persosim.simulator.Activator.getDefault().getSim();
}
}