package sasc.iso7816; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; import sasc.emv.EMVTags; import sasc.util.Util; public class TLVUtil { private static Tag searchTagById(byte[] tagIdBytes) { return EMVTags.getNotNull(tagIdBytes); } private static Tag searchTagById(ByteArrayInputStream stream) { return searchTagById(readTagIdBytes(stream)); } public static String getFormattedTagAndLength(byte[] data, int indentLength) { StringBuilder buf = new StringBuilder(); String indent = Util.getSpaces(indentLength); ByteArrayInputStream stream = new ByteArrayInputStream(data); boolean firstLine = true; while (stream.available() > 0) { if (firstLine) { firstLine = false; } else { buf.append("\n"); } buf.append(indent); Tag tag = searchTagById(stream); int length = readTagLength(stream); buf.append(Util.prettyPrintHex(tag.getTagBytes())); buf.append(" "); buf.append(Util.byteArrayToHexString(Util.intToByteArray(length))); buf.append(" -- "); buf.append(tag.getName()); } return buf.toString(); } public static byte[] readTagIdBytes(ByteArrayInputStream stream) { ByteArrayOutputStream tagBAOS = new ByteArrayOutputStream(); byte tagFirstOctet = (byte)stream.read(); tagBAOS.write(tagFirstOctet); byte MASK = 31; if ((tagFirstOctet & MASK) == MASK) { byte tlvIdNextOctet; do { int nextOctet = stream.read(); if (nextOctet < 0) { break; } tlvIdNextOctet = (byte)nextOctet; tagBAOS.write(tlvIdNextOctet); } while (Util.isBitSet(tlvIdNextOctet, 8) && (!Util.isBitSet(tlvIdNextOctet, 8) || (tlvIdNextOctet & 0x7F) != 0)); } return tagBAOS.toByteArray(); } public static int readTagLength(ByteArrayInputStream stream) { int length, tmpLength = stream.read(); if (tmpLength < 0) { throw new TLVException("Negative length: " + tmpLength); } if (tmpLength <= 127) { length = tmpLength; } else if (tmpLength == 128) { length = tmpLength; } else { int numberOfLengthOctets = tmpLength & 0x7F; tmpLength = 0; for (int i = 0; i < numberOfLengthOctets; i++) { int nextLengthOctet = stream.read(); if (nextLengthOctet < 0) { throw new TLVException("EOS when reading length bytes"); } tmpLength <<= 8; tmpLength |= nextLengthOctet; } length = tmpLength; } return length; } public static BERTLV getNextTLV(ByteArrayInputStream stream) { byte[] valueBytes; if (stream.available() < 2) { throw new TLVException("Error parsing data. Available bytes < 2 . Length=" + stream.available()); } stream.mark(0); int peekInt = stream.read(); byte peekByte = (byte)peekInt; while (peekInt != -1 && (peekByte == -1 || peekByte == 0)) { stream.mark(0); peekInt = stream.read(); peekByte = (byte)peekInt; } stream.reset(); if (stream.available() < 2) { throw new TLVException("Error parsing data. Available bytes < 2 . Length=" + stream.available()); } byte[] tagIdBytes = readTagIdBytes(stream); stream.mark(0); int posBefore = stream.available(); int length = readTagLength(stream); int posAfter = stream.available(); stream.reset(); byte[] lengthBytes = new byte[posBefore - posAfter]; if (lengthBytes.length < 1 || lengthBytes.length > 4) { throw new TLVException("Number of length bytes must be from 1 to 4. Found " + lengthBytes.length); } stream.read(lengthBytes, 0, lengthBytes.length); int rawLength = Util.byteArrayToInt(lengthBytes); Tag tag = searchTagById(tagIdBytes); if (rawLength == 128) { stream.mark(0); int prevOctet = 1; int len = 0; while (true) { len++; int curOctet = stream.read(); if (curOctet < 0) { throw new TLVException("Error parsing data. TLV length byte indicated indefinite length, but EOS was reached before 0x0000 was found" + stream .available()); } if (prevOctet == 0 && curOctet == 0) { break; } prevOctet = curOctet; } len -= 2; valueBytes = new byte[len]; stream.reset(); stream.read(valueBytes, 0, len); length = len; } else { if (stream.available() < length) { throw new TLVException("Length byte(s) indicated " + length + " value bytes, but only " + stream.available() + " " + ((stream.available() > 1) ? "are" : "is") + " available"); } valueBytes = new byte[length]; stream.read(valueBytes, 0, length); } stream.mark(0); peekInt = stream.read(); peekByte = (byte)peekInt; while (peekInt != -1 && (peekByte == -1 || peekByte == 0)) { stream.mark(0); peekInt = stream.read(); peekByte = (byte)peekInt; } stream.reset(); return new BERTLV(tag, length, lengthBytes, valueBytes); } private static String getTagValueAsString(Tag tag, byte[] value) { StringBuilder buf = new StringBuilder(); switch (tag.getTagValueType()) { case TEXT: buf.append("="); buf.append(new String(value)); break; case NUMERIC: buf.append("NUMERIC"); break; case BINARY: buf.append("BINARY"); break; case MIXED: buf.append("="); buf.append(Util.getSafePrintChars(value)); break; case DOL: buf.append(""); break; } return buf.toString(); } public static List parseTagAndLength(byte[] data) { ByteArrayInputStream stream = new ByteArrayInputStream(data); List tagAndLengthList = new ArrayList(); while (stream.available() > 0) { if (stream.available() < 2) { throw new SmartCardException("Data length < 2 : " + stream.available()); } byte[] tagIdBytes = readTagIdBytes(stream); int tagValueLength = readTagLength(stream); Tag tag = searchTagById(tagIdBytes); tagAndLengthList.add(new TagAndLength(tag, tagValueLength)); } return tagAndLengthList; } }