package de.persosim.simulator.cardobjects;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import de.persosim.simulator.exception.AccessDeniedException;
import de.persosim.simulator.seccondition.SecCondition;
import de.persosim.simulator.secstatus.SecStatus;
import de.persosim.simulator.tlv.ConstructedTlvDataObject;
import de.persosim.simulator.tlv.PrimitiveTlvDataObject;
import de.persosim.simulator.tlv.TlvTag;
import de.persosim.simulator.utils.Utils;
/**
* This class represents an ISO7816-4 compliant elementary file in the object
* hierarchy on the card
*
* @author mboonk
*
*/
public class ElementaryFile extends AbstractFile {
private byte[] content;
private ShortFileIdentifier shortFileIdentifier;
private SecCondition readingConditions;
private SecCondition writingConditions;
private SecCondition erasingConditions;
private SecCondition deletionConditions;
/**
* Creates a new {@link ElementaryFile} using both a full file identifier
* and a short file identifier. Sets all access restrictions to denied.
*
* @param fileIdentifier
* used for identification of the file in the object tree
* @param shortFileIdentifier
* used for identification of the file in the object tree
* @param content
* the initial contents of the file
*/
public ElementaryFile(FileIdentifier fileIdentifier, ShortFileIdentifier shortFileIdentifier, byte[] content) {
this(fileIdentifier, content);
this.shortFileIdentifier = shortFileIdentifier;
}
/**
* Creates a new {@link ElementaryFile} using only a full file identifier.
* Sets all access restrictions to denied.
*
* @param fileIdentifier
* used for identification of the file in the object tree
* @param content
* the initial contents of the file
*/
public ElementaryFile(FileIdentifier fileIdentifier, byte[] content) {
super(fileIdentifier);
this.content = content;
readingConditions = SecCondition.DENIED;
writingConditions = SecCondition.DENIED;
erasingConditions = SecCondition.DENIED;
deletionConditions = SecCondition.DENIED;
}
/**
* Creates a new {@link ElementaryFile} using both a full file identifier
* and a short file identifier.
*
* @param fileIdentifier
* used for identification of the file in the object tree
* @param shortFileIdentifier
* used for identification of the file in the object tree
* @param content
* the initial contents of the file
* @param readingConditions
* access restrictions for reading the file contents
* @param writingConditions
* access restrictions for updating or writing the file contents
* @param erasingConditions
* access restrictions for erasing (setting bytes to zero) of the
* file contents
* @param deletionConditions
* access restrictions for deletion (removal from the object
* tree) of the file
*/
public ElementaryFile(FileIdentifier fileIdentifier, ShortFileIdentifier shortFileIdentifier, byte[] content,
SecCondition readingConditions, SecCondition writingConditions, SecCondition erasingConditions,
SecCondition deletionConditions) {
this(fileIdentifier, content, readingConditions, writingConditions, erasingConditions, deletionConditions);
this.shortFileIdentifier = shortFileIdentifier;
}
/**
* Creates a new {@link ElementaryFile} using both a full file identifier
* and a short file identifier.
*
* @param fileIdentifier
* used for identification of the file in the object tree
* @param shortFileIdentifier
* used for identification of the file in the object tree
* @param content
* the initial contents of the file
* @param readingConditions
* access restrictions for reading the file contents
* @param writingConditions
* access restrictions for updating or writing the file contents
* @param erasingConditions
* access restrictions for erasing (setting bytes to zero) of the
* file contents
*/
public ElementaryFile(FileIdentifier fileIdentifier, ShortFileIdentifier shortFileIdentifier, byte[] content,
SecCondition readingConditions, SecCondition writingConditions, SecCondition erasingConditions) {
this(fileIdentifier, shortFileIdentifier, content, readingConditions, writingConditions, erasingConditions, SecCondition.DENIED);
}
/**
*
* Creates a new {@link ElementaryFile} using only a full file identifier.
*
* @param fileIdentifier
* @param content
* the initial contents of the file
* @param readingConditions
* access restrictions for reading the file contents
* @param writingConditions
* access restrictions for updating or writing the file contents
* @param erasingConditions
* access restrictions for erasing (setting bytes to zero) of the
* file contents
* @param deletionConditions
* access restrictions for deletion (removal from the object
* tree) of the file
*/
public ElementaryFile(FileIdentifier fileIdentifier, byte[] content, SecCondition readingConditions,
SecCondition writingConditions, SecCondition erasingConditions, SecCondition deletionConditions) {
super(fileIdentifier);
this.content = content;
this.readingConditions = readingConditions;
this.writingConditions = writingConditions;
this.erasingConditions = erasingConditions;
this.deletionConditions = deletionConditions;
}
@Override
public Collection<CardObject> getChildren() {
return Collections.emptySet();
}
/**
* Reads the files internal data.
* @return stored data as byte array
*/
public byte[] getContent() throws AccessDeniedException {
if (securityStatus == null || (readingConditions != null && securityStatus.checkAccessConditions(getLifeCycleState(), readingConditions))) {
return Arrays.copyOf(content, content.length);
}
throw new AccessDeniedException("Reading forbidden");
}
/**
* Replaces the files internal data.
*
* @param data
* to be used as a replacement
*/
public void update(int offset, byte[] data) throws AccessDeniedException {
if (securityStatus == null || (writingConditions != null && securityStatus.checkAccessConditions(getLifeCycleState(), writingConditions))){
for(int i = 0; i < data.length; i++) {
content[i + offset] = data[i];
}
return;
}
throw new AccessDeniedException("Updating forbidden");
}
public void setReadingConditions(SecCondition readingConditions) throws AccessDeniedException {
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
this.readingConditions = readingConditions;
return;
}
throw new AccessDeniedException("Setting reading conditions forbidden");
}
public void setWritingConditions(SecCondition writingConditions) throws AccessDeniedException {
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
this.writingConditions = writingConditions;
return;
}
throw new AccessDeniedException("Setting writing conditions forbidden");
}
public void setErasingConditions(SecCondition erasingConditions) throws AccessDeniedException {
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
this.erasingConditions = erasingConditions;
return;
}
throw new AccessDeniedException("Setting erasing conditions forbidden");
}
public void setDeletionConditions(SecCondition deletionConditions) throws AccessDeniedException {
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
this.deletionConditions = deletionConditions;
return;
}
throw new AccessDeniedException("Setting deletion conditions forbidden");
}
public void setShortFileIdentifier(ShortFileIdentifier shortFileIdentifier) throws AccessDeniedException {
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
this.shortFileIdentifier = shortFileIdentifier;
return;
}
throw new AccessDeniedException("Setting SFI forbidden");
}
public void setContent(byte[] content) throws AccessDeniedException{
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
this.content = content;
return;
}
throw new AccessDeniedException("Setting content forbidden");
}
/**
* Completely replaces the files internal data.
*
* @param data
* to be used as a replacement
*/
public void replace(byte[] data) throws AccessDeniedException {
if (SecStatus.checkAccessConditions(getLifeCycleState())) {
content = Arrays.copyOf(data, data.length);
return;
}
throw new AccessDeniedException("Updating forbidden");
}
@Override
public void addChild(CardObject newChild) {
}
@Override
public ConstructedTlvDataObject getFileControlParameterDataObject() {
ConstructedTlvDataObject result = super.getFileControlParameterDataObject();
result.addTlvDataObject(new PrimitiveTlvDataObject(new TlvTag((byte) 0x80),
Utils.removeLeadingZeroBytes(Utils.toUnsignedByteArray(content.length))));
if(shortFileIdentifier != null) {
result.addTlvDataObject(new PrimitiveTlvDataObject(new TlvTag((byte) 0x88),
Utils.toUnsignedByteArray((byte) shortFileIdentifier.getShortFileIdentifier())));
} else {
result.addTlvDataObject(new PrimitiveTlvDataObject(new TlvTag((byte) 0x88)));
}
return result;
}
@Override
public Collection<CardObjectIdentifier> getAllIdentifiers() {
Collection<CardObjectIdentifier> result = super.getAllIdentifiers();
if (shortFileIdentifier != null){
result.add(shortFileIdentifier);
}
return result;
}
/**
* Removes this file from its parent.
*
* @throws AccessDeniedException
*/
public void delete() throws AccessDeniedException {
if (securityStatus == null || (deletionConditions != null && securityStatus.checkAccessConditions(getLifeCycleState(), deletionConditions))) {
getParent().removeChild(this);
return;
}
throw new AccessDeniedException("The access conditions do not allow deletion of this file.");
}
/**
* This method erases the indicated content of this file. The sequence of
* bytes between the offsets is erased.
*
* @param startingOffset
* the first byte to erase
* @param endingOffset
* the first byte NOT to erase
* @throws AccessDeniedException
* @throws {@link
* IllegalArgumentException}, if the given offsets can not be
* used for erasing contents
*/
public void erase(int startingOffset, int endingOffset) throws AccessDeniedException {
if (securityStatus == null || (erasingConditions != null && securityStatus.checkAccessConditions(getLifeCycleState(), erasingConditions))) {
if (startingOffset < 0 | endingOffset > content.length | endingOffset < startingOffset) {
throw new IllegalArgumentException(
"The given offset combination (" + startingOffset + "," + endingOffset + ") is not feasible");
}
for (int i = startingOffset; i < endingOffset; i++) {
content[i] = 0;
}
return;
}
throw new AccessDeniedException("The access conditions do not allow erasing of this files contents.");
}
/**
* This method erases the content of this file.
*
* @throws AccessDeniedException
*/
public void erase() throws AccessDeniedException {
erase(0, content.length);
}
/**
* This method erases the indicated content of this file. The sequence of
* bytes between the startingOffset and the end of the file is erased.
*
* @param startingOffset
* the first byte to erase
* @throws AccessDeniedException
* @throws {@link
* IllegalArgumentException}, if the given offset does not fit
* the file
*/
public void erase(int startingOffset) throws AccessDeniedException {
erase(startingOffset, content.length);
}
@Override
public String toString() {
return "elementary file with file identifier " + fileIdentifier + " and short file identifier " + shortFileIdentifier;
}
}