package de.persosim.simulator.tlv;
import java.util.Arrays;
import de.persosim.simulator.exception.ISO7816Exception;
import de.persosim.simulator.utils.Utils;
/**
* This class implements the length field of any TLV data object. A length
* field may be created or set with any value, i.e. by explicitly bypassing any
* checks for validity or encoding. This explicitly allows for generating
* intentionally damaged length fields.
*
* @author slutters
*
*/
public class TlvLength extends TlvElement {
protected byte[] lengthField;
/*--------------------------------------------------------------------------------*/
/**
* Basic constructor for this object based on a range defined on an array of raw bytes.
*
* WARNING: If validity checks are to be skipped, the length will be set to the full range.
* This may cause serious problems if the range is also expected to contain a value field
* as it is the case during construction of any TLV data object or container from raw byte arrays.
*
* @param lengthFieldInput the byte array that in a certain range contains the TLV length
* @param minOffset the first offset of the range to contain the TLV length (inclusive)
* @param maxOffset the first offset not to be part of the range to contain the TLV length (exclusive).
* This offset may no longer be part of the array.
* @param performValidityChecks true: perform validity checks, false: do not perform validity checks
*/
public TlvLength(byte[] lengthFieldInput, int minOffset, int maxOffset, boolean performValidityChecks) {
super();
if(performValidityChecks == PERFORM_VALIDITY_CHECKS) {
this.setLengthField(lengthFieldInput, minOffset, maxOffset);
} else{
this.forceLengthField(Arrays.copyOfRange(lengthFieldInput, minOffset, maxOffset));
}
}
/**
* Basic constructor for this object based on a range from a byte array.
* @param lengthFieldInput the data field that in a certain range contains the TLV object
* @param minOffset the first offset of the range to contain the TLV object (inclusive)
* @param maxOffset the first offset not to be part of the range to contain the TLV object(exclusive).
* This offset may no longer be part of the array.
*/
public TlvLength(byte[] lengthFieldInput, int minOffset, int maxOffset) {
this(lengthFieldInput, minOffset, maxOffset, PERFORM_VALIDITY_CHECKS);
}
/**
* Basic constructor for this object based on a whole byte array.
* @param lengthFieldInput the data field that completely contains the TLV object
* @param performValidityChecks whether integrity of this object is checked and ok
*/
public TlvLength(byte[] lengthFieldInput, boolean performValidityChecks) {
super();
if(performValidityChecks == PERFORM_VALIDITY_CHECKS) {
this.setLengthField(lengthFieldInput);
} else{
this.forceLengthField(lengthFieldInput);
}
}
/**
* Basic constructor for this object based on a whole byte array.
* @param lengthFieldInput the data field that completely contains the TLV object
*/
public TlvLength(byte[] lengthFieldInput) {
this(lengthFieldInput, PERFORM_VALIDITY_CHECKS);
}
/**
* Basic constructor for this object based on value to be represented by this object.
* @param lengthValue the value to be represented by this object
* @param safetyOnOff whether integrity of this object is checked and ok
*/
public TlvLength(int lengthValue, boolean safetyOnOff) {
super();
byte[] lengthField = TlvLength.getLengthEncoding(lengthValue);
if(safetyOnOff == PERFORM_VALIDITY_CHECKS) {
this.setLengthField(lengthField);
} else{
this.forceLengthField(lengthField);
}
}
/**
* Basic constructor for this object based on value to be represented by this object.
* @param lengthValue the value to be represented by this object
*/
public TlvLength(int lengthValue) {
this(lengthValue, PERFORM_VALIDITY_CHECKS);
}
/*--------------------------------------------------------------------------------*/
/**
* Basic method to set the length field based on a range from an array of raw bytes.
* The defined range must contain at least the whole length field.
* The first byte of the range must also be the first byte of the length field.
* The last byte of the range is not expected to also be the last byte of the length
* field and may lay outside of the array.
*
* @param lengthFieldInput the byte array that contains the TLV element
* @param minOffset the first offset of the range to contain the TLV element (inclusive)
* @param maxOffset the first offset not to be part of the range to contain the TLV element (exclusive).
* This offset may no longer be part of the array.
*/
public void setLengthField(byte[] lengthFieldInput, int minOffset, int maxOffset) {
if(lengthFieldInput == 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 > lengthFieldInput.length) {throw new IllegalArgumentException("selected array area must not lie outside of data array");}
if(minOffset == maxOffset) {throw new IllegalArgumentException("selected part of data field must be greater than 0");}
//determine max offset
int endOffset;
byte firstLengthByte = lengthFieldInput[minOffset];
if((firstLengthByte & (byte) 0x80) == (byte) 0x80) {
/* if most significant bit is '1', i.e. we are not dealing with a 1-Byte length field */
int noOfBytesUsedToIndicateLength = Utils.maskUnsignedByteToInt((byte) (firstLengthByte & (byte) 0x7F)) + 1;
if((noOfBytesUsedToIndicateLength <= 1) || (noOfBytesUsedToIndicateLength > 5)) {
/* error, unspecified no of length bytes */
ISO7816Exception.throwIt(SW_6A80_WRONG_DATA);
}
if((minOffset + noOfBytesUsedToIndicateLength) > maxOffset) {
ISO7816Exception.throwIt(SW_6A85_NC_INCONSISTENT_WITH_TLV_STRUCTURE, "offset outside data array");
}
endOffset = minOffset + noOfBytesUsedToIndicateLength;
} else{
endOffset = minOffset + 1;
}
//copy relevant part of input into member lengthField
lengthField = Arrays.copyOfRange(lengthFieldInput, minOffset, endOffset);
}
/**
* Sets the length field (perform validity checks).
* @param lengthFieldInput the length field to be set
*/
public void setLengthField(byte[] lengthFieldInput) {
this.setLengthField(lengthFieldInput, 0, lengthFieldInput.length);
}
/**
* Forces the length field (skip validity checks).
* This method allows to set the length field bypassing any checks for content
* validity or encoding. The value is set "as is" without any processing.
* The only limitation is that the null value is not allowed.
* A length field is a basic element of any TLV data object. It may either be
* present or not. Creating a length field but setting a null value would be
* somewhere in between and hence is not allowed. If no length field is to be
* set, no length field needs to be created.
* @param lengthFieldInput the length field to be set
*/
public void forceLengthField(byte[] lengthFieldInput) {
if(lengthFieldInput == null) {throw new NullPointerException();}
this.lengthField = Arrays.copyOf(lengthFieldInput, lengthFieldInput.length);
}
/**
* Returns the length of the value field as indicated by the length field
* @return the length of the value field as indicated by the length field
*/
public int getIndicatedLength() {
int lengthNo;
byte[] actualLength;
actualLength = this.lengthField;
if(this.getLength() == 1) {
lengthNo = Utils.maskUnsignedByteToInt(actualLength[0]);
} else{
actualLength = Arrays.copyOfRange(this.lengthField, 1, this.lengthField.length);
lengthNo = Utils.getIntFromUnsignedByteArray(actualLength);
}
return lengthNo;
}
@Override
public boolean equals(Object anotherTlvLength) {
if(anotherTlvLength == null) {return false;}
if (!(anotherTlvLength instanceof TlvLength)) {
return false;
}
//TlvLengths are considered equal iff they encode the same length value in the same way
return Arrays.equals(lengthField, ((TlvLength) anotherTlvLength).lengthField);
}
@Override
public int hashCode() {
int hash = 1;
for (int i = 0; i < lengthField.length; i++) {
hash *= lengthField[i];
}
return hash;
}
@Override
public byte[] toByteArray() {
return Arrays.copyOf(lengthField, lengthField.length);
}
@Override
public int getLength() {
return this.lengthField.length;
}
@Override
public TlvLength clone() {
return new TlvLength(this.toByteArray(), SKIP_VALIDITY_CHECKS);
}
@Override
public boolean isValidBerEncoding() {
int lengthFieldLength = lengthField.length;
/* ensure valid length */
if((lengthFieldLength < 1) || (lengthFieldLength > 5)) {return false;}
/* ensure valid formatting of length */
if(lengthFieldLength == 1) {
if((lengthField[0] & ((short) 0x80)) == ((short) 0x80)) {return false;}
} else{
if((lengthField[0] & ((short) 0x80)) != ((short) 0x80)) {return false;}
int noOfBytesUsedToIndicateLength = Utils.maskUnsignedByteToInt((byte) (lengthField[0] & (byte) 0x7F)) + 1;
if(noOfBytesUsedToIndicateLength != lengthFieldLength) {return false;}
if((noOfBytesUsedToIndicateLength < 2) || (noOfBytesUsedToIndicateLength > 5)) {return false;}
}
return true;
}
@Override
public boolean isValidDerEncoding() {
/* Must be valid BER encoding */
if(!isValidBerEncoding()) {return false;}
/* Must be minimum length encoding */
return getMinNoOfBytesEncodingLength(getIndicatedLength()) == lengthField.length;
}
/*--------------------------------------------------------------------------------*/
/**
* Returns the minimum length encoding that encodes the provided length value.
* The returned byte array complies with DER encoding rules.
* @param indicatedLength the length value to be encoded
* @return the minimum length encoding that encodes the provided length value
*/
public static byte[] getLengthEncoding(int indicatedLength) {
if(indicatedLength < 0) {throw new NullPointerException("length must not be smaller than 0");}
byte[] temporaryLengthEncoding, finalLengthEncoding;
byte byteLength;
temporaryLengthEncoding = Utils.removeLeadingZeroBytes(Utils.toUnsignedByteArray(indicatedLength));
if(indicatedLength <= 127) {
finalLengthEncoding = temporaryLengthEncoding;
} else{
finalLengthEncoding = new byte[temporaryLengthEncoding.length + 1];
/* for a length input of 32 bit integer byte length will be in range of 0-4 */
byteLength = (byte) temporaryLengthEncoding.length;
finalLengthEncoding[0] = (byte) (((byte) 0x80) | byteLength);
System.arraycopy(temporaryLengthEncoding, 0, finalLengthEncoding, 1, temporaryLengthEncoding.length);
}
return finalLengthEncoding;
}
/**
* Returns the minimum number of bytes that is needed to encode the given length
* @return the minimum number of bytes that is needed to encode the given length
*/
public static int getMinNoOfBytesEncodingLength(int indicatedLength) {
return getLengthEncoding(indicatedLength).length;
}
}