package de.persosim.simulator.tlv;
import static org.globaltester.logging.BasicLogger.logException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import de.persosim.simulator.platform.Iso7816;
/**
* This class represents a collection of TLV data objects, themselves being
* represented by the class {@link TlvDataObject} and its sub-classes.
*
* It may be used e.g. as the value field of a TLV data object with constructed
* encoding or as a representation of APDU command data fields.
*
* Objects of this class explicitly allow for a root element, representing the
* data field of an APDU, that itself is no {@link TlvDataObject}, i.e. has no
* dedicated tag or explicit length field. Objects of this class may also be
* used as a representation of APDU command data fields.
*
* @author slutters
*
*/
public class TlvDataObjectContainer extends TlvValue implements Iso7816, TlvDataStructure {
protected Vector<TlvDataObject> tlvObjects;
/*--------------------------------------------------------------------------------*/
/**
* Standard constructor for cases in which a tag is marked as constructed but the
* corresponding length field indicates length 0. As constructed tags are supposed
* to contain a TLV structure this is to prevent NullPointerExceptions when accessing
* this TLV structure although the structure is empty.
*/
public TlvDataObjectContainer() {
this.tlvObjects = new Vector<TlvDataObject>();
}
/**
* Constructor for all TLV structures with indicated length > 0.
* @param dataField the data field that contains the TLV structure
* @param minOffset the first offset to be used (inclusive)
* @param maxOffset the last offset to be used (exclusive)
*/
public TlvDataObjectContainer(byte[] dataField, int minOffset, int maxOffset) {
if(dataField == null) {throw new NullPointerException();}
if(minOffset < 0) {throw new IllegalArgumentException("min offset must not be less than 0");}
if(maxOffset < minOffset) {throw new IllegalArgumentException("max offset must not be smaller than min offset");}
if(maxOffset > dataField.length) {throw new IllegalArgumentException("selected array area must not lie outside of data array");}
this.tlvObjects = new Vector<TlvDataObject>();
if(minOffset == maxOffset) {
/* The TLV data object container is empty */
return;
}
int currentOffset = minOffset;
TlvDataObject tlvObject;
while(currentOffset < maxOffset) {
/*
* It is assumed that currentOffset always holds the first offset of the next TLV object.
* As the number and standard conformity of the TLV data objects contained within the data
* field is a priori unknown, maxOffset only indicates a maximum bound no TLV data object
* is allowed to exceed. The loop will continue to extract TLV data objects and move on
* currentOffset accordingly so it will continually close in on maxOffset. Finally maxOffset
* will either be occupied by the last byte of a TLV data object and everything will be fine.
* Otherwise there will always be an incomplete TLV data object left throwing an exception.
*/
tlvObject = TlvDataObjectFactory.createTLVDataObject(dataField, currentOffset, maxOffset);
currentOffset += tlvObject.getLength();
tlvObjects.add(tlvObject);
}
}
/**
* Constructor for all TLV structures with indicated length > 0.
* @param dataField the data field that contains the TLV structure
*/
public TlvDataObjectContainer(byte[] dataField) {
this(dataField, 0, dataField.length);
}
/**
* Constructs an object only containing the provided object
* @param tlvDataObject the object to contain
*/
public TlvDataObjectContainer(TlvDataObject... tlvDataObject) {
this();
this.addTlvDataObject(tlvDataObject);
}
/**
* Constructs an object from the provided one.
* Usually the provided object is not fully processed.
* @param tlvValue the object to process
*/
public TlvDataObjectContainer(TlvValue tlvValue) {
this(tlvValue.toByteArray(), 0, tlvValue.getLength());
}
public TlvDataObjectContainer(Vector<TlvDataObject> tlvObjects) {
this.tlvObjects = tlvObjects;
}
/*--------------------------------------------------------------------------------*/
@Override
public TlvDataObject getTlvDataObject(TlvPath path, int index) {
if((path == null) || (path.size() == 0)) {throw new NullPointerException();}
if((index < 0) || (index >= path.size())) {throw new IllegalArgumentException("index must not be outside of path");}
//find indicated child
TlvDataObject indicatedChild = getTlvDataObject(path.get(index));
if (indicatedChild == null) {
return null;
}
if(index == (path.size() - 1)) {
return indicatedChild;
} else if(indicatedChild instanceof ConstructedTlvDataObject) {
return ((ConstructedTlvDataObject) indicatedChild).getTlvDataObject(path, index + 1);
} else{
return null;
}
}
@Override
public TlvDataObject getTlvDataObject(TlvPath path) {
return this.getTlvDataObject(path, 0);
}
@Override
public TlvDataObject getTlvDataObject(TlvTagIdentifier tagIdentifier) {
if(tagIdentifier == null) {throw new NullPointerException("tag must not be null");}
int remainingOccurences = tagIdentifier.getNoOfPreviousOccurrences();
for(TlvDataObject tlvDataObject : this.tlvObjects) {
if(tlvDataObject.getTlvTag().equals(tagIdentifier.getTag())) {
if (remainingOccurences == 0) {
return tlvDataObject;
} else {
remainingOccurences--;
}
}
}
return null;
}
@Override
public TlvDataObject getTlvDataObject(TlvTag tlvTag) {
return getTlvDataObject(new TlvTagIdentifier(tlvTag));
}
@Override
public boolean containsTlvDataObject(TlvTag tagField) {
return this.getTlvDataObject(tagField) != null;
}
@Override
public int getNoOfElements(boolean recursive) {
int noOfElements;
if(this.tlvObjects == null) {
throw new NullPointerException();
}
noOfElements = this.tlvObjects.size();
if(recursive) {
for(TlvDataObject tlvDataObject : this.tlvObjects) {
if(tlvDataObject instanceof ConstructedTlvDataObject) {
noOfElements += ((ConstructedTlvDataObject) tlvDataObject).getNoOfElements(recursive);
}
}
}
return noOfElements;
}
@Override
public int getNoOfElements() {
return this.getNoOfElements(false);
}
/**
* @return the tlvObjects
*/
public List<TlvDataObject> getTlvObjects() {
return tlvObjects;
}
@Override
public byte[] toByteArray() {
ByteArrayOutputStream outputStream;
byte[] tlvObjectAsByteArray;
outputStream = new ByteArrayOutputStream();
for(TlvDataObject tlvObject : this.tlvObjects) {
try {
tlvObjectAsByteArray = tlvObject.toByteArray();
outputStream.write(tlvObjectAsByteArray);
} catch (IOException e) {
logException(getClass(), e);
}
}
return outputStream.toByteArray();
}
/*--------------------------------------------------------------------------------*/
@Override
public Iterator<TlvDataObject> iterator() {
return this.tlvObjects.iterator();
}
/*--------------------------------------------------------------------------------*/
@Override
public void sort(Comparator<TlvDataObject> comparator) {
Collections.sort(this.tlvObjects, comparator);
}
@Override
public void addTlvDataObject(TlvPath path, TlvDataObject tlvDataObject) {
TlvDataObject supposedParent = getTlvDataObject(path);
if((supposedParent != null) && (supposedParent instanceof ConstructedTlvDataObject)) {
ConstructedTlvDataObject actualParent = (ConstructedTlvDataObject) supposedParent;
actualParent.addTlvDataObject(tlvDataObject);
} //XXX add consistent behavior for these failure cases, e.g. notify the caller that no action was taken at all
}
@Override
public void addTlvDataObject(TlvDataObject... tlvDataObject) {
for (int i = 0; i < tlvDataObject.length; i++) {
this.tlvObjects.add(tlvDataObject[i]);
}
}
@Override
public void removeTlvDataObject(TlvPath path) {
if(path == null) {throw new NullPointerException("path must not be null");};
if(path.size() < 1) {throw new IllegalArgumentException("path must not be empty");};
if(path.size() == 1) {
removeTlvDataObject(path.get(0));
} else{
TlvPath subPath = path.clone();
subPath.remove(0);
TlvDataObject supposedParent = getTlvDataObject(path.get(0));
if((supposedParent != null) && (supposedParent instanceof ConstructedTlvDataObject)) {
((ConstructedTlvDataObject) supposedParent).removeTlvDataObject(subPath);
} //XXX add consistent behavior for these failure cases, e.g. notify the caller that no action was taken at all
}
}
@Override
public void removeTlvDataObject(TlvTagIdentifier tagIdentifier) {
TlvDataObject objToRemove = getTlvDataObject(tagIdentifier);
tlvObjects.remove(objToRemove);
}
@Override
public void removeTlvDataObject(TlvTag tlvTag) {
removeTlvDataObject(new TlvTagIdentifier(tlvTag));
}
@Override
public int getLength() {
int length;
length = 0;
for(TlvDataObject tlvDataObject : this.tlvObjects) {
length += tlvDataObject.getLength();
}
return length;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("(");
for(TlvDataObject tlvDataObject : this.tlvObjects) {
sb.append("[");
sb.append(tlvDataObject.toString());
sb.append("]");
}
sb.append(")");
return sb.toString();
}
@Override
public boolean isValidBerEncoding() {
for(TlvDataObject tlvDataObject : tlvObjects) {
if(!tlvDataObject.isValidBerEncoding()) {return false;}
}
return true;
}
@Override
public boolean isValidDerEncoding() {
/* first check elements for themselves */
for(TlvDataObject tlvDataObject : tlvObjects) {
if(!tlvDataObject.isValidDerEncoding()) {return false;}
}
/* then check combination of elements */
if(tlvObjects.size() > 1) {
Comparator<TlvDataObject> comparator = new TlvDataObjectComparatorDer();
for(int i = 1; i < tlvObjects.size(); i++) {
if(comparator.compare(tlvObjects.get(i-1), tlvObjects.get(i)) > 0) {return false;}
}
}
return true;
}
@Override
public TlvDataObjectContainer copy(){
return new TlvDataObjectContainer(this.toByteArray());
}
}