/**
*
* @author Sebastien Riou
*/
package uk.co.nimp.scard;
import com.atolsystems.atolutilities.ACommandLineUtilities;
import com.atolsystems.atolutilities.ACommandLineUtilities.Arg;
import com.atolsystems.atolutilities.ArgSpec;
import com.atolsystems.atolutilities.IoServer;
import com.atolsystems.atolutilities.MutableInteger;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.List;
import java.io.File;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.co.nimp.smartcard.AnswerToReset;
import static uk.co.nimp.scard.MP300Exception.*;
import uk.co.nimp.smartcard.Apdu;
public class RemoteTerminalManager extends GenericTerminalManager {
static transient Integer remoteReaders[];
static final String ARG_HELP = "-help";
static final int ARG_HELP_ID = 0;
static final String ARG_ADD_REMOTE_TERMINAL = "-addRemoteTerminal:";
static final int ARG_ADD_REMOTE_TERMINAL_ID = 1;
static final String CONF_FILE_NAME="RemoteTerminalManager.conf";
static {
remoteReaders = new Integer[0];
//for test
remoteReaders = new Integer[1];
remoteReaders[0]=42533;
//GenericTerminalManager.registerTerminalManager(VirtualTerminalManager.class);
}
public RemoteTerminalManager() throws IOException{
}
protected String getConfFileName(){
return CONF_FILE_NAME;
}
@Override
protected void loadConfigurationImpl(File argFile) throws IOException {
try{
if(argFile.exists()){
ArrayList<String> argList=ACommandLineUtilities.processArgFile(argFile);
if((null==argList) || argList.isEmpty())
return;
String args[] = new String[argList.size()];
argList.toArray(args);
Integer curArgIndex = null;
Arg curArg = null;
curArgIndex = 0;
ArrayList<Integer> virtualTerminalList=new ArrayList<Integer>();
ArgSpec argSpecs[] = {
new ArgSpec(ARG_HELP, ARG_HELP_ID),
new ArgSpec(ARG_ADD_REMOTE_TERMINAL, ARG_ADD_REMOTE_TERMINAL_ID, ArgSpec.UNLIMITED_OCCURENCE),
};
Set<ArgSpec> specs=ACommandLineUtilities.addArgFileArgSpecs(argSpecs);
argSpecs=new ArgSpec[specs.size()];
specs.toArray(argSpecs);
ACommandLineUtilities.checkArgs(args, argSpecs);
while (curArgIndex < args.length) {
curArg = ACommandLineUtilities.getArg(args, argSpecs, curArgIndex, null);
if(null!=curArg){
switch (curArg.id) {
case ARG_HELP_ID:
StringBuilder sb=new StringBuilder();
sb.append("RemoteTerminalManager configuration file help\n\n");
sb.append(ARG_ADD_REMOTE_TERMINAL);
sb.append("<port of the reader>\n");
sb.append("Add a remote reader.\n");
System.out.println(sb.toString());
break;
case ARG_ADD_REMOTE_TERMINAL_ID:
virtualTerminalList.add(Integer.decode(curArg.value));
break;
/*default:
throw new RuntimeException("Internal error related to command line arguments");*/
}
}
curArgIndex++;
}
if(virtualTerminalList.size()>0){
remoteReaders = new Integer[virtualTerminalList.size()];
virtualTerminalList.toArray(remoteReaders);
}
}
}catch(Throwable e){
throw new RuntimeException("Exception occured during processing of configuration file below\n"+argFile.getAbsolutePath()+"\n", e);
}
}
static public void initContext() throws ScardException {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
{
//release all resources
//TODO: currently both calls return "bad argument"
int status = 0;
if (RET_OK != status) {
//throw new ScardException("MPOS_CloseResource returned " + status);
}
}
}
});
}
@Override
public List<GenericTerminal> list() throws ScardException {
List<GenericTerminal> terminalsList = new ArrayList<GenericTerminal>();
for(int i=0;i<remoteReaders.length;i++){
try {
GenericTerminal terminal = getTerminalImpl(remoteReaders[i]);
terminalsList.add(terminal);
} catch (MP300Exception e) {
} catch (IOException e){
}
}
return Collections.unmodifiableList(terminalsList);
}
private static final Map<Integer, Reference<GenericTerminal>> terminals = new HashMap<Integer, Reference<GenericTerminal>>();
protected static GenericTerminal getTerminalImpl(int port) throws ScardException, IOException {
Reference<GenericTerminal> ref = terminals.get(port);
GenericTerminal terminal = (ref != null) ? ref.get() : null;
if (terminal != null) {
return terminal;
}
String name="RemoteTerminal on port "+port;
IoServer context = new IoServer("RemoteTerminalManager", port);
terminal = new RemoteTerminal(name, context);
terminals.put(port, new WeakReference<GenericTerminal>(terminal));
return terminal;
}
static AnswerToReset SCardConnect(IoServer context) throws ScardException {
if(!context.isConnected()) throw new ScardException("card disconnected");
byte atrBuf[] = new byte[2];
atrBuf[0]=0x3F;
atrBuf[1]=0;
AnswerToReset out=new AnswerToReset(atrBuf);
return out;
}
static void SCardTransmit(IoServer context, Apdu apdu) throws ScardException {
if(!context.isConnected()) throw new ScardException("card disconnected");
try {
OutputStream out=context.getOutputStream();
out.write(apdu.getT0Header());
out.flush();
DataInputStream in = new DataInputStream(context.getInputStream());
byte pb;
while(true){
pb= in.readByte();
if(0x60!=pb) break;
}
if(Apdu.isT0Sw1Byte(pb)){//we got status word right away
apdu.setSw1(pb);
pb= in.readByte();
apdu.setSw2(pb);
}else{
if(apdu.getLc()+apdu.getExpectedLe()==0){
if(pb!=apdu.getIns()){
throw new ScardException("Card answered "+pb+" instead of a valid procedure byte");
}else{
if(apdu.getLc()>0){
int nBytes = apdu.getLc();
int curByte = 0;
byte []data = apdu.getLcDataAsBytes();
while(nBytes>curByte){
if(pb==apdu.getIns()){//we got ACK byte
out.write(data, curByte, nBytes-curByte);
out.flush();
curByte=nBytes;
}else if((0xFF & pb)==(0xFF & ~apdu.getIns())){//we got NACK byte
out.write(data[curByte++]);
out.flush();
if(nBytes>curByte)
pb=in.readByte();
}else{
throw new ScardException("Card answered "+pb+" instead of a valid procedure byte");
}
}
if(apdu.getExpectedLe()>0){
throw new RuntimeException("TODO: send get response command");
}
}
if(apdu.getExpectedLe()>0){
int nBytes = apdu.getExpectedLe();
int curByte = 0;
byte []data = new byte[nBytes];
while(nBytes>curByte){
if(pb==apdu.getIns()){//we got ACK byte
in.read(data, curByte, nBytes-curByte);
curByte=nBytes;
}else if((0xFF & pb)==(0xFF & ~apdu.getIns())){//we got NACK byte
data[curByte++]=in.readByte();
if(nBytes>curByte)
pb=in.readByte();
}else{
throw new ScardException("Card answered "+pb+" instead of a valid procedure byte");
}
}
apdu.setLeData(data);
}
}
byte sw1 = in.readByte();
byte sw2 = in.readByte();
apdu.setSw1(sw1);
apdu.setSw2(sw2);
}
}
} catch (IOException ex) {
throw new ScardException(ex);
}
}
static void SCardDisconnect(IoServer context, int disposition) throws ScardException {
context.disconnect();
}
static GenericTerminal.State SCardIsPresent(IoServer context) throws ScardException {
if(context.isConnected())
return GenericTerminal.State.CARD_PRESENT;
return GenericTerminal.State.CARD_ABSENT;
}
}