/**
*
* @author Sebastien Riou
*/
package uk.co.nimp.scard;
import com.atolsystems.atolutilities.AFileChooser;
import uk.co.nimp.scard.log.ScardPrintStreamLogHandler;
import com.atolsystems.atolutilities.AStringUtilities;
import com.atolsystems.atolutilities.InputFileFormatException;
import com.atolsystems.atolutilities.StopRequestFromUserException;
import com.sun.jna.ptr.IntByReference;
import java.io.*;
import java.nio.charset.Charset;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.smartcardio.CardException;
import javax.smartcardio.CardNotPresentException;
import uk.co.nimp.smartcard.Apdu;
import uk.co.nimp.smartcard.UnexpectedCardResponseException;
import static uk.co.nimp.scard.MP65TerminalManager.*;
import static uk.co.nimp.scard.MP300Exception.*;
class MP65Terminal extends GenericContactTerminal{
final protected int couplerId;
final protected int deviceId;
final protected byte[] host;
final static public int DEFAULT_FREQUENCY=4000000;
final static public int DEFAULT_VOLTAGE=5000;
int stoppedClkHertz=-1;
MP65Terminal(int deviceId, int couplerId, String name, byte[] host) {
super(name);
this.deviceId=deviceId;
this.couplerId = couplerId;
this.host=host;
}
@Override
public boolean isCardPresent() throws ScardException {
return MP65TerminalManager.SCardIsPresent(deviceId, couplerId) == GenericTerminal.State.CARD_PRESENT;
}
private boolean waitForCard(boolean wantPresent, long timeout) throws ScardException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout must not be negative");
}
long start = System.currentTimeMillis();
boolean exit = false;
boolean out = false;
do {
if (wantPresent) {
if (MP65TerminalManager.SCardIsPresent(deviceId, couplerId) == GenericTerminal.State.CARD_PRESENT) {
exit = true;
out = true;
}
} else {
if (MP65TerminalManager.SCardIsPresent(deviceId, couplerId) == GenericTerminal.State.CARD_ABSENT) {
exit = true;
out = true;
}
}
if ((0 != timeout) && (System.currentTimeMillis() - start < timeout)) {
exit = true;
}
} while (false == exit);
return out;
}
@Override
public boolean waitForCardPresent(long timeout) throws ScardException {
return waitForCard(true, timeout);
}
@Override
public boolean waitForCardAbsent(long timeout) throws ScardException {
return waitForCard(false, timeout);
}
@Override
public void connectImpl(int protocol, int activation) throws ScardException, CardNotPresentException {
try {
if(connected && (activation==GenericTerminal.ACTIVATION_FORCE_COLD))
SCardDisconnect(deviceId, couplerId);
if(null==vddMillivolts){
SCardSetVdd(deviceId, couplerId, DEFAULT_VOLTAGE);
vddMillivolts=DEFAULT_VOLTAGE;
}
int hertz;
if(null==clkHertz)
hertz=DEFAULT_FREQUENCY;
else
hertz=clkHertz;
atr = SCardConnect(deviceId, couplerId, hertz, protocol, negociateComSpeed);
clkHertz=hertz;
logAtr(atr);
//TODO: retrieve the actual protocol
this.protocol = PROTOCOL_T_0;
state = State.CARD_PRESENT;
} catch (MP300Exception e) {
if (e.code == MP300Exception.CRET_ABSENT) {
throw new CardNotPresentException("No card present", e);
} else {
throw new ScardException("connect() failed", e);
}
}
}
@Override
protected void disconnectImpl() throws ScardException {
if (GenericTerminal.State.CARD_PRESENT != state) {
return;
}
SCardDisconnect(deviceId, couplerId);
}
@Override
protected void forceDisconnectImpl() throws ScardException {
if (GenericTerminal.State.CARD_PRESENT != state) {
return;
}
SCardForceDisconnection(deviceId, couplerId);
}
@Override
public void sendApduImpl(Apdu apdu) throws ScardException, UnexpectedCardResponseException {
int tpduHeader = (0xFF & apdu.getCla());
tpduHeader = (tpduHeader << 8) + (0xFF & apdu.getIns());
tpduHeader = (tpduHeader << 8) + (0xFF & apdu.getP1());
tpduHeader = (tpduHeader << 8) + (0xFF & apdu.getP2());
Apdu.CardResponse response = SCardTransmit(deviceId, couplerId, tpduHeader, apdu.getLcDataAsBytes(), apdu.getExpectedLe());
apdu.setResponse(response);
}
@Override
public void setFrequencyImpl(int hertz) throws ScardException {
if(stoppedClkHertz==hertz){
stoppedClkHertz=-1;
SCardClkStop(deviceId, couplerId, 0,0,win32Mp300ComDll.CLOCK_RESUME);
}else
SCardSetClk(deviceId, couplerId, hertz);
}
@Override
protected void InitSetClkPinCapabilities(){
setClkPinCapabilities=new SetPinCapabilities(true,true,true);
}
@Override
public void setClkPinImpl(boolean high) throws ScardException {
if (high) {
SCardClkStop(deviceId, couplerId, 0,0,win32Mp300ComDll.CLOCK_STOP_HIGH);
} else {
SCardClkStop(deviceId, couplerId, 0,0,win32Mp300ComDll.CLOCK_STOP_LOW );
}
stoppedClkHertz=clkHertz;
clkHertz=0;
}
@Override
public void setVoltageImpl(int millivolts) throws ScardException {
SCardSetVdd(deviceId, couplerId, millivolts);
}
String getNextCommand(BufferedReader reader) throws IOException{
String command="";
do{
command=reader.readLine();
if(null==command)
return "";
}while(command.isEmpty() || command.startsWith("//"));
return command;
}
void execBatchScript(InputStream batchCommands, int nRun) throws ScardException, IOException {
InputStreamReader reader = new InputStreamReader(batchCommands);
execBatchScript(reader, nRun);
}
void execBatchScript(String batchCommands, int nRun) throws ScardException, IOException {
StringReader reader = new StringReader(batchCommands);
execBatchScript(reader, nRun);
}
void execBatchScript(Reader batchCommandsReader, int nRun) throws ScardException, IOException {
int status=0;
BufferedReader reader = new BufferedReader(batchCommandsReader);
IntByReference pdwBatchId=new IntByReference();
status=win32Mp300ComDll.MPS_BatchOpen(pdwBatchId);
if (RET_OK != status) {
throw new ScardException("MPS_BatchOpen returned " + status);
}
StringBuilder debug=new StringBuilder();
int batchId=pdwBatchId.getValue();
String command=getNextCommand(reader);
while(false==command.isEmpty()){
debug.append(command);
status=win32Mp300ComDll.MPS_Add2Batch(batchId, 0, AStringUtilities.StringToBytes(command));
if (RET_OK != status) {
throw new ScardException("MPS_Add2Batch returned " + status);
}
command=getNextCommand(reader);
if(command.startsWith("-->")){
command=command.substring(3);
debug.append(" --> ");
debug.append(command);
status=win32Mp300ComDll.MPS_AddResponse2Batch(batchId, AStringUtilities.StringToBytes(command));
if (RET_OK != status) {
throw new ScardException("MPS_AddResponse2Batch returned " + status);
}
command=getNextCommand(reader);
}
debug.append("\n");
}
IntByReference pdwFaultyLine=new IntByReference();
System.out.println("Start batch:");
System.out.println(debug);
for(int i=1;i<=nRun;i++){
status=win32Mp300ComDll.MPS_ExecuteBatch(batchId, BATCH_EXECUTE_STOP_ON_ERROR, pdwFaultyLine);
if (RET_OK != status) {
throw new MP300Exception("Batch iteration "+i+": MPS_ExecuteBatch returned " + status + "\n" +
"pdwFaultyLine="+pdwFaultyLine.getValue(), status);
}
if(0==(i%25))
System.out.print(".");
if(0==(i%1000))
System.out.println(i+" iterations done");
}
status=win32Mp300ComDll.MPS_CloseBatch(batchId);
if (RET_OK != status) {
throw new MP300Exception("MPS_CloseBatch returned " + status, status);
}
if(0!=(nRun%10))
System.out.println(nRun+" iterations done");
System.out.println("Batch terminated succesfully");
}
static void batchScriptTest(MP65Terminal terminal) throws ScardException, IOException{
String commands=
"CPSA 0 001C0000 00000002 01EF\n"+
"-->CPSA 0000\n"+
"CPSA 0 001A0000 2\n"+
"-->CPSA 0000\n";
terminal.execBatchScript(commands, 1);
}
static void batchScriptFileTest(MP65Terminal terminal, File scriptFile, int nRun) throws ScardException, IOException{
//String commands=(String) AFileUtilities.file2CharSequence(scriptFile, Charset.forName("UTF-16"));
//terminal.execBatchScript(commands, nRun);
InputStreamReader reader = new InputStreamReader(new FileInputStream(scriptFile), Charset.forName("UTF-16"));
terminal.execBatchScript(reader, nRun);
}
//small command line app to execute Micropross remote commands from a file
public static void mainBatch(String[] args) throws ScardException, CardException, Exception {
String batchScriptFileName="mp65script.txt";
int nRun=1;
if(args.length>=1)
batchScriptFileName=args[0];
if(args.length>=2)
nRun=Integer.decode(args[1]);
//create a thread, as recommended by micropross support for stack overflow problems
/*Thread testThread = new Thread() {
@Override
public void run() {
{*/
try {
MP65TerminalManager manager = new MP65TerminalManager();
manager.loadConfiguration(Main.getDefaultConfFolder(), "MP65Test");
List<GenericTerminal> terminals = manager.list();
terminals = manager.list();
terminals = manager.list();
if(0==terminals.size()){
System.out.println("MP65 terminal not detected.");
return;
}
MP65Terminal mp65 = (MP65Terminal) terminals.get(0);
terminals = manager.list();
mp65.addLogHandler(new ScardPrintStreamLogHandler(System.out));
System.out.println("Please insert a card in terminal "+ mp65.getName());
while(false==mp65.isCardPresent()){
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
Logger.getLogger(MP65Terminal.class.getName()).log(Level.SEVERE, null, ex);
}
}
terminals = manager.list();
terminals = manager.list();
System.out.println("Try to connect to " + mp65.getName());
mp65.coldConnect();
/*Apdu apdu = new Apdu(0x00, 0x8A, 0x00, 0x44, 0x02);
mp65.sendApdu(apdu);*/
//manager.batchInterfaceTest();
//batchScriptTest(mp65);
batchScriptFileTest(mp65, new File(batchScriptFileName), nRun);
} catch (IOException ex) {
Logger.getLogger(MP65Terminal.class.getName()).log(Level.SEVERE, null, ex);
} catch (ScardException ex) {
Logger.getLogger(MP65Terminal.class.getName()).log(Level.SEVERE, null, ex);
} catch (CardException ex) {
Logger.getLogger(MP65Terminal.class.getName()).log(Level.SEVERE, null, ex);
}
/* }
}
};
testThread.start();*/
}
public static void main(String[] args) throws Exception {
MP65TerminalManager manager = new MP65TerminalManager();
manager.loadConfiguration(Main.getDefaultConfFolder(), "MP65Test");
List<GenericTerminal> terminals = manager.list();
if(0==terminals.size()){
System.out.println("MP65 terminal not detected.");
return;
}
MP65Terminal mp65 = (MP65Terminal) terminals.get(0);
System.out.println("Connected to " + mp65.getName());
ScriptPlayer player = new ScriptPlayer();
do{
try {
File script = AFileChooser.askForFile("choose a script file", false);
if(null==script) return;
StarScriptReader reader = new StarScriptReader(script);
player.play(mp65, reader.getCmds());
} catch (Throwable ex) {
Logger.getLogger(MP65Terminal.class.getName()).log(Level.SEVERE, null, ex);
}
}while(true);
}
}