package de.persosim.simulator.apdumatching;
import static org.globaltester.logging.BasicLogger.log;
import java.util.ArrayList;
import java.util.Iterator;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.tlv.TlvDataObject;
import de.persosim.simulator.tlv.TlvDataObjectContainer;
import de.persosim.simulator.tlv.TlvPath;
import de.persosim.simulator.tlv.TlvTagIdentifier;
/**
* This class provides a container for specifications of TLV elements. Its
* primary function is to serve as root element of the specified TLV structure.
*
* @author slutters
*
*/
public class TlvSpecificationContainer extends ArrayList<TlvSpecification> implements Iso7816, ApduSpecificationConstants {
private static final long serialVersionUID = 1L;
protected boolean allowUnspecifiedSubTags;
protected boolean isStrictOrder;
public TlvSpecificationContainer(boolean allowUnspecifiedSubTags, boolean isStrictOrder) {
this.allowUnspecifiedSubTags = allowUnspecifiedSubTags;
this.isStrictOrder = isStrictOrder;
}
public TlvSpecificationContainer(boolean allowUnspecifiedSubTags) {
this(allowUnspecifiedSubTags, STRICT_ORDER);
}
public TlvSpecificationContainer() {
this(DO_NOT_ALLOW_FURTHER_TAGS);
}
/**
* This method adds TLV data object specifications to an existing hierarchy
* according to the provided path and path offset. The path (offset) is
* relative to the object on which it is applied.
*
* @param path
* the path at which the provided specification is to be added
* @param pathOffset
* the offset of the current position within the path
* @param tlvSpec
* the TLV data object specifications to be added
*/
public void add(TlvPath path, int pathOffset, TlvSpecification tlvSpec) {
if(path == null) {throw new NullPointerException("path must not be null");}
for(int i = 0; i < path.size(); i++) {
if(path.get(i) == null) {
throw new NullPointerException("subtag in path must not be null");
}
}
if(pathOffset == path.size()) {
add(tlvSpec);
return;
} else{
TlvSpecification subTlvSpec;
int index = getIndexOfSubTag(path.get(pathOffset));
if(index >= 0) {
subTlvSpec = get(index);
subTlvSpec.add(path, pathOffset + 1, tlvSpec);
} else{
throw new NullPointerException("path element not found");
}
}
}
/**
* This method inserts a TLV data object specification within an existing
* hierarchy as child to the constructed TLV data object specification at
* the provided path. The path is relative to the object on which it is
* applied.
*
* @param path
* the path at which the provided specification is to be added
* @param tlvSpec
* the TLV data object specifications to be added
*/
public void add(TlvPath path, TlvSpecification tlvSpec) {
add(path, 0, tlvSpec);
}
/**
* This method returns the index of an occurrence of a tag matching
* the provided tag within the sub tags of this object. If no occurrence can
* be found the returned index will be "-1".
*
* @param tlvTagIdentifier
* the tag to be matched for
* @return the first occurrence of the provided tag
*/
public int getIndexOfSubTag(TlvTagIdentifier tlvTagIdentifier) {
if(tlvTagIdentifier == null) {throw new NullPointerException("tag identifier must not be null");}
int remainingOccurences = tlvTagIdentifier.getNoOfPreviousOccurrences();
for(int i = 0; i < size(); i++) {
if(get(i).matches(tlvTagIdentifier.getTag())) {
if (remainingOccurences == 0) {
return i;
} else {
remainingOccurences--;
}
}
}
return -1;
}
// XXX extract a common Interface for matching, with a consistent description of the matches() method, similar to Java.util.regex.matcher, in the end we are dcefining something very similar to Regex here
// This should cover *TlvSpecification*.matches, as well as Tlv*.matches and ApduSpecification.matchesFullAPDU()
/**
* This method returns whether the provided TLV data object container matches the hierarchy of specifications within this object.
* @param tlvContainer a TLV data object container
* @return the matching result
*/
public boolean matches(TlvDataObjectContainer tlvContainer) {
Iterator<TlvDataObject> tlvIterator;
int counter, diffCounter, currentWorkingIndex, highestAlreadyEncounteredIndex;;
TlvDataObject tlvDataObject;
TlvSpecification currentTlvSpecification;
counter = 0;
highestAlreadyEncounteredIndex = 0;
tlvIterator = tlvContainer.iterator();
while(tlvIterator.hasNext()) {
tlvDataObject = tlvIterator.next();
currentWorkingIndex = this.getIndexOfSubTag(new TlvTagIdentifier(tlvDataObject.getTlvTag()));
if(currentWorkingIndex < 0) {
if(!this.allowUnspecifiedSubTags) {
/* we encountered an unknown (sub-) tag but these are implicitly forbidden at the specified place */
log(ApduSpecification.class, "unexpected tag " + tlvDataObject.getTlvTag());
return false;
}
} else{
currentTlvSpecification = get(currentWorkingIndex);
if(currentTlvSpecification.getRequired() == REQ_MISMATCH) {
log(ApduSpecification.class, "tag " + tlvDataObject.getTlvTag() + " not allowed");
return false;
}
if(currentTlvSpecification.getRequired() == REQ_MATCH) {
counter++;
}
if(isStrictOrder) {
if(currentWorkingIndex < highestAlreadyEncounteredIndex) {
/* we encountered a known (sub-) tag but out of the specified order */
log(ApduSpecification.class, "tag " + tlvDataObject.getTlvTag() + " is out of order");
return false;
} else{
highestAlreadyEncounteredIndex = currentWorkingIndex;
}
}
if(!currentTlvSpecification.matches(tlvDataObject)) {
log(ApduSpecification.class, "error");
return false;
}
}
}
// XXX why is a counter sufficient here? Shouldn't the algorithm iterate over the list of specifications instead of the input? Imagine a specification that should match A,B and an input A,A, this might also lead to a counter of 2 instead of a mismatch
diffCounter = this.getNoOfTagsMatchingRequirement(REQ_MATCH) - counter;
if(diffCounter > 0) {
/* tlv object failed to satisfy all required matches */
/* "missing tags" */
if(diffCounter == 1) {
log(ApduSpecification.class, "missing " + diffCounter + " more mandatory tag");
return false;
} else{
log(ApduSpecification.class, "missing " + diffCounter + " more mandatory tags");
return false;
}
}
return true;
}
/**
* This methods sets the expectations for unspecified sub tags.
* @param allowUnspecifiedSubTags whether unspecified sub tags are to be tolerated
*/
public void setAllowUnspecifiedSubTags(boolean allowUnspecifiedSubTags) {
this.allowUnspecifiedSubTags = allowUnspecifiedSubTags;
}
/**
* This method returns the number of immediate child specifications matching the provided requirement state.
* @param req the requirement state to match against
* @return number of immediate child specifications matching the provided requirement state
*/
public int getNoOfTagsMatchingRequirement(byte req) {
int counter;
counter = 0;
for(int i = 0; i < size(); i++) {
if(get(i).getRequired() == req) {
counter++;
}
}
return counter;
}
/**
* This method sets whether sub tags are to be expected exactly in the provided order.
* @param strictOrder the order in which sub tags are evaluated
*/
public void setStrictOrder(boolean strictOrder) {
isStrictOrder = strictOrder;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (allowUnspecifiedSubTags ? 1231 : 1237);
result = prime * result + (isStrictOrder ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
TlvSpecificationContainer other = (TlvSpecificationContainer) obj;
if (allowUnspecifiedSubTags != other.allowUnspecifiedSubTags)
return false;
if (isStrictOrder != other.isStrictOrder)
return false;
return true;
}
}