package de.persosim.simulator.platform;
import static org.globaltester.logging.BasicLogger.TRACE;
import static org.globaltester.logging.BasicLogger.log;
import static org.globaltester.logging.BasicLogger.logException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import de.persosim.simulator.apdu.ResponseApdu;
import de.persosim.simulator.cardobjects.MasterFile;
import de.persosim.simulator.exception.AccessDeniedException;
import de.persosim.simulator.exception.ProcessingException;
import de.persosim.simulator.processing.UpdatePropagation;
import de.persosim.simulator.protocols.Protocol;
import de.persosim.simulator.protocols.ProtocolStateMachine;
import de.persosim.simulator.protocols.ProtocolUpdate;
import de.persosim.simulator.secstatus.SecMechanism;
import de.persosim.simulator.secstatus.SecStatus;
import de.persosim.simulator.secstatus.SecStatus.SecContext;
import de.persosim.simulator.secstatus.SecStatusMechanismUpdatePropagation;
import de.persosim.simulator.statemachine.AbstractStateMachine;
import de.persosim.simulator.statemachine.StateMachine;
/**
* This class implements the processing of CommandApdus. It orchestrates
* registered protocols and mediates between those protocols and the card
* internal state, such as {@link SecStatus} and the object tree.
*
* @author amay
*
*/
public abstract class AbstractCommandProcessor extends Layer implements
CardStateAccessor, StateMachine {
public static final String COMMANDPROCESSOR = "CommandProcessor";
// -------------------------------------------------------
// Methods/fields to implement {@link Layer} functionality
// -------------------------------------------------------
@Override
public String getLayerName() {
return COMMANDPROCESSOR;
}
@Override
public void processAscending() {
log(this, "will now begin processing of ascending APDU", TRACE);
try {
securityStatus.updateSecStatus(processingData);
//process the event
int event = 0xFF;
if (processingData.getCommandApdu() != null) {
event = processingData.getCommandApdu().getIns();
}
this.processEvent(event);
//convert internal SW if required
if (processingData.getResponseApdu() != null && PlatformUtil.is4xxxStatusWord(processingData.getResponseApdu().getStatusWord())){
log(this, "APDU contents could not be processed by any protocol");
ResponseApdu rApdu = new ResponseApdu(PlatformUtil.convert4xxxTo6xxxStatusWord(processingData.getResponseApdu().getStatusWord()));
this.processingData.updateResponseAPDU(this,
"No protocol was able to process the APDU contents", rApdu);
}
securityStatus.updateSecStatus(processingData);
log(this, "successfully processed ascending APDU", TRACE);
} catch (Exception e) {
logException(this, e, TRACE);
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6FFF_IMPLEMENTATION_ERROR);
this.processingData.updateResponseAPDU(this,
"Generic error handling", resp);
}
}
@Override
public void powerOn() {
super.powerOn();
log(this, "powerOn, remove all protocols from stack", TRACE);
setStackPointerToBottom();
removeCurrentProtocolAndAboveFromStack();
reset();
log(this, "powerOn, reset SecStatus", TRACE);
securityStatus.reset();
}
// ---------------------------------------------------
// methods/fields handling/representing the card state
// ---------------------------------------------------
protected transient SecStatus securityStatus;
protected MasterFile masterFile;
/**
* Adds a new protocol to the list of available protocols. The new protocol
* is added at the end of the list.
* <p/>
* This method does not provide any consistency checking. It is in the
* responsibility of the caller to make sure that only valid protocols are
* added (e.g. that duplicate protocols are only added if the given protocol
* supports this behavior).
*
* @param newProtocol
* protocol to add, this is expected to be initialized and ready
* to use
*/
public void addProtocol(Protocol newProtocol) {
protocols.add(newProtocol);
}
// --------------------------------------------------------
// methods implementing {@link CardStateAccessor} interface
// --------------------------------------------------------
@Override
public Collection<SecMechanism> getCurrentMechanisms(SecContext context,
Collection<Class<? extends SecMechanism>> wantedMechanisms) {
return securityStatus.getCurrentMechanisms(context, wantedMechanisms);
}
@Override
public MasterFile getMasterFile() {
return masterFile;
}
// ---------------------------------------------------
// Methods/fields used from within state machine code.
// ---------------------------------------------------
/**
* List of available protocols
*/
protected List<Protocol> protocols = new ArrayList<>();
private transient Protocol currentlyActiveProtocol;
/**
* stackPointer is a pointer pointing at an element of protocolStack, i.e.
* the currently active/unfinished/interrupted protocols
*/
protected transient int stackPointer;
/**
* the stack containing all active/unfinished/interrupted protocols
*/
protected transient ArrayList<Protocol> protocolStack;
/**
* protocolPointer is a pointer pointing at an element of protocols, i.e.
* the list of known/supported protocols
*/
protected transient int protocolPointer;
public void setStackPointerToBottom() {
this.stackPointer = 0;
}
public boolean stackPointerIsNull() {
if ((this.protocolStack.size() == 0)
|| (this.protocolStack.size() <= this.stackPointer)) {
return true;
}
return false;
}
public void makeStackPointerCurrentlyActiveProtocol() {
setCurrentlyActiveProtocol(this.protocolStack.get(this.stackPointer));
log(this, "currently active protocol is now: "
+ getCurrentlyActiveProtocol().getProtocolName());
}
protected Protocol getCurrentlyActiveProtocol() {
return currentlyActiveProtocol;
}
protected void setCurrentlyActiveProtocol(Protocol nextActiveProtocol) {
currentlyActiveProtocol = nextActiveProtocol;
ProtocolMechanism protocolMechanism;
if(nextActiveProtocol == null) {
// this effectively deletes the security mechanism
protocolMechanism = null;
} else{
protocolMechanism = new ProtocolMechanism(currentlyActiveProtocol.getClass());
}
SecStatusMechanismUpdatePropagation updatePropagation = new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, protocolMechanism);
securityStatus.updateMechanisms(updatePropagation);
}
public void incrementStackPointer() {
this.stackPointer++;
}
public void currentProtocolProcess() {
log(this, "protocol chosen for processing is: " + getCurrentlyActiveProtocol().getProtocolName());
getCurrentlyActiveProtocol().process(processingData);
}
/**
* Check whether the currently active protocol has requested its removal
* from the stack. This request should be found in
* {@link Layer#processingData} as ProtocolUpdate
*
* @return true iff a ProtocolUpdate is found in processingData that
* requests removal of the current protocol.
*/
public boolean isProtocolFinished() {
LinkedList<UpdatePropagation> protocolUpdates = processingData
.getUpdatePropagations(ProtocolUpdate.class);
try {
UpdatePropagation lastProtocolUpdate = protocolUpdates.getLast();
if (lastProtocolUpdate != null
&& lastProtocolUpdate instanceof ProtocolUpdate) {
return ((ProtocolUpdate) lastProtocolUpdate).isFinished();
} else {
return false;
}
} catch (NoSuchElementException e) {
return false;
}
}
public void removeCurrentProtocolAndAboveFromStack() {
log(this, "started cleaning up stack - will commence top down", TRACE);
for (int i = this.protocolStack.size() - 1; i >= this.stackPointer; i--) {
log(this, "removing protocol "
+ this.protocolStack.get(i).getProtocolName() + " from stack",
TRACE);
this.protocolStack.remove(i);
}
log(this, "finished cleaning up stack", TRACE);
}
/**
* Method used from within state machine code.
* <p/>
* Add the currently active protocol (as defined by {@link #protocolPointer}
* ) to the {@link #protocolStack}
*/
public void addProtocolAtProtocolPointerToStack() {
Protocol protocol = protocols.get(protocolPointer);
log(this,
"protocol put to top of stack is "
+ protocol.getProtocolName());
this.protocolStack.add(protocol);
}
/**
*
*/
public boolean protocolAtPointerWantsToGetOnStack() {
Protocol protocol = protocols.get(protocolPointer);
return protocol == null ? false : protocol.isMoveToStackRequested();
}
/**
* Method used from within state machine code.
* <p/>
* Calls {@link AbstractStateMachine#reset() reset} method for the protocol
* in the list of known {@link #protocols} as specified by
* {@link #protocolPointer}.
*/
public void resetProtocolAtProtocolPointer() {
protocols.get(protocolPointer).reset();
}
/**
* Method used from within state machine code.
* <p/>
* Resets {@link #protocolPointer} to point at first protocol in the
* {@link #protocols protocol list}.
*/
public void setProtocolPointerToFirstElementOfProtocolList() {
protocolPointer = 0;
}
/**
* Method used from within state machine code.
* <p/>
* Increments {@link #protocolPointer} to point at the next protocol in the
* {@link #protocols protocol list}.
*/
public void setProtocolPointerToNextElementOfProtocolList() {
protocolPointer++;
}
/**
* Method used from within state machine code.
* <p/>
* Returns whether all protocols contained in the {@link #protocols protocol
* list} have been processed, i.e. there is nor further protocol available
* for processing.
*/
public boolean allProtocolsOfProtocolListProcessed() {
return protocolPointer >= protocols.size();
}
/**
* Method used from within state machine code.
* <p/>
* Promotes the protocol specified by the {@link #protocolPointer} within
* the {@link #protocols protocol list} to be the currently active
* protocol.
*/
public void makeProtocolAtProtocolPointerCurrentlyActiveProtocol() {
setCurrentlyActiveProtocol(protocols.get(this.protocolPointer));
}
/**
* Method used from within state machine code.
* <p/>
* Set the status word for unsupported/unprocessed commands. Method is
* called if no protocol is able or willing to process a command.
*/
public void setStatusWordForUnsupportedCommand() {
if (processingData.getResponseApdu() != null && PlatformUtil.is4xxxStatusWord(processingData.getResponseApdu().getStatusWord())){
log(this, "APDU contents could not be processed by any protocol");
ResponseApdu rApdu = new ResponseApdu(PlatformUtil.convert4xxxTo6xxxStatusWord(processingData.getResponseApdu().getStatusWord()));
this.processingData.updateResponseAPDU(this,
"No protocol was able to process the APDU contents", rApdu);
} else {
log(this, "APDU not supported");
ResponseApdu rApdu = new ResponseApdu(Iso7816.SW_6D00_INS_NOT_SUPPORTED);
this.processingData.updateResponseAPDU(this,
"No protocol was able to process the APDU", rApdu);
/*
* TODO Test cases for which the current implementation yields a sw which is
* suitable/acceptable for passing the test case but does not describe actual circumstances
* appropriately. This list does not claim to be exhaustive and may be extended.
*
* ISO7816_P_04: invalid class byte, accepts: checking error, appropriate: 6E00
* ISO7816_P_05: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_07: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_10: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_11: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_12: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_13: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_15: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_16: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_17: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_18: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_19: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_20: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_21: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_22: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_31: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_32: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_33: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_34: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_37: invalid class byte, accepts: checking error, appropriate: 6E00
* ISO7816_P_38: invalid class byte, accepts: checking error, appropriate: 6E00
* ISO7816_P_39: invalid class byte, accepts: checking error, appropriate: 6E00
* ISO7816_P_40: invalid class byte, accepts: checking error, appropriate: 6E00
* ISO7816_P_62: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_64: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_69: invalid tag, accepts: checking error, appropriate: 6A80
* ISO7816_P_77: invalid tag, accepts: checking error, appropriate: 6A80
*
* Sketched _possible_ solution:
* Have protocols report _all_ APDU specifications and perform fuzzy matching to find
* most appropriate sw.
*/
}
}
// -----------------------
// Control of StateMachine
// -----------------------
private transient boolean initialized = false;
protected transient boolean continueProcessing;
@Override
public void init() {
protocolStack = new ArrayList<>();
stackPointer = 0;
reset();
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void initializeForUse() {
securityStatus = new SecStatus();
try {
getObjectTree().setSecStatus(securityStatus);
} catch (AccessDeniedException e) {
throw new ProcessingException(SW_6FFF_IMPLEMENTATION_ERROR, "something went wrong reinitializing the command processor");
}
for(Protocol protocol:protocols) {
protocol.setCardStateAccessor(this);
}
PersonalizationHelper.setLifeCycleStates(masterFile);
init();
}
@Override
public void reset() {
reInitialize();
processEvent((byte) 0xFF); // handle the first transition
}
/**
* @see ProtocolStateMachine#returnResult()
*/
public void returnResult() {
this.continueProcessing = false;
}
/**
* @see ProtocolStateMachine#apduHasBeenProcessed()
*/
public boolean apduHasBeenProcessed() {
return this.processingData.isProcessingFinished();
}
/**
* Returns the root element of the object tree.
*/
public MasterFile getObjectTree(){
return masterFile;
}
/**
* Returns the list of activated protocols.
* <p/>
* The protocols contained in this List are required to be already
* initialized and ready to be added to a {@link CardStateAccessor} and
* used afterwards
*
* @return
*/
public List<Protocol> getProtocolList(){
return protocols;
}
}