/* * Copyright 2015 Robert Bachmann * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.licel.jcardsim.smartcardio; import com.licel.jcardsim.base.CardManager; import com.licel.jcardsim.base.SimulatorRuntime; import com.licel.jcardsim.io.JavaxSmartCardInterface; import javax.smartcardio.*; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicReference; /** * Simulates a JavaCard. * * @see com.licel.jcardsim.smartcardio.CardTerminalSimulator */ public class CardSimulator extends JavaxSmartCardInterface { private final CardImpl card = new CardImpl(); private final AtomicReference<CardTerminal> owningCardTerminalReference = new AtomicReference<CardTerminal>(); private final AtomicReference<Thread> threadReference = new AtomicReference<Thread>(); /** * Create a Simulator object using a new SimulatorRuntime. * <ul> * <li>SimulatorRuntime#resetRuntime is called</li> * </ul> */ public CardSimulator() { this(new SimulatorRuntime()); } /** * Create a Simulator object using a provided Runtime. * <ul> * <li>SimulatorRuntime#resetRuntime is called</li> * </ul> * * @param runtime SimulatorRuntime instance to use * @throws java.lang.NullPointerException if <code>runtime</code> is null */ public CardSimulator(SimulatorRuntime runtime) { super(runtime); } /** * Wrapper for {@link #transmitCommand(byte[])} * * @param commandApdu CommandAPDU * @return ResponseAPDU */ @Override public ResponseAPDU transmitCommand(CommandAPDU commandApdu) { return new ResponseAPDU(transmitCommand(commandApdu.getBytes())); } /** * <p>Assigns this simulated card to a CardTerminal.</p> * <p>If the card is already assigned to another CardTerminal, it will be ejected * and inserted into the CardTerminal <code>terminal</code>.</p> * * @param terminal card terminal or <code>null</code> */ public synchronized void assignToTerminal(CardTerminal terminal) { final CardTerminal oldCardTerminal = owningCardTerminalReference.getAndSet(terminal); if (terminal == oldCardTerminal) { return; } if (oldCardTerminal != null) { // eject card from old Terminal ((CardTerminalSimulator.CardTerminalImpl) oldCardTerminal).assignSimulator(null); } if (terminal != null) { // reset card card.disconnect(); // assign to new terminal ((CardTerminalSimulator.CardTerminalImpl) terminal).assignSimulator(this); } } /** * @return the assigned CardTerminal or null if none is assigned */ public CardTerminal getAssignedCardTerminal() { return owningCardTerminalReference.get(); } final Card internalConnect(String protocol) { card.connect(protocol); return card; } final void internalEject(CardTerminal oldTerminal) { if (owningCardTerminalReference.compareAndSet(oldTerminal, null)) { card.eject(); } } private enum CardState { Connected, Disconnected, Ejected } private static final class CardChannelImpl extends CardChannel { private final CardImpl card; private final int channelNr; public CardChannelImpl(CardImpl card, int channelNr) { this.card = card; this.channelNr = channelNr; } @Override public Card getCard() { return card; } @Override public int getChannelNumber() { card.ensureConnected(); return channelNr; } @Override public ResponseAPDU transmit(CommandAPDU commandAPDU) throws CardException { return new ResponseAPDU(card.transmitCommand(commandAPDU.getBytes())); } @Override public int transmit(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) throws CardException { byte[] result = card.transmitCommand(new CommandAPDU(byteBuffer).getBytes()); byteBuffer2.put(result); return result.length; } @Override public void close() throws CardException { throw new CardException("Can not close basic channel"); } } private final class CardImpl extends Card { private final CardChannel basicChannel; private volatile String protocol = "T=0"; private volatile byte protocolByte = 0; private volatile CardState state = CardState.Connected; CardImpl() { this.basicChannel = new CardChannelImpl(this, 0); } void ensureConnected() { CardState cardState = state; if (cardState == CardState.Disconnected) { throw new IllegalStateException("Card was disconnected"); } else if (cardState == CardState.Ejected) { throw new IllegalStateException("Card was removed"); } } @Override public ATR getATR() { return new ATR(CardSimulator.this.getATR()); } @Override public String getProtocol() { return protocol; } @Override public CardChannel getBasicChannel() { return basicChannel; } @Override public CardChannel openLogicalChannel() throws CardException { throw new CardException("Logical channel not supported"); } @Override public void beginExclusive() throws CardException { synchronized (runtime) { if (!threadReference.compareAndSet(null, Thread.currentThread())) { throw new CardException("Card is held exclusively by Thread " + threadReference.get()); } } } @Override public void endExclusive() throws CardException { synchronized (runtime) { if (!threadReference.compareAndSet(Thread.currentThread(), null)) { throw new CardException("Card is held exclusively by Thread " + threadReference.get()); } } } @Override public byte[] transmitControlCommand(int i, byte[] bytes) throws CardException { throw new UnsupportedOperationException("Not supported yet."); } @Override public void disconnect(boolean reset) throws CardException { synchronized (runtime) { if (reset) { CardSimulator.this.reset(); } state = CardState.Disconnected; } } void connect(String protocol) { synchronized (runtime) { this.protocolByte = CardSimulator.this.getProtocolByte(protocol); this.protocol = protocol; this.state = CardState.Connected; } } void eject() { synchronized (runtime) { CardSimulator.this.reset(); state = CardState.Ejected; } } void disconnect() { synchronized (runtime) { CardSimulator.this.reset(); state = CardState.Disconnected; } } byte[] transmitCommand(byte[] capdu) throws CardException { synchronized (runtime) { ensureConnected(); Thread thread = threadReference.get(); if (thread != null && thread != Thread.currentThread()) { throw new CardException("Card is held exclusively by Thread " + thread.getName()); } byte currentProtocol = getProtocolByte(CardSimulator.this.getProtocol()); try { runtime.changeProtocol(protocolByte); return CardManager.dispatchApdu(CardSimulator.this, capdu); } finally { runtime.changeProtocol(currentProtocol); } } } } }