package de.persosim.simulator.apdu;
import static org.globaltester.logging.BasicLogger.logException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.platform.Iso7816Lib;
import de.persosim.simulator.tlv.TlvDataObjectContainer;
import de.persosim.simulator.tlv.TlvValue;
import de.persosim.simulator.tlv.TlvValuePlain;
import de.persosim.simulator.utils.HexString;
import de.persosim.simulator.utils.Serializer;
import de.persosim.simulator.utils.Utils;
/**
* Container class carrying the information of the command APDU. This class
* provides simplified access to all the relevant information that can be
* extracted from the command APDU.
*
* It also stores the processing history of this CommandApdu. For example if the
* APDU was SM secured and unwrapped by the SecureMessaging layer the original
* CommandApdu is preserved in the predecessor field.
*
* @author amay
*
*/
public class CommandApduImpl implements CommandApdu {
protected final byte [] header;
private final boolean isExtendedLength;
private final byte isoCase;
private final int ne;
private final short nc;
private final TlvValue commandData;
private CommandApdu predecessor = null;
/**
* Parses the apdu from the given byte array.
* @param apdu
*/
CommandApduImpl(byte[] apdu) {
this(apdu, null);
}
/**
* Parses the apdu from the given byte array and sets the provided instance as predecessor.
* @param apdu
* @param previousCommandApdu the predecessor of this instance, may be null
*/
protected CommandApduImpl(byte[] apdu, CommandApdu previousCommandApdu) {
//store history
predecessor = previousCommandApdu;
//copy header
header = new byte [4];
System.arraycopy(apdu, 0, header, 0, header.length);
//analyze/store Iso case and length
isExtendedLength = Iso7816Lib.isExtendedLengthLCLE(apdu);
isoCase = Iso7816Lib.getISOcase(apdu);
//handle commandData (if present)
if ((isoCase == Iso7816.ISO_CASE_3) || (isoCase == Iso7816.ISO_CASE_4)) {
nc = Iso7816Lib.getNc(apdu);
commandData = Iso7816Lib.getCommandData(apdu);
} else {
nc = 0;
commandData = new TlvValuePlain(new byte [0]);
}
//store ne (if present)
if ((isoCase == Iso7816.ISO_CASE_2) || (isoCase == Iso7816.ISO_CASE_4)) {
ne = Iso7816Lib.getNe(apdu);
} else {
ne = 0;
}
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getIsoFormat()
*/
@Override
public byte getIsoFormat() {
return Iso7816Lib.getISOFormat(Iso7816Lib.getClassByte(header));
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getCla()
*/
@Override
public byte getCla() {
return Iso7816Lib.getClassByte(header);
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getIns()
*/
@Override
public byte getIns() {
return Iso7816Lib.getInstructionByte(header);
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getP1()
*/
@Override
public byte getP1() {
return Iso7816Lib.getP1(header);
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getP2()
*/
@Override
public byte getP2() {
return Iso7816Lib.getP2(header);
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getIsoCase()
*/
@Override
public byte getIsoCase() {
return isoCase;
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#isExtendedLength()
*/
@Override
public boolean isExtendedLength() {
return isExtendedLength;
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getNc()
*/
@Override
public int getNc() {
return nc;
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getCommandData()
*/
@Override
public TlvValue getCommandData() {
TlvValue ret = this.commandData;
if(ret != null){
return ret.copy();
}
return null;
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getCommandDataObjectContainer()
*/
@Override
public TlvDataObjectContainer getCommandDataObjectContainer() {
TlvDataObjectContainer commandDataRet;
if (!(commandData instanceof TlvDataObjectContainer)) {
commandDataRet = new TlvDataObjectContainer(commandData);
} else {
commandDataRet = (TlvDataObjectContainer) commandData;
}
return Serializer.deepCopy(commandDataRet);
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getNe()
*/
@Override
public int getNe() {
return ne;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(HexString.encode(getHeader()));
if (isoCase > 2) {
sb.append('|');
sb.append(HexString.encode(getLc()));
sb.append('|');
sb.append(getCommandData().toString());
}
if ((isoCase == 2) || (isoCase == 4)) {
sb.append('|');
sb.append(HexString.encode(getLe()));
}
return sb.toString();
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getP1P2()
*/
@Override
public short getP1P2() {
return Utils.concatenate(getP1(), getP2());
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getHeader()
*/
@Override
public byte[] getHeader() {
byte[] header = new byte[this.header.length];
System.arraycopy(this.header, 0, header, 0, this.header.length);
return header;
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#toByteArray()
*/
@Override
public byte[] toByteArray() {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
os.write(getHeader());
if (isoCase > 2) {
os.write(getLc());
os.write(getCommandData().toByteArray());
}
os.write(getLe());
} catch (IOException e) {
logException(getClass(), e);
}
return os.toByteArray();
}
/**
* Constructs valid byte encoding of Lc.
* <p/>
* If no data field is present an empty array is returned. If data field is
* present and extended length is used the returned byte array contains the
* required leading zero byte.
*
* @return byte encoding of Lc field
*/
private byte[] getLc() {
if (isoCase > 2) {
if (isExtendedLength) {
byte[] retVal = new byte[3];
retVal[0] = 0;
retVal[1] = (byte) ((nc & (short) 0xFF00) >> 8);
retVal[2] = (byte) (nc & (short) 0x00FF);
return retVal;
} else {
return new byte[]{(byte) nc};
}
} else {
return new byte[]{};
}
}
/**
* Constructs valid byte encoding of Le.
* <p/>
* If Le is absent an empty array is returned.
* If extended length is used Le is encoded in two bytes.
* If data field is absent and extended length is used the returned byte array contains the
* required leading zero byte.
*
* @return byte encoding of Le field
*/
private byte[] getLe() {
if (ne > 0) {
if(isExtendedLength) {
if (isoCase > 2) {
return Utils.toUnsignedByteArray((short)ne);
} else {
byte[] retVal = new byte[3];
retVal[0] = 0;
retVal[1] = (byte) ((ne & (short) 0xFF00) >> 8);
retVal[2] = (byte) (ne & (short) 0x00FF);
return retVal;
}
} else {
return new byte[] {(byte) ne};
}
} else {
return new byte[]{};
}
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#getPredecessor()
*/
@Override
public CommandApdu getPredecessor() {
return predecessor;
}
/* (non-Javadoc)
* @see de.persosim.simulator.apdu.CommandApdu#isNeZeroEncoded()
*/
@Override
public boolean isNeZeroEncoded() {
if (isExtendedLength) {
return ne == 65536;
} else {
return ne == 256;
}
}
}