package com.github.jthuraisamy.mastertap.helpers;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.github.jthuraisamy.mastertap.Helper;
import com.github.jthuraisamy.mastertap.TLVParser;
import com.github.jthuraisamy.mastertap.models.CardRecord;
import com.google.common.primitives.Bytes;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class VisaHelper {
private static final String TAG = "MasterTapLog-" + VisaHelper.class.getSimpleName();
/**
* Get PDOL length for the Get Processing Options (GPO) command.
*
* @param aidFci byte[]
* @return int
*/
private static int getPdolLength(byte[] aidFci) {
int pdolLength = 0;
byte[] pdol = TLVParser.readTlv(aidFci, new byte[]{(byte) 0x9F, 0x38});
for (int i = 0; i < pdol.length; i++) {
if (i % 3 == 2) {
pdolLength += pdol[i];
}
}
return pdolLength;
}
/**
* Return the Get Processing Options (GPO) command constructed with the PDOL length derived
* from the given application selection data elements in byte[] aidFci.
*
* @param aidFci byte[]
* @return byte[]
*/
public static byte[] getGpoCommand(byte[] aidFci) {
int pdolLength = VisaHelper.getPdolLength(aidFci);
try {
ByteArrayOutputStream gpoCommand = new ByteArrayOutputStream();
gpoCommand.write(new byte[]{(byte) 0x80, (byte) 0xA8, 0x00, 0x00});
gpoCommand.write(new byte[]{(byte) (pdolLength + 2), (byte) 0x83, (byte) pdolLength, (byte) 0x80});
gpoCommand.write(new byte[pdolLength]);
return gpoCommand.toByteArray();
} catch (IOException e) {
return null;
}
}
/**
* Retrieve records specified in AFL and remove the contactless indicator from track 2 data.
*
* @param tagCommunicator IsoDep
* @param afl byte[]
* @return List<CardRecord>
*/
public static List<CardRecord> getRecordsFromAfl(IsoDep tagCommunicator, byte[] afl) {
// The AFL must be a multiple of 4 bytes.
if ((afl.length & 0x03) > 0) return null;
List<CardRecord> cardRecords = new ArrayList<CardRecord>();
// Get the number of files.
int numFiles = afl.length / 4;
// Read records for each SFI.
for (int i = 0; i < numFiles; i++) {
int shortFileIdentifier = afl[4 * i] >> 3;
int startRecord = afl[4 * i + 1];
int endRecord = afl[4 * i + 2];
for (; startRecord <= endRecord; startRecord++) {
// Initialize a CardRecord.
CardRecord cardRecord = new CardRecord();
cardRecord.setShortFileIdentifier(shortFileIdentifier);
cardRecord.setRecordNumber(startRecord);
// Generate read record command.
byte[] readRecordCommand = getReadRecordCommand(shortFileIdentifier, startRecord);
// Execute read record command and save to CardRecord.
try {
byte[] recordResponse = tagCommunicator.transceive(readRecordCommand);
recordResponse = stripContactlessIndicator(recordResponse);
cardRecord.setRawResponse(recordResponse);
Log.i(TAG, Helper.byteToHex(readRecordCommand) + " = " + Helper.byteToHex(recordResponse));
} catch (IOException e) {
e.printStackTrace();
}
// Append CardRecord to list.
cardRecords.add(cardRecord);
}
}
return cardRecords;
}
/**
* Construct a read record command with the given SFI and record number.
*
* @param sfi int
* @param recordNumber int
* @return byte[]
*/
private static byte[] getReadRecordCommand(int sfi, int recordNumber) {
byte[] readRecordCommand = new byte[5];
readRecordCommand[1] = (byte) 0xB2;
readRecordCommand[2] = (byte) recordNumber;
readRecordCommand[3] = (byte) ((sfi << 0x03) | 0x04);
return readRecordCommand;
}
/**
* Remove the contactless indicator digit from the track 2 equivalent data in the given record.
*
* @param record byte[]
* @return byte[]
*/
private static byte[] stripContactlessIndicator(byte[] record) {
int track2HeaderIndex = Bytes.indexOf(record, (byte) 0x57);
if (track2HeaderIndex < 0) return record;
byte[] track2EquivalentData = TLVParser.readTlv(record, new byte[] {0x57});
record[track2HeaderIndex + track2EquivalentData.length + 1] = 0x0F;
return record;
}
}