/**
* This file is part of CardApplet-MMPP which is card applet implementation
* of M Remote-SE Mobile PayP for SimplyTapp cloud platform.
* Copyright 2014 SimplyTapp, Inc.
*
* CardApplet-MMPP is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* CardApplet-MMPP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CardApplet-MMPP. If not, see <http://www.gnu.org/licenses/>.
*/
package com.st.mmpp;
import java.io.Serializable;
import javacard.framework.Util;
/**
* Define data objects for SFI records and Transaction Log records.
*
* @author SimplyTapp, Inc.
* @version 1.0
*/
final class Records implements Serializable {
private static final long serialVersionUID = 1L;
static final byte SFI_NOT_FOUND = (byte) 0x00;
static final byte SFI_FOUND = (byte) 0x01;
static final byte RECORD_FOUND = (byte) 0x02;
// NOTE: Maximum record length supported needs to be between 180 and 247.
private static final short MAX_SFI_RECORD_LENGTH = (short) 247;
//private static final short MAX_SFI_RECORD_LENGTH = (short) 254;
private Record[] sfiRecords;
private short sfiRecordCounter;
private Record foundRecord;
/**
* Constructor for records.
*
* @param maxSFIRecords
*/
Records(short maxSFIRecords) {
this.sfiRecords = new Record[maxSFIRecords];
this.sfiRecordCounter = (short) 0;
}
// DEBUG
short debugGetSFIRecordCounter() {
return this.sfiRecordCounter;
}
/**
* Find SFI record.
*
* @param sfi
* @param recordNumber
* @param record
* @return
*/
byte findSFIRecord(byte sfi, short recordNumber) {
this.foundRecord = null;
byte result = SFI_NOT_FOUND;
// Search SFI records.
short recordOffset = (short) 0;
while (recordOffset < this.sfiRecordCounter) {
Record record = this.sfiRecords[recordOffset++];
if (record.sfi == sfi) {
result = SFI_FOUND;
if (record.recordNumber == recordNumber) {
this.foundRecord = record;
result = RECORD_FOUND;
break;
}
}
}
// result = 0x00 if SFI not found.
// result = 0x01 if SFI found, record number not found.
// result = 0x02 if SFI found, record number found.
return result;
}
/**
* Return maximum data length for found record.
*
* @return
*/
short getFoundRecordMaxLength() {
if (this.foundRecord == null) {
return (short) 0;
}
return (short) this.foundRecord.data.length;
}
/**
* Update SFI record.
*
* @param data
* @param dataOffset
* @param dataLength
*/
void updateRecord(byte[] data, short dataOffset, short dataLength) {
if (this.foundRecord == null) {
return;
}
Util.arrayCopyNonAtomic(data, dataOffset, this.foundRecord.data, (short) 0, dataLength);
this.foundRecord.dataLength = dataLength;
}
/**
* Add SFI Record.
*
* @param sfi
* @param recordNumber
* @param data
* @param dataOffset
* @param dataLength
*/
void addSFIRecord(byte sfi, byte recordNumber, byte[] data, short dataOffset, short dataLength) {
// Check if record length is longer than maximum supported length.
// Check if number of records has already reached maximum number.
// Check if SFI is not the same as SFI reserved for Transaction Log File.
if ((dataLength >= MAX_SFI_RECORD_LENGTH) ||
(this.sfiRecordCounter >= this.sfiRecords.length) ||
(sfi == Constants.SFI_TRANSACTION_LOG_FILE)) {
return;
}
// Check for duplicate record.
if (findSFIRecord(sfi, recordNumber) == RECORD_FOUND) {
// Found duplicate record, do not add record.
return;
}
this.sfiRecords[this.sfiRecordCounter] = new Record(sfi, recordNumber, data, dataOffset, dataLength);
this.sfiRecordCounter++;
}
/**
* Find record, retrieve record data, return record data.
*
* @param sfi
* @param recordNumber
* @param dataBuffer
* @return
*/
short getRecordData(byte sfi, short recordNumber, byte[] dataBuffer) {
byte result = findSFIRecord(sfi, recordNumber);
if (result == RECORD_FOUND) {
return Util.arrayCopyNonAtomic(this.foundRecord.data, (short) 0, dataBuffer, (short) 0, this.foundRecord.dataLength);
}
// Use offset 1 to indicate error type.
dataBuffer[(byte) 1] = result;
// Record not found.
// dataBuffer[1] = 0x00 if SFI not found.
// 0x01 if SFI found, record number not found.
return (short) -1;
}
/**
* Find record and return record data.
*
* @param sfi
* @param recordNumber
* @return
*/
byte[] getRecord(byte sfi, short recordNumber) {
byte result = findSFIRecord(sfi, recordNumber);
if (result == RECORD_FOUND) {
return this.foundRecord.data;
}
else {
return null;
}
}
/**
* SFI Record object.
*/
private final class Record implements Serializable {
private static final long serialVersionUID = 1L;
private byte sfi;
private byte recordNumber;
private byte[] data;
private short dataLength;
/**
* Constructor for SFI Record object.
*
* @param sfi
* @param recordNumber
* @param data
* @param dataOffset
* @param dataLength
*/
private Record(byte sfi, byte recordNumber, byte[] data, short dataOffset, short dataLength) {
this.sfi = sfi;
this.recordNumber = recordNumber;
// Add tag and length to record data.
short recordLength = (short) (dataLength + (byte) 2);
if (dataLength > (short) 127) {
recordLength++;
}
this.data = new byte[recordLength];
// Reuse 'recordLength' to track offset.
recordLength = (short) 0;
this.data[recordLength++] = Constants.TAG_READ_RECORD_RESPONSE_MESSAGE_TEMPLATE;
if (dataLength > (short) 127) {
this.data[recordLength++] = (byte) 0x81;
}
this.data[recordLength++] = (byte) dataLength;
this.dataLength = Util.arrayCopyNonAtomic(data, dataOffset, this.data, recordLength, dataLength);
}
}
}