package org.sleuthkit.autopsy.datamodel;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.accounts.BINRange;
public class CreditCards {
//Interface for objects that provide details about one or more BINs.
static public interface BankIdentificationNumber {
/**
* Get the city of the issuer.
*
* @return the city of the issuer.
*/
Optional<String> getBankCity();
/**
* Get the name of the issuer.
*
* @return the name of the issuer.
*/
Optional<String> getBankName();
/**
* Get the phone number of the issuer.
*
* @return the phone number of the issuer.
*/
Optional<String> getBankPhoneNumber();
/**
* Get the URL of the issuer.
*
* @return the URL of the issuer.
*/
Optional<String> getBankURL();
/**
* Get the brand of this BIN range.
*
* @return the brand of this BIN range.
*/
Optional<String> getBrand();
/**
* Get the type of card (credit vs debit) for this BIN range.
*
* @return the type of cards in this BIN range.
*/
Optional<String> getCardType();
/**
* Get the country of the issuer.
*
* @return the country of the issuer.
*/
Optional<String> getCountry();
/**
* Get the length of account numbers in this BIN range.
*
* NOTE: the length is currently unused, and not in the data file for
* any ranges. It could be quite helpfull for validation...
*
* @return the length of account numbers in this BIN range. Or an empty
* Optional if the length is unknown.
*
*/
Optional<Integer> getNumberLength();
/**
* Get the scheme this BIN range uses to amex,visa,mastercard, etc
*
* @return the scheme this BIN range uses.
*/
Optional<String> getScheme();
}
private static final Logger LOGGER = Logger.getLogger(CreditCards.class.getName());
/**
* Range Map from a (ranges of) BINs to data model object with details of
* the BIN, ie, bank name, phone, url, visa/amex/mastercard/...,
*/
@GuardedBy("CreditCards.class")
private final static RangeMap<Integer, BINRange> binRanges = TreeRangeMap.create();
/**
* Flag for if we have loaded the BINs from the file already.
*/
@GuardedBy("CreditCards.class")
private static boolean binsLoaded = false;
/**
* Load the BIN range information from disk. If the map has already been
* initialized, don't load again.
*/
synchronized private static void loadBINRanges() {
if (binsLoaded == false) {
try {
InputStreamReader in = new InputStreamReader(CreditCards.class.getResourceAsStream("ranges.csv")); //NON-NLS
CSVParser rangesParser = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in);
//parse each row and add to range map
for (CSVRecord record : rangesParser) {
/**
* Because ranges.csv allows both 6 and (the newer) 8 digit
* BINs, but we need a consistent length for the range map,
* we pad all the numbers out to 8 digits
*/
String start = StringUtils.rightPad(record.get("iin_start"), 8, "0"); //pad start with 0's //NON-NLS
//if there is no end listed, use start, since ranges will be closed.
String end = StringUtils.defaultIfBlank(record.get("iin_end"), start); //NON-NLS
end = StringUtils.rightPad(end, 8, "99"); //pad end with 9's //NON-NLS
final String numberLength = record.get("number_length"); //NON-NLS
try {
BINRange binRange = new BINRange(Integer.parseInt(start),
Integer.parseInt(end),
StringUtils.isBlank(numberLength) ? null : Integer.valueOf(numberLength),
record.get("scheme"), //NON-NLS
record.get("brand"), //NON-NLS
record.get("type"), //NON-NLS
record.get("country"), //NON-NLS
record.get("bank_name"), //NON-NLS
record.get("bank_url"), //NON-NLS
record.get("bank_phone"), //NON-NLS
record.get("bank_city")); //NON-NLS
binRanges.put(Range.closed(binRange.getBINstart(), binRange.getBINend()), binRange);
} catch (NumberFormatException numberFormatException) {
LOGGER.log(Level.WARNING, "Failed to parse BIN range: " + record.toString(), numberFormatException); //NON-NLS
}
binsLoaded = true;
}
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Failed to load BIN ranges form ranges.csv", ex); //NON-NLS
MessageNotifyUtil.Notify.warn("Credit Card Number Discovery", "There was an error loading Bank Identification Number information. Accounts will not have their BINs identified.");
}
}
}
/**
* Get an BINInfo object with details about the given BIN
*
* @param bin the BIN to get details of.
*
* @return
*/
synchronized static public BankIdentificationNumber getBINInfo(int bin) {
loadBINRanges();
return binRanges.get(bin);
}
}