package de.persosim.simulator.adapter.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import org.globaltester.simulator.Simulator;
import de.persosim.simulator.CommandParser;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.utils.HexString;
import de.persosim.simulator.utils.Utils;
/**
* This class provides the socket interface to the PersoSim simulator.
*
* It instantiates and manages the communication socket as well as the PersoSim
* kernel and mediates commands/responses between those two. It is also in
* charge of simulating behavior "outside" the card, like power on/off or reset
* of the card. Therefore it provides it's own APDU handler that handles some
* special control APDUs
*
* @author amay
*
*/
public class SocketAdapter implements Runnable {
private static final byte[] ACK = Utils.toUnsignedByteArray(Iso7816.SW_9000_NO_ERROR);
private static final byte[] NACK = Utils.toUnsignedByteArray(Iso7816.SW_6F00_UNKNOWN);
private int port;
private Thread simThread = null;
private boolean isRunning;
private ServerSocket server;
private Socket clientSocket;
private SimulatorProvider simProvider;
/**
* Create new instance.
*
* @param simPort
* port the server socket should listen on
*/
public SocketAdapter(SimulatorProvider simProvider, int simPort) {
this.simProvider = simProvider;
this.port = simPort;
}
/**
* Start execution of the simulation (within its own thread).
*
* If this simulation already owns a (running) Thread this method does
* nothing and returns false.
*
* If the newly created Thread does not start execution within a small
* timeout this method also returns false;
*
* @return true iff a new simulation Thread was created and successfully
* started
*/
public synchronized boolean start() {
// check for existing thread
if (simThread != null) {
// a previous Thread exists, this needs to be stopped before a new
// one can be created
return isRunning();
}
// start the simulator within a thread
simThread = new Thread(this);
simThread.start();
// wait until the just started Thread begins execution
int counter = 0;
while (!isRunning()) {
try {
counter++;
Thread.sleep(500);
if (counter > 4) {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
} ;
return isRunning();
}
public boolean isRunning() {
return isRunning;
}
public boolean stop() {
isRunning = false;
//stop listening for new connections
if (server != null) {
try {
server.close();
} catch (IOException e) {
CommandParser.showExceptionToUser(e);
}
}
// terminate existing client connection
if (clientSocket != null) {
try {
clientSocket.close();
} catch (IOException e) {
CommandParser.showExceptionToUser(e);
}
}
//wait for second thread
if (simThread != null) {
try {
simThread.join();
simThread = null;
} catch (InterruptedException e) {
CommandParser.showExceptionToUser(e);
}
}
return !isRunning();
}
@Override
public void run() {
// open ServerSocket
try {
server = new ServerSocket(port);
} catch (IOException e) {
CommandParser.showExceptionToUser(e);
return; // without an open ServerSocket this method is done
}
// handle connections
isRunning = true;
while (isRunning) {
handleConnection(server);
}
// close ServerSocket
if (server != null) {
try {
server.close();
} catch (IOException e) {
CommandParser.showExceptionToUser(e);
}
}
}
/**
* Handles a single connection from ServerSocket.
*
* @param server
*/
private void handleConnection(ServerSocket server) {
if (server == null) {
// nothing to do without ServerSocket
return;
}
clientSocket = null;
try {
clientSocket = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintStream out = new PrintStream(clientSocket.getOutputStream());
do {
// read APDU from socket
String apduLine = null;
try {
apduLine = in.readLine();
} catch (SocketException e){
//if the other side closed the the connection, this is expected behavior
}
if (apduLine == null) {
// connection closed by peer
break;
}
// parse hex APDU
byte[] apdu = null;
byte[] response = new byte[] { 0x6F, 0x23 };
try {
apdu = HexString.toByteArray(apduLine);
} catch (RuntimeException e) {
CommandParser.showExceptionToUser(e);
// nothing else needs to be done, will lead to an empty
// apdu==null, thus no processing is done and the default SW
// 6F23 is returned
}
// process the APDU, generate response
Simulator sim = simProvider.getSimulator();
// if there is a simulator available, get the response
if (sim != null){
int clains = Utils.maskUnsignedShortToInt(Utils.concatenate(apdu[0], apdu[1]));
switch (clains) {
case 0xFF00:
response = sim.cardPowerDown();
break;
case 0xFF01:
response = sim.cardPowerUp();
break;
case 0xFF6F:
response = NACK;
break;
case 0xFF90:
response = ACK;
break;
case 0xFFFF:
response = sim.cardReset();
break;
default:
// all other (unknown) APDUs are forwarded to the
// simulator processingl
response = sim.processCommand(apdu);
}
}
// encode response and return it
String respLine = HexString.encode(response);
out.println(respLine);
out.flush();
} while (isRunning);
} catch (IOException e) {
//show the exception only if the server is still running, otherwise it is expected behavior
if (isRunning) {
CommandParser.showExceptionToUser(e);
}
} finally {
if (clientSocket != null) {
try {
clientSocket.close();
} catch (IOException e) {
CommandParser.showExceptionToUser(e);
}
}
}
}
}