/*
*/
/**
*
* @author Sebastien Riou
*/
package uk.co.nimp.scard;
import java.io.IOException;
import java.util.List;
import javax.smartcardio.CardNotPresentException;
import uk.co.nimp.smartcard.AnswerToReset;
import uk.co.nimp.smartcard.Apdu;
import uk.co.nimp.smartcard.UnexpectedCardResponseException;
import static uk.co.nimp.scard.NimpPcScTerminalManager.*;
class NimpPcScTerminal extends GenericTerminal {
final long contextId;
protected Long cardId;
NimpPcScTerminal(long contextId, String name) {
super(name);
this.contextId = contextId;
cardId = null;
}
@Override
public boolean isCardPresent() throws ScardException {
int[] status = NimpPcScTerminalManager.SCardGetStatusChange(contextId, 0,
new int[]{NimpPcScTerminalManager.SCARD_STATE_UNAWARE}, new String[]{name});
state = NimpPcScTerminalManager.win32Status2GenericTerminalState((byte) status[0]);
return (status[0] & NimpPcScTerminalManager.SCARD_STATE_PRESENT) != 0;
}
private boolean waitForCard(boolean wantPresent, long timeout) throws ScardException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout must not be negative");
}
if (timeout == 0) {
timeout = NimpPcScTerminalManager.TIMEOUT_INFINITE;
}
int[] status = new int[]{NimpPcScTerminalManager.SCARD_STATE_UNAWARE};
String[] readers = new String[]{name};
try {
// check if card status already matches
status = NimpPcScTerminalManager.SCardGetStatusChange(contextId, 0, status, readers);
state = NimpPcScTerminalManager.win32Status2GenericTerminalState((byte) status[0]);
boolean present = (status[0] & NimpPcScTerminalManager.SCARD_STATE_PRESENT) != 0;
if (wantPresent == present) {
return true;
}
// no match, wait
status = NimpPcScTerminalManager.SCardGetStatusChange(contextId, timeout, status, readers);
present = (status[0] & NimpPcScTerminalManager.SCARD_STATE_PRESENT) != 0;
// should never happen
if (wantPresent != present) {
throw new ScardException("wait mismatch");
}
return true;
} catch (PcScException e) {
if (e.code == NimpPcScTerminalManager.SCARD_E_TIMEOUT) {
return false;
} else {
throw new ScardException("waitForCard() failed", e);
}
}
}
@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 {
int sharingMode = SCARD_SHARE_SHARED;
//int sharingMode = SCARD_SHARE_EXCLUSIVE;
int connectProtocol;
int activationPolicy;
switch (protocol) {
case PROTOCOL_T_0:
connectProtocol = SCARD_PROTOCOL_T0;
break;
case PROTOCOL_T_1:
connectProtocol = SCARD_PROTOCOL_T1;
break;
case PROTOCOL_DIRECT:
connectProtocol = 0;
sharingMode = SCARD_SHARE_DIRECT;
break;
case PROTOCOL_ANY:
connectProtocol = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1;
break;
case PROTOCOL_T_15:
case PROTOCOL_T_CL_A:
case PROTOCOL_T_CL_B:
case PROTOCOL_ANY_STD_CL:
default:
throw new IllegalArgumentException("protocol = " + GenericTerminal.getProtocolName(protocol));
}
switch (activation) {
case ACTIVATION_FORCE_COLD:
activationPolicy = SCARD_UNPOWER_CARD;
break;
case ACTIVATION_FORCE_WARM:
activationPolicy = SCARD_RESET_CARD;
break;
case ACTIVATION_ANY:
//activationPolicy = SCARD_LEAVE_CARD;
activationPolicy = SCARD_RESET_CARD;
break;
default:
throw new IllegalArgumentException("activation = " + activation);
}
atr = null;
if (null == cardId) {
try {
cardId = SCardConnect(contextId, name, sharingMode, connectProtocol);
byte[] status = new byte[2];
byte[] atrBytes = SCardStatus(cardId, status);
atr = new AnswerToReset(atrBytes);
if (ACTIVATION_ANY == activation) {
logAtr(atr);
}
this.protocol = NimpPcScTerminalManager.win32Protocol2GenericTerminalProtocol(status[1]);
//basicChannel = new ChannelImpl(this, 0);
state = State.CARD_PRESENT;
} catch (PcScException e) {
if (e.code == NimpPcScTerminalManager.SCARD_W_REMOVED_CARD) {
throw new CardNotPresentException("No card present", e);
} else {
throw new ScardException("connect() failed", e);
}
}
}
if ((null == atr) || (ACTIVATION_ANY != activation)) {
try {
SCardReconnect(cardId, sharingMode, connectProtocol, activationPolicy);
byte[] status = new byte[2];
byte[] atrBytes = SCardStatus(cardId, status);
atr = new AnswerToReset(atrBytes);
logAtr(atr);
this.protocol = NimpPcScTerminalManager.win32Protocol2GenericTerminalProtocol(status[1]);
//basicChannel = new ChannelImpl(this, 0);
state = State.CARD_PRESENT;
} catch (PcScException e) {
if (e.code == NimpPcScTerminalManager.SCARD_W_REMOVED_CARD) {
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;
}
if (null != cardId) {
long id = cardId;
cardId = null;
SCardDisconnect(id, NimpPcScTerminalManager.SCARD_UNPOWER_CARD);
}
}
@Override
protected void forceDisconnectImpl() throws ScardException {
disconnectImpl();
}
private byte[] concat(byte[] b1, byte[] b2, int n2) {
int n1 = b1.length;
if ((n1 == 0) && (n2 == b2.length)) {
return b2;
}
byte[] res = new byte[n1 + n2];
System.arraycopy(b1, 0, res, 0, n1);
System.arraycopy(b2, 0, res, n1, n2);
return res;
}
private final static byte[] B0 = new byte[0];
//private boolean t0GetResponse =true;
//private boolean t1GetResponse =true;
private final static boolean t1StripLe = false;
private byte[] doTransmit(byte[] command, boolean t0GetResponse, boolean t1GetResponse) throws ScardException {
// note that we modify the 'command' array in some cases, so it must
// be a copy of the application provided data.
if (null == cardId) {
throw new ScardException("Connection with card is not established.");
}
int n = command.length;
boolean t0 = protocol == SCARD_PROTOCOL_T0;
boolean t1 = protocol == SCARD_PROTOCOL_T1;
if (t0 && (n >= 7) && (command[4] == 0)) {
throw new ScardException("Extended length forms not supported for T=0");
}
if ((t0 || (t1 && t1StripLe)) && (n >= 7)) {
int lc = command[4] & 0xff;
if (lc != 0) {
if (n == lc + 6) {
n--;
}
} else {
lc = ((command[5] & 0xff) << 8) | (command[6] & 0xff);
if (n == lc + 9) {
n -= 2;
}
}
}
boolean getresponse = (t0 && t0GetResponse) || (t1 && t1GetResponse);
int k = 0;
byte[] result = B0;
while (true) {
if (++k >= 32) {
throw new ScardException("Could not obtain response");
}
byte[] response = SCardTransmit(cardId, protocol, command, 0, n);
int rn = response.length;
if (getresponse && (rn >= 2)) {
// see ISO 7816/2005, 5.1.3
if ((rn == 2) && (response[0] == 0x6c)) {
// Resend command using SW2 as short Le field
command[n - 1] = response[1];
continue;
}
if (response[rn - 2] == 0x61) {
// Issue a GET RESPONSE command with the same CLA
// using SW2 as short Le field
if (rn > 2) {
result = concat(result, response, rn - 2);
}
command[1] = (byte) 0xC0;
command[2] = 0;
command[3] = 0;
command[4] = response[rn - 1];
n = 5;
continue;
}
}
result = concat(result, response, rn);
break;
}
return result;
}
@Override
public void sendApduImpl(Apdu apdu) throws ScardException, UnexpectedCardResponseException {
byte[] commandBytes = apdu.getCommandAPDU().getBytes();
byte[] responseBytes;
/*Timer apduTimer=null;
ApduTimeoutTask apduTimeoutTask=null;
if(timeoutMs!=0){
apduTimer=new Timer();
apduTimeoutTask=new ApduTimeoutTask();
apduTimer.schedule(apduTimeoutTask, timeoutMs);
}*/
if (0 == apdu.getExpectedLe()) {
responseBytes = doTransmit(commandBytes, false, false);//when no data expected, disable automatic sending of get response
} else {
responseBytes = doTransmit(commandBytes, autoGetResponse, autoGetResponse);
}
/*if(timeoutMs!=0){
apduTimer.cancel();
apdu.setResponse(responseBytes);
if(apduTimeoutTask.expired)
throw new ScardException("Timeout for this apdu expired (timeout was "+timeoutMs+"ms)");
}else*/
apdu.setResponse(responseBytes);
}
public static void main(String[] args) throws ScardException, IOException, CardNotPresentException, Exception {
NimpPcScTerminalManager manager = new NimpPcScTerminalManager();
manager.loadConfiguration(Main.getDefaultConfFolder(), "PcScTest");
List<GenericTerminal> terminals = manager.list();
if (0 == terminals.size()) {
System.out.println("PcSc terminal not detected.");
return;
}
GenericTerminal terminal = terminals.get(0);
Apdu test=new Apdu("001C0000");
terminal.coldConnect();
System.out.println("connected to "+terminal+ " using protocol "+GenericTerminal.getProtocolName(terminal.getProtocol()));
long start=System.currentTimeMillis();
for(int i=0;i<1000;i++){
terminal.sendApdu(test);
}
long end=System.currentTimeMillis();
long delta=end-start;
System.out.println("delta= "+delta+" ms");
}
}