<|start_filename|>calypso/src/fr/mikado/isodep/CardException.java<|end_filename|>
package fr.mikado.isodep;

/**
 * Created by alexis on 05/04/17.
 */
public class CardException extends Exception {

    public CardException(){
    }

    public CardException(String message){
        super(message);
    }

    public CardException(Throwable cause){
        super(cause);
    }

    public CardException(String message, Throwable cause){
        super(message, cause);
    }
}


<|start_filename|>calypso/src/fr/mikado/isodep/CommandAPDU.java<|end_filename|>
package fr.mikado.isodep;

/**
 * Created by alexis on 05/04/17.
 */
public class CommandAPDU {
    private final byte[] apdu;

    public CommandAPDU(byte[] cApdu){
        this.apdu = cApdu;
    }

    public byte[] getBytes(){
        return this.apdu;
    }
}


<|start_filename|>Makefile<|end_filename|>

all: CalypsoInspector CalypsoFileFinder

CFF_PACKAGE=fr/mikado/calypsofilefinder
CIN_PACKAGE=fr/mikado/calypsoinspector
DEP_PACKAGE=fr/mikado/isodepimpl

COM_UNITS=$(addprefix	fr/mikado/calypso/,	BitArray*				\
											CalypsoCard*			\
											CalypsoEnvironment*		\
											CalypsoFile*			\
											CalypsoRawDump*			\
											CalypsoRecordField*		\
											CalypsoRecord*			\
											SWDecoder*			)	\
		  $(addprefix	fr/mikado/isodep/,	CardException*			\
											CommandAPDU*			\
											IsoDepInterface*		\
											ResponseAPDU*		)	\
		  $(addprefix	fr/mikado/xmlio/,	XMLIOImpl*				\
											XMLIOInterface*			)

CFF_UNITS=$(addprefix	$(CFF_PACKAGE)/,	CalypsoCardSearch*		\
											Main*				)	\
		  $(addprefix	$(DEP_PACKAGE)/,	IsoDepImpl*				)

CIN_UNITS=$(addprefix	$(CIN_PACKAGE)/,	CalypsoDump*			\
		  									Main*				)	\
		  $(addprefix	$(DEP_PACKAGE)/,	IsoDepImpl*				)

COM_SOURCES=$(addsuffix .java,	$(COM_UNITS))
CFF_SOURCES=$(addsuffix .java,	$(CFF_UNITS))
CIN_SOURCES=$(addsuffix .java,	$(CIN_UNITS))

COM_BIN=$(addsuffix .class,	$(COM_UNITS))
CFF_BIN=$(addsuffix .class,	$(CFF_UNITS))
CIN_BIN=$(addsuffix .class,	$(CIN_UNITS))

JDOMLIB=lib/jdom2-2.0.3.jar
JDOMURL=http://www.java2s.com/Code/JarDownload/jdom2/jdom2-2.0.3.jar.zip

$(JDOMLIB):
	mkdir -p lib
	wget $(JDOMURL) -O $(JDOMLIB).zip
	unzip $(JDOMLIB).zip -d ./lib
	rm $(JDOMLIB).zip

out/libcalypso.jar: $(JDOMLIB) $(addprefix calypso/src/, $(COM_SOURCES))
	mkdir -p out
	cd calypso/src && javac -cp ../../$(JDOMLIB) $(COM_SOURCES)
	cd calypso/src && jar cvf ../../out/libcalypso.jar $(COM_BIN)

CalypsoFileFinder: out/libcalypso.jar $(addprefix src/, $(CFF_SOURCES))
	cd src && javac -cp ../$(JDOMLIB):../out/libcalypso.jar $(CFF_SOURCES)
	cd src && jar cvf ../out/CalypsoFileFinder.jar $(CFF_BIN)

CalypsoInspector: out/libcalypso.jar $(addprefix src/, $(CIN_SOURCES))
	cd src && javac -cp ../out/libcalypso.jar $(CIN_SOURCES)
	cd src && jar cvf ../out/CalypsoInspector.jar $(CIN_BIN)
	
clean:
	@find . -type f | grep \.class$$ | while read l; do echo Deleting $$l; rm -f $$l; done;

reset: clean
	@find out lib -type f | grep \.jar$$ | while read l; do echo Deleting $$l; rm -f $$l; done;
	@echo Deleting out/; rm -rf out/
	@echo Deleting lib/; rm -rf lib/


<|start_filename|>calypso/src/fr/mikado/calypso/CalypsoEnvironment.java<|end_filename|>
package fr.mikado.calypso;

import fr.mikado.xmlio.XMLIOImpl;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.JDOMParseException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;

/**
 * This class contains all the complementary information about the network and the structure of the card.
 */
public class CalypsoEnvironment {

    private class NetworkListItem{
        public String networkName;
        public int countryCode;
        public int networkId;
        public NetworkListItem(String networkName, int countryCode, int networkId){
            this.networkName = networkName;
            this.countryCode = countryCode;
            this.networkId = networkId;
        }
    }

    private HashMap<Integer, String>    fares;
    private HashMap<Integer, String>    profiles;
    private HashMap<Integer, String>    stops;
    private HashMap<Integer, String>    routes;
    private ArrayList<CalypsoFile>      cardStructure;
    private int countryId;
    private int networkId;
    //private int regionId;
    private boolean faresConfigured;
    private boolean profilesConfigured;
    private boolean topologyConfigured;
    private boolean cardStructureConfigured;
    private HashMap<String, NetworkListItem> networkList;

    // dirty and messy
    public ArrayList<Integer> contractPointers;

    /**
     * Creates an empty CalypsoEnvironment
     */
    public CalypsoEnvironment(){
        this.init();
    }

    public CalypsoEnvironment(int countryCode, int networkId) throws JDOMParseException {
        this.init();
        this.loadEnvironment(countryCode, networkId);
    }

    public CalypsoEnvironment(String networkName) throws Exception {
        init();
        this.buildEnvironmentList();
        this.loadEnvironment(networkName);
    }

    private void init(){
        this.fares = new HashMap<>();
        this.profiles = new HashMap<>();
        this.routes = new HashMap<>();
        this.stops = new HashMap<>();
        this.cardStructure = new ArrayList<>();
        this.topologyConfigured = false;
        this.cardStructureConfigured = false;
        this.contractPointers = new ArrayList<>();
    }

    public void buildEnvironmentList(){
        this.networkList = new HashMap<>();
        Document networks = this.loadDocument("networks/networks.xml");
        if(networks == null){
            System.out.println("You broke my shit man. Where my networks/networks.xml ?");
            return;
        }
        Element root = networks.getRootElement();
        if(root == null){
            System.out.println("You broke my shit man. Where my networks/networks.xml:countries node ?");
            return;
        }
        for(Element country : root.getChildren())
            for(Element network : country.getChildren()) {
                String networkName = network.getAttributeValue("name").toLowerCase();
                int countryCode = Integer.parseInt(network.getParentElement().getAttributeValue("id"));
                int networkId = Integer.parseInt(network.getAttributeValue("id"));

                this.networkList.put(networkName, new NetworkListItem(networkName, countryCode, networkId));
            }
    }

    public ArrayList<String> getAvailableNetworks(){
        if(this.networkList == null)
            return null;
        return new ArrayList<>(this.networkList.keySet());
    }

    public NetworkListItem getNetworkInfo(String networkName){
        return this.networkList.get(networkName);
    }

    public void loadEnvironment(int countryCode, int networkId) throws JDOMParseException {
        // countryCode/networkID/cardstruct.xml
        // countryCode/networkID/topology.xml
        // countryCode/networkID/fares.xml
        // countryCode/networkID/profiles.xml
        this.countryId = countryCode;
        this.networkId = networkId;

        String dir = "networks/"+countryCode+"/"+networkId+"/";
        System.out.println("Loading network profiles...");
        this.setNetworkProfiles(dir+"profiles.xml");
        System.out.println("Loading network fares...");
        this.setNetworkFares(dir+"fares.xml");
        System.out.println("Loading network topology...");
        this.setNetworkTopology(dir+"topology.xml");
        System.out.println("Loading card strucutre...");
        this.setCardStructure(dir+"cardstruct.xml");
    }

    public void loadEnvironment(String networkName) throws Exception {
        networkName = networkName.toLowerCase();
        NetworkListItem network = this.networkList.get(networkName);
        if(network == null)
            throw new Exception("Network not in networks.xml");
        this.loadEnvironment(network.countryCode, network.networkId);
    }

    public static Document openDocument(String filename){
        return new XMLIOImpl().loadDocument(filename);
    }

    // Yeah... Two names for the same method, right? Well I both need this to be static (be reusable outside of CalypsoEnv, and be overridable on Android... Ugly.
    public Document loadDocument(String filename){
        return openDocument(filename);
    }

    public boolean setNetworkProfiles(String faresFile) throws JDOMParseException {
        Document doc = this.loadDocument(faresFile);
        if(doc == null)
            return false;
        return setNetworkProfiles(doc);
    }

    /**
     * Loads the network profiles from an XML file.
     * @param doc XML Document containing profiles information.
     * @return Profile information properly loaded.
     * @throws JDOMParseException
     */
    public boolean setNetworkProfiles(Document doc) throws JDOMParseException {
        if(doc == null)
            return false;

        try {
            this.profilesConfigured = this.parseProfilesTree(doc);
        } catch (JDOMParseException e) {
            Logger.getGlobal().severe(e.getMessage());
        }

        if(this.profilesConfigured)
            System.out.println("Profiles loaded");
        else
            System.out.println("Profiles NOT loaded !");

        return this.profilesConfigured;
    }

    private boolean parseProfilesTree(Document doc) throws JDOMParseException {
        Element root = doc.getRootElement();
        if(!root.getName().equals("calypsoEnvironment")) {
            throw new JDOMParseException("Root element should be a calypsoEnvironment.", new Throwable("throwable"));
        }

        if(!this.checkCoherentEnvironment(root)){
            System.out.println("Incoherent network/country/region IDs.");
            return false;
        }

        Element profilesElement = root.getChildren().get(0);
        if(!profilesElement.getName().equals("profiles")) {
            throw new JDOMParseException("First first level child element should be profiles.", new Throwable("throwable"));
        }

        for(Element e : profilesElement.getChildren()) {
            String name = e.getAttributeValue("name");
            int id = Integer.parseInt(e.getAttributeValue("id"));
            this.profiles.put(id, name);
        }
        return true;
    }

    public boolean setNetworkFares(String faresFile) throws JDOMParseException {
        Document doc = this.loadDocument(faresFile);
        if(doc == null)
            return false;
        return setNetworkFares(doc);
    }

    /**
     * Loads the network fares from an XML file.
     * @param doc  XML Document containing fares information.
     * @return Network fare information properly loaded.
     * @throws JDOMParseException
     */
    public boolean setNetworkFares(Document doc) throws JDOMParseException {
        if(doc == null)
            return false;

        try {
            this.faresConfigured = this.parseFaresTree(doc);
        } catch (JDOMParseException e) {
            Logger.getGlobal().severe(e.getMessage());
        }

        if(this.faresConfigured)
            System.out.println("Fares loaded");
        else
            System.out.println("Fares NOT loaded !");

        return this.faresConfigured;
    }

    private boolean parseFaresTree(Document doc) throws JDOMParseException {
        Element root = doc.getRootElement();
        if(!root.getName().equals("calypsoEnvironment")) {
            throw new JDOMParseException("Root element should be a calypsoEnvironment.", new Throwable("throwable"));
        }

        if(!this.checkCoherentEnvironment(root)){
            System.out.println("Incoherent network/country/region IDs.");
            return false;
        }

        Element faresElement = root.getChildren().get(0);
        if(!faresElement.getName().equals("fares")) {
            throw new JDOMParseException("First first level child element should be fares.", new Throwable("throwable"));
        }

        for(Element e : faresElement.getChildren()) {
            String name = e.getAttributeValue("description");
            int id = Integer.parseInt(e.getAttributeValue("id"));
            this.fares.put(id, name);
        }
        return true;
    }

    public boolean setNetworkTopology(String topologyFile) throws JDOMParseException {
        Document doc = this.loadDocument(topologyFile);
        if(doc == null)
            return false;
        return setNetworkTopology(doc);
    }

    /**
     * Loads the network topology (stops and routes) from an XML file.
     * @param doc XML Document containing topology information.
     * @return Topology information properly loaded.
     * @throws JDOMParseException
     */
    public boolean setNetworkTopology(Document doc) throws JDOMParseException {
        if(doc == null)
            return false;

        try {
            this.topologyConfigured = this.parseTopologyTree(doc);
        } catch (JDOMParseException e) {
            Logger.getGlobal().severe(e.getMessage());
        }

        if(this.topologyConfigured)
            System.out.println("Topology loaded");
        else
            System.out.println("Topology NOT loaded !");

        return this.topologyConfigured;
    }

    private boolean parseTopologyTree(Document doc) throws JDOMParseException {
        Element root = doc.getRootElement();
        if(!root.getName().equals("calypsoEnvironment")) {
            throw new JDOMParseException("Root element should be a calypsoEnvironment.", new Throwable("throwable"));
        }

        if(!this.checkCoherentEnvironment(root)){
            System.out.println("Incoherent network/country/region IDs.");
            return false;
        }

        Element topoElement = root.getChildren().get(0);
        if(!topoElement.getName().equals("topology")) {
            throw new JDOMParseException("First first level child element should be topology.", new Throwable("throwable"));
        }

        // stops
        Element stops = topoElement.getChild("stops");
        for(Element e : stops.getChildren()) {
            String name = e.getAttributeValue("name");
            int id = Integer.parseInt(e.getAttributeValue("id"));
            this.stops.put(id, name);
        }

        // routes
        Element routes = topoElement.getChild("routes");
        for(Element e : routes.getChildren()) {
            String name = e.getAttributeValue("name");
            int id = Integer.parseInt(e.getAttributeValue("id"));
            this.routes.put(id, name);
        }
        return true;
    }

    public boolean setCardStructure(String cardStructureFile) throws JDOMParseException {
        Document doc = this.loadDocument(cardStructureFile);
        if(doc == null)
            return false;
        return setCardStructure(doc);
    }

    /**
     * Loads the card structure (File and their structures) from an XML file.
     * @param doc XML Document containing card structure information.
     * @return Card structure information properly loaded.
     * @throws JDOMParseException
     */
    public boolean setCardStructure(Document doc) throws JDOMParseException {
        this.cardStructure = new ArrayList<>();
        if(doc == null)
            return false;

        try {
            this.cardStructureConfigured = this.parseStructureTree(doc);
        } catch (JDOMParseException e) {
            Logger.getGlobal().severe(e.getMessage());
        }

        if(this.cardStructureConfigured)
            System.out.println("Card strucutre loaded");
        else
            System.out.println("Card strucutre NOT loaded !");
        return this.cardStructureConfigured;
    }

    // on vérifie que le fichier de topo correspond au fichier de carte.
    private boolean checkCoherentEnvironment(Element envElement) {
        String networkIdStr = envElement.getAttributeValue("networkId");
        if(networkIdStr != null) {
            int networkId = Integer.parseInt(networkIdStr);
            if (this.networkId != 0 && this.networkId != networkId)
                return false;
            else
                this.networkId = networkId;
        }

        String countryIdStr = envElement.getAttributeValue("countryId");
        if(countryIdStr != null) {
            int countryId = Integer.parseInt(envElement.getAttributeValue("countryId"));
            if (this.countryId != 0 && this.countryId != countryId)
                return false;
            else
                this.countryId = countryId;
        }

        return true;
    }

    private boolean parseStructureTree(Document doc) throws JDOMParseException {
        Element root = doc.getRootElement();
        if(!root.getName().equals("calypsoEnvironment")) {
            throw new JDOMParseException("Root element should be calypsoEnvironment.", new Throwable("throwable"));
        }

        if(!this.checkCoherentEnvironment(root)){
            System.out.println("Incoherent network/country/region IDs.");
            return false;
        }

        Element cardElement = root.getChildren().get(0);
        if(!cardElement.getName().equals("card")) {
            throw new JDOMParseException("First first level child element should be a card.", new Throwable("throwable"));
        }

        for(Element e : cardElement.getChildren()) {
            CalypsoFile nf = new CalypsoFile(e, this);
            this.cardStructure.add(nf);
            if(e.getAttributeValue("type").equals("DF"))
                for(Element ee : e.getChildren()) {
                    nf.addChild(new CalypsoFile(ee, this));
                }
        }

        return true;
    }

    public int getCountryId(){
        return this.countryId;
    }
/*
    public int getRegionId(){
        return this.regionId;
    }
*/
    public int getNetworkId(){
        return this.networkId;
    }

    public ArrayList<CalypsoFile> getFiles(){
        return this.cardStructure;
    }

    public String getRouteName(int routeId){
        return (this.routes.containsKey(routeId)) ? this.routes.get(routeId) : null;
    }

    public String getStopName(int stopId){
        return (this.stops.containsKey(stopId)) ? this.stops.get(stopId) : null;
    }

    public String getFareName(int fareId){
        return this.fares.get(fareId);
    }

    public boolean isTopologyConfigured() {
        return topologyConfigured;
    }

    public boolean areFaresConfigured() {
        return faresConfigured;
    }

    public boolean areProfilesConfigured(){
        return this.profilesConfigured;
    }

    public String getProfileName(int profileId){
        return this.profiles.get(profileId);
    }

    public int getContractIndex(int contractPointer){
        for(int i = 0;i<this.contractPointers.size();i++)
            if(this.contractPointers.get(i) == contractPointer)
                return i;
        return -1;
    }

    public ArrayList<Integer> getContractPointers(){
        return this.contractPointers;
    }

    public CalypsoFile getFile(String description){
        for(CalypsoFile f : this.getFiles()) {
            if (f.getDescription().equals(description))
                return f;
            for (CalypsoFile ff : f.getChildren())
                if (ff.getDescription().equals(description))
                    return ff;
        }
        return null;
    }

    public void purgeFilesContents(){
        this.contractPointers.clear();
        for(CalypsoFile f : this.getFiles()) {
            f.deleteRecords();
            for (CalypsoFile ff : f.getChildren())
                f.deleteRecords();
        }
    }

    public void clear(){
        this.routes = new HashMap<>();
        this.stops = new HashMap<>();
        this.profiles = new HashMap<>();
        this.fares = new HashMap<>();
        this.cardStructure = new ArrayList<>();
        this.contractPointers = new ArrayList<>();
        this.topologyConfigured = false;
        this.profilesConfigured = false;
        this.faresConfigured = false;
    }

    public void loadFileDump(CalypsoRawDump.CalypsoFileDump fd){
        CalypsoFile f = this.getFile(fd.getFilename());
        for(byte[] recBytes : fd.getRecords())
            f.newRecord(recBytes);
        for(CalypsoRawDump.CalypsoFileDump child : fd.getChildren())
            loadFileDump(child);
    }

    public void loadDump(CalypsoRawDump dump){
        this.purgeFilesContents();
        for(CalypsoRawDump.CalypsoFileDump fd : dump.getFiles())
            loadFileDump(fd);
    }
}


<|start_filename|>calypso/src/fr/mikado/calypso/CalypsoCard.java<|end_filename|>
package fr.mikado.calypso;

import fr.mikado.isodep.CardException;
import fr.mikado.isodep.CommandAPDU;
import fr.mikado.isodep.IsoDepInterface;
import fr.mikado.isodep.ResponseAPDU;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.logging.Logger;

import static fr.mikado.calypso.CalypsoFile.CalypsoFileType.EF;

/**
 * This class describes a Calypso Card from a Card and a CalypsoEnvironment.
 * the structure of the card is contained in the Calypso Environment.
 */
public class CalypsoCard {
    private CalypsoEnvironment env;
    private IsoDepInterface card;

    public CalypsoCard(IsoDepInterface card, CalypsoEnvironment env){
        this.card = card;
        this.env = env;
    }

    /**
     * @return The serial number printed on the card.
     */
    public long getCardNumber(){
        BitArray atr = new BitArray(card.getATR(), 40, 32);
        return atr.getLong();
    }

    public String getROMVersion() throws CardException {
        CommandAPDU c = new CommandAPDU(new byte[]{(byte)0x00, (byte)0x10, 0x00, 0x00, 0x00});
        ResponseAPDU r;
        try {
            r = this.card.transmit(c);
        } catch (CardException e) {
            System.out.println("Error while getting response APDU while asking for chip properties.");
            throw(e);
        }

        return new BitArray(r.getBytes(), 55*8, 8).toHex().toUpperCase();
    }

    /**
     * @return The version of the Celego chip inside.
     * @throws CardException
     */
    public String getChipVersion() throws CardException {
        CommandAPDU c = new CommandAPDU(new byte[]{(byte)0x00, (byte)0x10, 0x00, 0x00, 0x00});
        ResponseAPDU r;
        try {
            r = this.card.transmit(c);
        } catch (CardException e) {
            System.out.println("Error while getting response APDU while asking for chip properties.");
            throw(e);
        }
        return new BitArray(r.getBytes(), 54*8, 8).toHex().toLowerCase();
    }

    /**
     * Reads the contents of the file using the structure information contained in the Calypso Environment.
     */
    public void read() throws CardException {
        for(CalypsoFile f : env.getFiles())
            for(Integer LFI : f.getLFIs())
                this.readFile(f, LFI);
    }

    /**
     * Reads a record in a file selected by its SFI
     * @param SFI Short File Identifier
     * @param recordId Id of the record
     * @return Response APDU from the READ RECORD (94 B2) command.
     * @throws CardException
     */
    private ResponseAPDU readRecordSFI(int SFI, int recordId) throws CardException {
        return this.card.transmit(new CommandAPDU(new byte[]{(byte) 0x94, (byte) 0xB2, (byte) recordId, (byte)(0x04 + (SFI<<3)), 0x00}));
    }

    /**
     * Reads a record in a file previsouly selected by its LFI
     * @param recordId Id of the record
     * @return Response APDU from the READ RECORD (94 B2) command.
     * @throws CardException
     */
    private ResponseAPDU readRecordLFI(int recordId) throws CardException {
        return this.card.transmit(new CommandAPDU(new byte[]{(byte) 0x94, (byte) 0xB2, (byte) recordId, 0x04, 0x00}));
    }

    private boolean selectFile(CalypsoFile f, int LFI){
        return selectFileFromLFI(LFI);
    }

    /**
     * Selects a file on the card using LFI.
     * @param LFI Long File Identifier
     * @return Reponse APDU from the SELECT FILE (94 A4) command.
     */
    private boolean selectFileFromLFI(int LFI){
        CommandAPDU c = new CommandAPDU(new byte[]{(byte) 0x94, (byte) 0xA4, 0x00, 0x00, 0x02, (byte) ((LFI>>8)&0xff), (byte) (LFI&0xff), 0x00});
        ResponseAPDU r;

        try {
            r = this.card.transmit(c);
        } catch (CardException e) {
            System.out.println("Cannot select file LFI:"+Integer.toHexString(LFI)+"h : " + e.getMessage());
            return false;
        }
        if(r.getSW() != 0x9000)
            System.out.println("Error while selecting file LFI:"+Integer.toHexString(LFI)+"h : " + SWDecoder.decode(r.getSW()));
        return r.getSW() == 0x9000;
    }

    /**
     * Reads the contents of a CalypsoFile on the card.
     * @param f CalypsoFile
     * @param LFI Which LFI to use for this file
     */
    public void readFile(CalypsoFile f, Integer LFI) throws CardException {
        if(!f.isSFIAddressable())
            if(!this.selectFile(f, LFI))
                return;

        ResponseAPDU responseAPDU;
        if(f.getType() == EF) {
            for (int i = 1; ; i++) {
                if(f.isSFIAddressable())
                    responseAPDU = this.readRecordSFI(f.getSFI(), i);
                else
                    responseAPDU = this.readRecordLFI(i);
                if (responseAPDU.getSW() == 0x6A83)
                    break;
                if (responseAPDU.getSW() != 0x9000) {
                    Logger.getGlobal().warning("Could not read record #"+i+" for file "+f.getIdentifier() + "\n" +
                                                SWDecoder.decode(responseAPDU.getSW()) + " Last read record : " + i);
                    break;
                }
                f.newRecord(responseAPDU.getData());
            }
        }else
            for(CalypsoFile child : f.getChildren())
                if(child.isSFIAddressable())
                    this.readFile(child, null);
                else
                    for(Integer LFIe : child.getLFIs())
                        this.readFile(child, LFIe);
    }

    public void disconnect(){
        try {
            this.card.disconnect();
        } catch (CardException e) {
            Logger.getGlobal().warning("Error while disconnecting the card. Don't care.");
        }
    }

    /**
     * Prints all the data from the CalypsoCard.
     * @throws CardException
     */
    public void dump(boolean debug) throws CardException {
        System.out.println("Calypso card Country=" + this.env.getCountryId() + " Network=" + this.env.getNetworkId());
        //System.out.println("Calypso card number #" + this.getCardNumber());
        if(debug) {
            String chipVer = this.getChipVersion();
            System.out.println("Calypso celego chip : version " + chipVer + " (" + (chipVer.equals("3c ") ? "" : "NOT ") + "Android NFC compatible)");
            System.out.println("ROM version : " + this.getROMVersion());
            System.out.println("ATR :" + BitArray.bytes2Hex(this.card.getATR()));
        }
        System.out.println("Contents :");
        for(CalypsoFile f : this.env.getFiles())
            f.dump(1, debug);
    }

    /**
     * Prints the structure of the CalypsoCard.
     */
    public void dumpTree() {
        System.out.println("Calypso card Country="+ this.env.getCountryId() +" Network="+ this.env.getNetworkId());
        for(CalypsoFile f : this.env.getFiles())
            f.dumpStructure(1);
    }

    public CalypsoEnvironment getEnvironment(){
        return this.env;
    }
}













<|start_filename|>src/fr/mikado/calypsofilefinder/CalypsoCardSearch.java<|end_filename|>
package fr.mikado.calypsofilefinder;

import fr.mikado.calypso.*;
import fr.mikado.isodep.CardException;
import fr.mikado.isodep.IsoDepInterface;

import java.io.*;
import java.util.ArrayList;

public class CalypsoCardSearch {

    public class SearchResult{
        private CalypsoRecordField resultField;
        private CalypsoRecord resultRecord;
        private int offset;

        public SearchResult(){
        }
        public SearchResult(CalypsoRecordField field, int offset){
            this.resultField = field;
            this.offset = offset;
        }
        public SearchResult(CalypsoRecord record, int offset){
            this.resultRecord = record;
            this.offset = offset;
        }
        public String toString() {
            if(this.resultField != null) {
                CalypsoRecord r = this.resultField.getParentRecord();
                CalypsoFile f = r.getParent();
                StringBuilder sb = new StringBuilder();
                sb.append("Result found !");
                sb.append("\n   File : "  + f.getFullPath());
                sb.append("\n   Record #" + r.getId());
                sb.append("\n   Field \"" + this.resultField.getDescription() + "\"");
                sb.append("\n   Bit offset " + this.offset);
                return sb.toString();
            }else if(this.resultRecord != null){
                CalypsoFile f = this.resultRecord.getParent();
                StringBuilder sb = new StringBuilder();
                sb.append("Result found ! \n   LFI : ");
                sb.append(f.getFullPath());
                sb.append("\n   Record #" + this.resultRecord.getId());
                sb.append("\n   Bit offset " + this.offset);
                return sb.toString();
            }
            else{
                return "Lol wut?";
            }
        }
    }

    private final CalypsoCard card;
    private BitArray needle;
    protected ArrayList<SearchResult> results;

    public CalypsoCardSearch(CalypsoCard c, BitArray needle) throws IOException {
        this.needle = needle;
        this.card = c;
        this.results = new ArrayList<>();
    }

    public CalypsoCardSearch(IsoDepInterface c, CalypsoEnvironment cardEnv, BitArray needle) throws IOException {
        this.needle = needle;
        this.card= new CalypsoCard(c, cardEnv);
        this.results = new ArrayList<>();
    }

    private void findBitsInRecordField(CalypsoRecordField rf, BitArray bits){
        if(rf.getSubfields().size() > 0) { // if no children, search in the field bits.
            int offset = rf.getBits().find(bits);
            if (offset >= 0) // if the pattern is found
                this.results.add(new SearchResult(rf, offset));
        }else // if children, search through these
            for(CalypsoRecordField child : rf.getSubfields())
                findBitsInRecordField(child, bits);
    }

    private void findBitsInFile(CalypsoFile f, BitArray bits){
        if(f.getType() == CalypsoFile.CalypsoFileType.DF) // If file.type == DF, search in the files it contains
            for(CalypsoFile child : f.getChildren())
               findBitsInFile(child, bits);
        else
            for(CalypsoRecord r : f.getRecords())
                if(r.getFields().size() != 0) { // search for bits in the record fields if any
                    for (CalypsoRecordField rf : r.getFields())
                        this.findBitsInRecordField(rf, bits);
                }else{ // If no fields in the record, search directly in the record bits.
                    int offset = r.getBits().find(bits);
                    if(offset >= 0){
                        System.out.println("FOUND !");
                        this.results.add(new SearchResult(r, offset));
                    }
                }
    }

    public void search() throws CardException {
        System.out.println("Reading card...");
        this.card.read();
        System.out.println("Searching...");
        for(CalypsoFile f : this.card.getEnvironment().getFiles())
            findBitsInFile(f, needle);
        this.card.dump(true);
    }

    public void dumpResults(){
        if(this.results.size() > 0)
            for(SearchResult r : this.results)
                System.out.println(r);
        else
            System.out.println("No results found :(");
    }

}


<|start_filename|>calypso/src/fr/mikado/calypso/BitArray.java<|end_filename|>
package fr.mikado.calypso;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/**
 * Simple Bit manipulation class.
 * (may have some bugs not discovered in the unit tests...)
 */
public class BitArray {
    private ArrayList<Boolean> bits;
    private static char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    public BitArray(int size){
        this.bits = new ArrayList<>(Collections.nCopies(size, false));
    }

    public BitArray(ArrayList<Boolean> bits){
        this.bits = new ArrayList<>(Collections.nCopies(bits.size(), false));
        for (int i = 0; i < bits.size(); i++)
            this.bits.set(i, bits.get(i));
    }

    /**
     * Crée un BitArray à partir des bits dans un char[]
     * @param bs Le MSB est dans la première case de bs !
     */
    public BitArray(byte[] bs){
        this.bits = new ArrayList<>(Collections.nCopies(bs.length*8, false));
        for (int i = 0; i < bs.length*8; i++)
            this.set(bs.length*8-i-1, ((bs[i / 8] >> ((7-i%8)) & 0x01) == 1));
    }

    /**
     * Crée un BitArray à partir d'une range de bits dans un char[]
     * @param bs Le MSB est à gauche ! MSB = bs[start]>>7 & 0x01
     * @param start Index du premier bit
     * @param count Nombre de bits
     */
    public BitArray(byte[] bs, int start, int count){
        this.bits = new ArrayList<>(Collections.nCopies(count, false));
        for (int i = 0; i < count; i++) {
            int bit = (bs[(start+i) / 8] >>(7-(start+i)%8)) & 0x01;
            this.set(count-i-1, bit == 1);
        }
    }
    /**
     * Crée un BitArray à partir d'un long
     * @param bits bits
     * @param count Nombre de bits dans le long (en partant du LSB)
     */
    public BitArray(long bits, int count){
        assert(count <= 64);
        this.bits = new ArrayList<>(Collections.nCopies(count, false));
        for (int i = 0; i < count; i++)
            this.set(count-i-1, ((bits>>(count-1-i))&0x01L) == 1);
    }

    public static byte[] hex2Bytes(String hexBytes){
        if(hexBytes.length()%2 != 0)
            throw new NumberFormatException("Hex byte string has to contain an even number of characters");
        byte[] bytes = new byte[hexBytes.length()/2];
        char[] chars = hexBytes.toCharArray();
        String hexCharsStr = new String(hexChars);
        for(int i = 0;i<hexBytes.length();i+=2){
            bytes[i/2]  = (byte) (hexCharsStr.indexOf(chars[i]) << 4);
            bytes[i/2] |= (byte) (hexCharsStr.indexOf(chars[i+1]));
        }
        return bytes;
    }

    public static String bytes2Hex(byte[] bs, int offset, int len){
        byte[] t = new byte[len];
        for (int i = offset; i < len; i++)
            t[i-offset] = bs[i];
        return bytes2Hex(t);
    }

    public static String byte2Hex(byte b){
        StringBuilder sb = new StringBuilder();
        sb.append(hexChars[(b>>4)&0xf]);
        sb.append(hexChars[b&0xf]);
        return sb.toString();
    }

    public static String bytes2Hex(byte[] bs){
        StringBuilder sb = new StringBuilder();
        for(byte b : bs) {
            sb.append(byte2Hex(b));
            sb.append(' ');
        }
        return sb.toString();
    }

    public void set(int index, boolean val){
        this.bits.set(index, val);
    }

    public Boolean get(int index){
        return this.bits.get(index);
    }

    public BitArray get(int i, int li){
        BitArray res = new BitArray(new ArrayList<>(Collections.nCopies(li, false)));
        for(int j = i;j<(i+li);j++)
            res.set(j-i, this.bits.get(j));
        return res;
    }

    public BitArray rshift(){
        return this.get(1, this.bits.size()-1);
    }

    public BitArray rshift(int shift){
        return this.get(shift, Math.max(shift, this.bits.size()));
    }

    public BitArray lshift(){
        BitArray result = new BitArray(this.bits.size()+1);
        result.set(0, false);
        for (int i = 0; i < this.bits.size(); i++)
            result.set(i+1, this.bits.get(i));
        return result;
    }
    public BitArray lshift(int shift){
        if(shift == 0)
            return this;
        for (int i = 0; i < shift; i++)
            this.lshift();
        return this;
    }

    public BitArray getFlipped(){
        ArrayList<Boolean> nv = new ArrayList<>(Collections.nCopies(this.bits.size(), false));
        for(int i = 0;i<this.bits.size();i++)
            nv.set(i, this.bits.get(this.bits.size() - i - 1));
        return new BitArray(nv);
    }

    public static byte[] shiftByteArray(byte[] bs, int bitcount){
        byte[] res = new byte[bs.length + (bitcount%8 == 0 ? 1 : 0)]; // si le tableau est pile plein, on ajoute une case à droite pour préserver les bits outshiftés.

        for(int i =0;i<bitcount;i++){
            byte bit = (byte)(bs[i/8]>>(7-i%8) & 0x01);
            res[(i+1)/8] += bit<<(7-(i+1)%8);
        }

        return res;
    }

    public char[] getChars(){
        char[] res = new char[(this.bits.size()+7)/8];
        for (int i = 0; i < this.bits.size(); i++)
            res[res.length-i/8-1] += (char) ((this.bits.get(i) ? 1 :0) << (i % 8));
        return res;
    }
    public byte[] getBytes(){
        byte[] res = new byte[(this.bits.size()+7)/8];
        for (int i = 0; i < this.bits.size(); i++)
            res[res.length-i/8-1] += (char) ((this.bits.get(i) ? 1 :0) << (i % 8));
        return res;
    }

    public String toHex(){
        StringBuilder sb = new StringBuilder();
        byte[] bytes = this.getBytes();
        for (byte b : bytes) {
            sb.append(byte2Hex(b));
            sb.append(" ");
        }
        return sb.toString();
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();

        for(Boolean b : this.bits)
            sb.append(b ? '1': '0');
        sb.reverse();

        return sb.toString();
    }

    public int[] getInts(){
        int[] res = new int[(this.bits.size()+31)/32];

        for(int i = 0;i<this.bits.size();i++)
            res[i/32] = (res[i/32] << 1) + (this.get(this.bits.size() - i - 1) ? 1 : 0);

        return res;
    }

    public int getInt(){
        int res = 0;

        for(int i = 0;i<32;i++) {
            boolean bit = (32-i-1) >= this.bits.size() ? false : this.get(32-i-1);
            res = (res << 1) + (bit ? 1 : 0);
        }

        return res;
    }

    public long[] getLongs(){
        long[] res = new long[(this.bits.size()+63)/64];

        for(int i = 0;i<this.bits.size();i++)
            res[i/64] = (res[i/64] << 1) + (this.get(this.bits.size() - i - 1) ? 1 : 0);

        return res;
    }

    public long getLong(){
        long res = 0;

        for(int i = 0;i<64;i++) {
            boolean bit = (64 - i - 1) >= this.bits.size() ? false : this.get(64 - i - 1);
            res = (res << 1) + (bit ? 1 : 0);
        }

        return res;
    }

    public int getSize(){
        return this.bits.size();
    }

    public boolean equals(BitArray b){
        if(b == null)
            return false;
        if(this.bits.size() != b.bits.size())
            return false;
        for(int i = 0;i<this.getSize();i++)
            if(this.bits.get(i) != b.bits.get(i))
                return false;
        return true;
    }

    /**
     * Searches a bit pattern in the bit array.
     * @param needle The pattern to look for
     * @return The offset if >=0, -1 if not found
     */
    public int find(BitArray needle){
        if(this.getSize() < needle.getSize())
            return -1;
        for (int i = 0; i < this.getSize() - needle.getSize(); i++) {
            BitArray toCompare = this.getFlipped().get(i, needle.getSize()).getFlipped();
            if (needle.equals(toCompare))
                return i;
        }
        return -1;
    }

    /**
     * Searches a bit pattern in the bit array.
     * @param needle The pattern to look for
     * @param needleSize Size of the pattern
     * @return The offset if >=0, -1 if not found
     */
    public int find(long needle, int needleSize){
        BitArray needleBits = new BitArray(needle, needleSize);
        return this.find(needleBits);
    }

    /**
     * Checks if all bits are zeroes
     * @return All bits are zeroes
     */
    public boolean isNull(){
        for(Boolean b : this.bits)
            if(b)
                return false;
        return true;
    }
}


<|start_filename|>calypso/src/fr/mikado/isodep/IsoDepInterface.java<|end_filename|>
package fr.mikado.isodep;

/**
 * Created by alexis on 05/04/17.
 */
public interface IsoDepInterface {
    ResponseAPDU transmit(CommandAPDU cAPDU) throws CardException;
    byte[] getATR();
    void disconnect() throws CardException;
}


<|start_filename|>calypso/src/fr/mikado/calypso/SWDecoder.java<|end_filename|>
package fr.mikado.calypso;

import java.util.HashMap;

/**
 * Contains information from there, so some SWs may be EMV related : https://www.eftlab.co.uk/index.php/site-map/knowledge-base/118-apdu-response-list
 */

public class SWDecoder {

    private static class ISO78164Result{
        private int sw;
        private String description;
        private String level;

        public ISO78164Result(int sw, String level, String description){
            this.sw = sw;
            this.level = level;
            this.description = description;
        }
        public String getDescription() {  return description;   }
        public String getLevel() {  return level;   }
    }

    public static String decode(int SW){
        if(swDescriptions.containsKey(SW)) {
            ISO78164Result r = swDescriptions.get(SW);
            return r.getLevel() + " : " + r.getDescription();
        }else
            return "Unknown 7816-4 SW : 0x" + Integer.toHexString(SW);
    }

    private static HashMap<Integer, ISO78164Result> swDescriptions;
    static{
        swDescriptions = new HashMap<>();
        swDescriptions.put(0x6200, new ISO78164Result(0x6200, "Warning", "No information given (NV-Ram not changed)"));
        swDescriptions.put(0x6201, new ISO78164Result(0x6201, "Warning", "NV-Ram not changed 1."));
        swDescriptions.put(0x6281, new ISO78164Result(0x6281, "Warning", "Part of returned data may be corrupted"));
        swDescriptions.put(0x6282, new ISO78164Result(0x6282, "Warning", "End of file/record reached before reading Le bytes"));
        swDescriptions.put(0x6283, new ISO78164Result(0x6283, "Warning", "Selected file invalidated"));
        swDescriptions.put(0x6284, new ISO78164Result(0x6284, "Warning", "Selected file is not valid. FCI not formated according to ISO"));
        swDescriptions.put(0x6285, new ISO78164Result(0x6285, "Warning", "No input data available from a sensor on the card. No Purse Engine enslaved for R3bc"));
        swDescriptions.put(0x62A2, new ISO78164Result(0x62A2, "Warning", "Wrong R-MAC"));
        swDescriptions.put(0x62A4, new ISO78164Result(0x62A4, "Warning", "Card locked (during reset( ))"));
        swDescriptions.put(0x62F1, new ISO78164Result(0x62F1, "Warning", "Wrong C-MAC"));
        swDescriptions.put(0x62F3, new ISO78164Result(0x62F3, "Warning", "Internal reset"));
        swDescriptions.put(0x62F5, new ISO78164Result(0x62F5, "Warning", "Default agent locked"));
        swDescriptions.put(0x62F7, new ISO78164Result(0x62F7, "Warning", "Cardholder locked"));
        swDescriptions.put(0x62F8, new ISO78164Result(0x62F8, "Warning", "Basement is current agent"));
        swDescriptions.put(0x62F9, new ISO78164Result(0x62F9, "Warning", "CALC Key Set not unblocked"));
        swDescriptions.put(0x6300, new ISO78164Result(0x6300, "Warning", "No information given (NV-Ram changed)"));
        swDescriptions.put(0x6381, new ISO78164Result(0x6381, "Warning", "File filled up by the last write. Loading/updating is not allowed."));
        swDescriptions.put(0x6382, new ISO78164Result(0x6382, "Warning", "Card key not supported."));
        swDescriptions.put(0x6383, new ISO78164Result(0x6383, "Warning", "Reader key not supported."));
        swDescriptions.put(0x6384, new ISO78164Result(0x6384, "Warning", "Plaintext transmission not supported."));
        swDescriptions.put(0x6385, new ISO78164Result(0x6385, "Warning", "Secured transmission not supported."));
        swDescriptions.put(0x6386, new ISO78164Result(0x6386, "Warning", "Volatile memory is not available."));
        swDescriptions.put(0x6387, new ISO78164Result(0x6387, "Warning", "Non-volatile memory is not available."));
        swDescriptions.put(0x6388, new ISO78164Result(0x6388, "Warning", "Key number not valid."));
        swDescriptions.put(0x6389, new ISO78164Result(0x6389, "Warning", "Key length is not correct."));
        swDescriptions.put(0x63C0, new ISO78164Result(0x63C0, "Warning", "Verify fail, no try left."));
        swDescriptions.put(0x63C1, new ISO78164Result(0x63C1, "Warning", "Verify fail, 1 try left."));
        swDescriptions.put(0x63C2, new ISO78164Result(0x63C2, "Warning", "Verify fail, 2 tries left."));
        swDescriptions.put(0x63C3, new ISO78164Result(0x63C3, "Warning", "Verify fail, 3 tries left."));
        swDescriptions.put(0x63F1, new ISO78164Result(0x63F1, "Warning", "More data expected."));
        swDescriptions.put(0x63F2, new ISO78164Result(0x63F2, "Warning", "More data expected and proactive command pending."));
        swDescriptions.put(0x6400, new ISO78164Result(0x6400, "Error",   "No information given (NV-Ram not changed)"));
        swDescriptions.put(0x6401, new ISO78164Result(0x6401, "Error",   "Command timeout. Immediate response required by the card."));
        swDescriptions.put(0x6500, new ISO78164Result(0x6500, "Error",   "No information given"));
        swDescriptions.put(0x6501, new ISO78164Result(0x6501, "Error",   "Write error. Memory failure. There have been problems in writing or reading the EEPROM. Other hardware problems may also bring this error."));
        swDescriptions.put(0x6581, new ISO78164Result(0x6581, "Error",   "Memory failure"));
        swDescriptions.put(0x6600, new ISO78164Result(0x6600, "S",       "Error while receiving (timeout)"));
        swDescriptions.put(0x6601, new ISO78164Result(0x6601, "S",       "Error while receiving (character parity error)"));
        swDescriptions.put(0x6602, new ISO78164Result(0x6602, "S",       "Wrong checksum"));
        swDescriptions.put(0x6603, new ISO78164Result(0x6603, "S",       "The current DF file without FCI"));
        swDescriptions.put(0x6604, new ISO78164Result(0x6604, "S",       "No SF or KF under the current DF"));
        swDescriptions.put(0x6669, new ISO78164Result(0x6669, "S",       "Incorrect Encryption/Decryption Padding"));
        swDescriptions.put(0x6700, new ISO78164Result(0x6700, "Error",   "Wrong length"));
        swDescriptions.put(0x6800, new ISO78164Result(0x6800, "Error",   "No information given (The request function is not supported by the card)"));
        swDescriptions.put(0x6881, new ISO78164Result(0x6881, "Error",   "Logical channel not supported"));
        swDescriptions.put(0x6882, new ISO78164Result(0x6882, "Error",   "Secure messaging not supported"));
        swDescriptions.put(0x6883, new ISO78164Result(0x6883, "Error",   "Last command of the chain expected"));
        swDescriptions.put(0x6884, new ISO78164Result(0x6884, "Error",   "Command chaining not supported"));
        swDescriptions.put(0x6900, new ISO78164Result(0x6900, "Error",   "No information given (Command not allowed)"));
        swDescriptions.put(0x6901, new ISO78164Result(0x6901, "Error",   "Command not accepted (inactive state)"));
        swDescriptions.put(0x6981, new ISO78164Result(0x6981, "Error",   "Command incompatible with file structure"));
        swDescriptions.put(0x6982, new ISO78164Result(0x6982, "Error",   "Security condition not satisfied."));
        swDescriptions.put(0x6983, new ISO78164Result(0x6983, "Error",   "Authentication method blocked"));
        swDescriptions.put(0x6984, new ISO78164Result(0x6984, "Error",   "Referenced data reversibly blocked (invalidated)"));
        swDescriptions.put(0x6985, new ISO78164Result(0x6985, "Error",   "Conditions of use not satisfied."));
        swDescriptions.put(0x6986, new ISO78164Result(0x6986, "Error",   "Command not allowed (no current EF)"));
        swDescriptions.put(0x6987, new ISO78164Result(0x6987, "Error",   "Expected secure messaging (SM) object missing"));
        swDescriptions.put(0x6988, new ISO78164Result(0x6988, "Error",   "Incorrect secure messaging (SM) data object"));
        swDescriptions.put(0x698D, new ISO78164Result(0x698D, " ",       "Reserved"));
        swDescriptions.put(0x6996, new ISO78164Result(0x6996, "Error",   "Data must be updated again"));
        swDescriptions.put(0x69E1, new ISO78164Result(0x69E1, "Error",   "POL1 of the currently Enabled Profile prevents this action."));
        swDescriptions.put(0x69F0, new ISO78164Result(0x69F0, "Error",   "Permission Denied"));
        swDescriptions.put(0x69F1, new ISO78164Result(0x69F1, "Error",   "Permission Denied - Missing Privilege"));
        swDescriptions.put(0x6A00, new ISO78164Result(0x6A00, "Error",   "No information given (Bytes P1 and/or P2 are incorrect)"));
        swDescriptions.put(0x6A80, new ISO78164Result(0x6A80, "Error",   "The parameters in the data field are incorrect."));
        swDescriptions.put(0x6A81, new ISO78164Result(0x6A81, "Error",   "Function not supported"));
        swDescriptions.put(0x6A82, new ISO78164Result(0x6A82, "Error",   "File not found"));
        swDescriptions.put(0x6A83, new ISO78164Result(0x6A83, "Error",   "Record not found"));
        swDescriptions.put(0x6A84, new ISO78164Result(0x6A84, "Error",   "There is insufficient memory space in record or file"));
        swDescriptions.put(0x6A85, new ISO78164Result(0x6A85, "Error",   "Lc inconsistent with TLV structure"));
        swDescriptions.put(0x6A86, new ISO78164Result(0x6A86, "Error",   "Incorrect P1 or P2 parameter."));
        swDescriptions.put(0x6A87, new ISO78164Result(0x6A87, "Error",   "Lc inconsistent with P1-P2"));
        swDescriptions.put(0x6A88, new ISO78164Result(0x6A88, "Error",   "Referenced data not found"));
        swDescriptions.put(0x6A89, new ISO78164Result(0x6A89, "Error",   "File already exists"));
        swDescriptions.put(0x6A8A, new ISO78164Result(0x6A8A, "Error",   "DF name already exists."));
        swDescriptions.put(0x6AF0, new ISO78164Result(0x6AF0, "Error",   "Wrong parameter value"));
        swDescriptions.put(0x6B00, new ISO78164Result(0x6B00, "Error",   "Wrong parameter(s) P1-P2"));
        swDescriptions.put(0x6C00, new ISO78164Result(0x6C00, "Error",   "Incorrect P3 length."));
        swDescriptions.put(0x6D00, new ISO78164Result(0x6D00, "Error",   "Instruction code not supported or invalid"));
        swDescriptions.put(0x6E00, new ISO78164Result(0x6E00, "Error",   "Class not supported"));
        swDescriptions.put(0x6F00, new ISO78164Result(0x6F00, "Error",   "Command aborted - more exact diagnosis not possible (e.g., operating system error)."));
        swDescriptions.put(0x6FFF, new ISO78164Result(0x6FFF, "Error",   "Card dead (overuse, …)"));
        swDescriptions.put(0x9000, new ISO78164Result(0x9000, "I",       "Command successfully executed (OK)."));
        swDescriptions.put(0x9004, new ISO78164Result(0x9004, "Warning", "PIN not successfully verified, 3 or more PIN tries left"));
        swDescriptions.put(0x9008, new ISO78164Result(0x9008, " ",       "Key/file not found"));
        swDescriptions.put(0x9080, new ISO78164Result(0x9080, "Warning", "Unblock Try Counter has reached zero"));
        swDescriptions.put(0x9100, new ISO78164Result(0x9100, " ",       "OK"));
        swDescriptions.put(0x9101, new ISO78164Result(0x9101, " ",       "States.activity, States.lock Status or States.lockable has wrong value"));
        swDescriptions.put(0x9102, new ISO78164Result(0x9102, " ",       "Transaction number reached its limit"));
        swDescriptions.put(0x910C, new ISO78164Result(0x910C, " ",       "No changes"));
        swDescriptions.put(0x910E, new ISO78164Result(0x910E, " ",       "Insufficient NV-Memory to complete command"));
        swDescriptions.put(0x911C, new ISO78164Result(0x911C, " ",       "Command code not supported"));
        swDescriptions.put(0x911E, new ISO78164Result(0x911E, " ",       "CRC or MAC does not match data"));
        swDescriptions.put(0x9140, new ISO78164Result(0x9140, " ",       "Invalid key number specified"));
        swDescriptions.put(0x917E, new ISO78164Result(0x917E, " ",       "Length of command string invalid"));
        swDescriptions.put(0x919D, new ISO78164Result(0x919D, " ",       "Not allow the requested command"));
        swDescriptions.put(0x919E, new ISO78164Result(0x919E, " ",       "Value of the parameter invalid"));
        swDescriptions.put(0x91A0, new ISO78164Result(0x91A0, " ",       "Requested AID not present on PICC"));
        swDescriptions.put(0x91A1, new ISO78164Result(0x91A1, " ",       "Unrecoverable error within application"));
        swDescriptions.put(0x91AE, new ISO78164Result(0x91AE, " ",       "Authentication status does not allow the requested command"));
        swDescriptions.put(0x91AF, new ISO78164Result(0x91AF, " ",       "Additional data frame is expected to be sent"));
        swDescriptions.put(0x91BE, new ISO78164Result(0x91BE, " ",       "Out of boundary"));
        swDescriptions.put(0x91C1, new ISO78164Result(0x91C1, " ",       "Unrecoverable error within PICC"));
        swDescriptions.put(0x91CA, new ISO78164Result(0x91CA, " ",       "Previous Command was not fully completed"));
        swDescriptions.put(0x91CD, new ISO78164Result(0x91CD, " ",       "PICC was disabled by an unrecoverable error"));
        swDescriptions.put(0x91CE, new ISO78164Result(0x91CE, " ",       "Number of Applications limited to 28"));
        swDescriptions.put(0x91DE, new ISO78164Result(0x91DE, " ",       "File or application already exists"));
        swDescriptions.put(0x91EE, new ISO78164Result(0x91EE, " ",       "Could not complete NV-write operation due to loss of power"));
        swDescriptions.put(0x91F0, new ISO78164Result(0x91F0, " ",       "Specified file number does not exist"));
        swDescriptions.put(0x91F1, new ISO78164Result(0x91F1, " ",       "Unrecoverable error within file"));
        swDescriptions.put(0x9210, new ISO78164Result(0x9210, "Error",   "Insufficient memory. No more storage available."));
        swDescriptions.put(0x9240, new ISO78164Result(0x9240, "Error",   "Writing to EEPROM not successful."));
        swDescriptions.put(0x9301, new ISO78164Result(0x9301, " ",       "Integrity error"));
        swDescriptions.put(0x9302, new ISO78164Result(0x9302, " ",       "Candidate S2 invalid"));
        swDescriptions.put(0x9303, new ISO78164Result(0x9303, "Error",   "Application is permanently locked"));
        swDescriptions.put(0x9400, new ISO78164Result(0x9400, "Error",   "No EF selected."));
        swDescriptions.put(0x9401, new ISO78164Result(0x9401, " ",       "Candidate currency code does not match purse currency"));
        swDescriptions.put(0x9402, new ISO78164Result(0x9402, " ",       "Candidate amount too high"));
        swDescriptions.put(0x9402, new ISO78164Result(0x9402, "Error",   "Address range exceeded."));
        swDescriptions.put(0x9403, new ISO78164Result(0x9403, " ",       "Candidate amount too low"));
        swDescriptions.put(0x9404, new ISO78164Result(0x9404, "Error",   "FID not found, record not found or comparison pattern not found."));
        swDescriptions.put(0x9405, new ISO78164Result(0x9405, " ",       "Problems in the data field"));
        swDescriptions.put(0x9406, new ISO78164Result(0x9406, "Error",   "Required MAC unavailable"));
        swDescriptions.put(0x9407, new ISO78164Result(0x9407, " ",       "Bad currency : purse engine has no slot with R3bc currency"));
        swDescriptions.put(0x9408, new ISO78164Result(0x9408, " ",       "R3bc currency not supported in purse engine"));
        swDescriptions.put(0x9408, new ISO78164Result(0x9408, "Error",   "Selected file type does not match command."));
        swDescriptions.put(0x9580, new ISO78164Result(0x9580, " ",       "Bad sequence"));
        swDescriptions.put(0x9681, new ISO78164Result(0x9681, " ",       "Slave not found"));
        swDescriptions.put(0x9700, new ISO78164Result(0x9700, " ",       "PIN blocked and Unblock Try Counter is 1 or 2"));
        swDescriptions.put(0x9702, new ISO78164Result(0x9702, " ",       "Main keys are blocked"));
        swDescriptions.put(0x9704, new ISO78164Result(0x9704, " ",       "PIN not successfully verified, 3 or more PIN tries left"));
        swDescriptions.put(0x9784, new ISO78164Result(0x9784, " ",       "Base key"));
        swDescriptions.put(0x9785, new ISO78164Result(0x9785, " ",       "Limit exceeded - C-MAC key"));
        swDescriptions.put(0x9786, new ISO78164Result(0x9786, " ",       "SM error - Limit exceeded - R-MAC key"));
        swDescriptions.put(0x9787, new ISO78164Result(0x9787, " ",       "Limit exceeded - sequence counter"));
        swDescriptions.put(0x9788, new ISO78164Result(0x9788, " ",       "Limit exceeded - R-MAC length"));
        swDescriptions.put(0x9789, new ISO78164Result(0x9789, " ",       "Service not available"));
        swDescriptions.put(0x9802, new ISO78164Result(0x9802, "Error",   "No PIN defined."));
        swDescriptions.put(0x9804, new ISO78164Result(0x9804, "Error",   "Access conditions not satisfied, authentication failed."));
        swDescriptions.put(0x9835, new ISO78164Result(0x9835, "Error",   "ASK RANDOM or GIVE RANDOM not executed."));
        swDescriptions.put(0x9840, new ISO78164Result(0x9840, "Error",   "PIN verification not successful."));
        swDescriptions.put(0x9850, new ISO78164Result(0x9850, "Error",   "INCREASE or DECREASE could not be executed because a limit has been reached."));
        swDescriptions.put(0x9862, new ISO78164Result(0x9862, "Error",   "Authentication Error, application specific (incorrect MAC)"));
        swDescriptions.put(0x9900, new ISO78164Result(0x9900, " ",       "1 PIN try left"));
        swDescriptions.put(0x9904, new ISO78164Result(0x9904, " ",       "PIN not successfully verified, 1 PIN try left"));
        swDescriptions.put(0x9985, new ISO78164Result(0x9985, " ",       "Wrong status - Cardholder lock"));
        swDescriptions.put(0x9986, new ISO78164Result(0x9986, "Error",   "Missing privilege"));
        swDescriptions.put(0x9987, new ISO78164Result(0x9987, " ",       "PIN is not installed"));
        swDescriptions.put(0x9988, new ISO78164Result(0x9988, " ",       "Wrong status - R-MAC state"));
        swDescriptions.put(0x9A00, new ISO78164Result(0x9A00, " ",       "2 PIN try left"));
        swDescriptions.put(0x9A04, new ISO78164Result(0x9A04, " ",       "PIN not successfully verified, 2 PIN try left"));
        swDescriptions.put(0x9A71, new ISO78164Result(0x9A71, " ",       "Wrong parameter value - Double agent AID"));
        swDescriptions.put(0x9A72, new ISO78164Result(0x9A72, " ",       "Wrong parameter value - Double agent Type"));
        swDescriptions.put(0x9D05, new ISO78164Result(0x9D05, "Error",   "Incorrect certificate type"));
        swDescriptions.put(0x9D07, new ISO78164Result(0x9D07, "Error",   "Incorrect session data size"));
        swDescriptions.put(0x9D08, new ISO78164Result(0x9D08, "Error",   "Incorrect DIR file record size"));
        swDescriptions.put(0x9D09, new ISO78164Result(0x9D09, "Error",   "Incorrect FCI record size"));
        swDescriptions.put(0x9D0A, new ISO78164Result(0x9D0A, "Error",   "Incorrect code size"));
        swDescriptions.put(0x9D10, new ISO78164Result(0x9D10, "Error",   "Insufficient memory to load application"));
        swDescriptions.put(0x9D11, new ISO78164Result(0x9D11, "Error",   "Invalid AID"));
        swDescriptions.put(0x9D12, new ISO78164Result(0x9D12, "Error",   "Duplicate AID"));
        swDescriptions.put(0x9D13, new ISO78164Result(0x9D13, "Error",   "Application previously loaded"));
        swDescriptions.put(0x9D14, new ISO78164Result(0x9D14, "Error",   "Application history list full"));
        swDescriptions.put(0x9D15, new ISO78164Result(0x9D15, "Error",   "Application not open"));
        swDescriptions.put(0x9D17, new ISO78164Result(0x9D17, "Error",   "Invalid offset"));
        swDescriptions.put(0x9D18, new ISO78164Result(0x9D18, "Error",   "Application already loaded"));
        swDescriptions.put(0x9D19, new ISO78164Result(0x9D19, "Error",   "Invalid certificate"));
        swDescriptions.put(0x9D1A, new ISO78164Result(0x9D1A, "Error",   "Invalid signature"));
        swDescriptions.put(0x9D1B, new ISO78164Result(0x9D1B, "Error",   "Invalid KTU"));
        swDescriptions.put(0x9D1D, new ISO78164Result(0x9D1D, "Error",   "MSM controls not set"));
        swDescriptions.put(0x9D1E, new ISO78164Result(0x9D1E, "Error",   "Application signature does not exist"));
        swDescriptions.put(0x9D1F, new ISO78164Result(0x9D1F, "Error",   "KTU does not exist"));
        swDescriptions.put(0x9D20, new ISO78164Result(0x9D20, "Error",   "Application not loaded"));
        swDescriptions.put(0x9D21, new ISO78164Result(0x9D21, "Error",   "Invalid Open command data length"));
        swDescriptions.put(0x9D30, new ISO78164Result(0x9D30, "Error",   "Check data parameter is incorrect (invalid start address)"));
        swDescriptions.put(0x9D31, new ISO78164Result(0x9D31, "Error",   "Check data parameter is incorrect (invalid length)"));
        swDescriptions.put(0x9D32, new ISO78164Result(0x9D32, "Error",   "Check data parameter is incorrect (illegal memory check area)"));
        swDescriptions.put(0x9D40, new ISO78164Result(0x9D40, "Error",   "Invalid MSM Controls ciphertext"));
        swDescriptions.put(0x9D41, new ISO78164Result(0x9D41, "Error",   "MSM controls already set"));
        swDescriptions.put(0x9D42, new ISO78164Result(0x9D42, "Error",   "Set MSM Controls data length less than 2 bytes"));
        swDescriptions.put(0x9D43, new ISO78164Result(0x9D43, "Error",   "Invalid MSM Controls data length"));
        swDescriptions.put(0x9D44, new ISO78164Result(0x9D44, "Error",   "Excess MSM Controls ciphertext"));
        swDescriptions.put(0x9D45, new ISO78164Result(0x9D45, "Error",   "Verification of MSM Controls data failed"));
        swDescriptions.put(0x9D50, new ISO78164Result(0x9D50, "Error",   "Invalid MCD Issuer production ID"));
        swDescriptions.put(0x9D51, new ISO78164Result(0x9D51, "Error",   "Invalid MCD Issuer ID"));
        swDescriptions.put(0x9D52, new ISO78164Result(0x9D52, "Error",   "Invalid set MSM controls data date"));
        swDescriptions.put(0x9D53, new ISO78164Result(0x9D53, "Error",   "Invalid MCD number"));
        swDescriptions.put(0x9D54, new ISO78164Result(0x9D54, "Error",   "Reserved field error"));
        swDescriptions.put(0x9D55, new ISO78164Result(0x9D55, "Error",   "Reserved field error"));
        swDescriptions.put(0x9D56, new ISO78164Result(0x9D56, "Error",   "Reserved field error"));
        swDescriptions.put(0x9D57, new ISO78164Result(0x9D57, "Error",   "Reserved field error"));
        swDescriptions.put(0x9D60, new ISO78164Result(0x9D60, "Error",   "MAC verification failed"));
        swDescriptions.put(0x9D61, new ISO78164Result(0x9D61, "Error",   "Maximum number of unblocks reached"));
        swDescriptions.put(0x9D62, new ISO78164Result(0x9D62, "Error",   "Card was not blocked"));
        swDescriptions.put(0x9D63, new ISO78164Result(0x9D63, "Error",   "Crypto functions not available"));
        swDescriptions.put(0x9D64, new ISO78164Result(0x9D64, "Error",   "No application loaded"));
        swDescriptions.put(0x9E00, new ISO78164Result(0x9E00, " ",       "PIN not installed"));
        swDescriptions.put(0x9E04, new ISO78164Result(0x9E04, " ",       "PIN not successfully verified, PIN not installed"));
        swDescriptions.put(0x9F00, new ISO78164Result(0x9F00, " ",       "PIN blocked and Unblock Try Counter is 3"));
        swDescriptions.put(0x9F04, new ISO78164Result(0x9F04, " ",       "PIN not successfully verified, PIN blocked and Unblock Try Counter is 3"));
    }
}


<|start_filename|>calypso/test/fr/mikado/calypso/BitArrayTest.java<|end_filename|>
package fr.mikado.calypso;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Created by alexis on 15/03/17.
 */
public class BitArrayTest {

    @org.junit.Test
    public void testSet() throws Exception {
        BitArray test      = new BitArray(new byte[]{1, 3, 3, 4});
        BitArray expected = new BitArray(new byte[]{1, 2, 3, 4});

        test.set(16, false);
        assertArrayEquals(expected.getBytes(), test.getBytes());
    }

    @org.junit.Test
    public void testGet() throws Exception {
        BitArray test = new BitArray(new byte[]{1, 3, 3, 4, 6, 7});
        assertEquals(test.get(0), true);
    }

    @org.junit.Test
    public void testRshift() throws Exception {
        BitArray test =     new BitArray(new byte[]{2, 4, 6, 8, 13,            15});
        BitArray expected = new BitArray(new byte[]{1, 2, 3, 4,  6, (byte)(7+128)});

        assertArrayEquals(expected.getBytes(), test.rshift().getBytes());
    }

    @org.junit.Test
    public void testLshift() throws Exception {
        BitArray test =     new BitArray(new byte[]{   (byte)0x80, 2,  6, (byte)(7+128)});
        BitArray expected = new BitArray(new byte[]{1,          0, 4, 13,           14});

        assertArrayEquals(expected.getBytes(), test.lshift().getBytes());

    }

    @org.junit.Test
    public void testShiftByteArray() throws Exception {

    }

    @org.junit.Test
    public void testGetChars() throws Exception {
        BitArray test = new BitArray(new byte[]{'a', 'b', 'c', 'd'});
        char[] expected = new char[]{'a', 'b', 'c', 'd'};

        char[] result = test.getChars();
        assertArrayEquals(expected, result);
    }

    @org.junit.Test
    public void testGetBytes() throws Exception {
        BitArray test = new BitArray(new byte[]{'a', 'b', 'c', 'd'});
        byte[] expected = new byte[]{0x61, 0x62, 0x63, 0x64};

        byte[] result = test.getBytes();
        assertArrayEquals(expected, result);
    }

    @org.junit.Test
    public void testToHex() throws Exception {
        String expected = "61 62 63 64 ";
        BitArray test = new BitArray(new byte[]{'a', 'b', 'c', 'd'});

        assertEquals(expected, test.toHex());
    }

    @Test
    public void testGetInt() throws Exception {
        BitArray test = new BitArray(new byte[]{0x0c, 0x00, 0x00});
        int expected = 0x000c0000;

        int result = test.getInt();
        assertEquals(expected, result);
    }

    @Test
    public void testGetLong() throws Exception {
        BitArray test = new BitArray(new byte[]{0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
        long expected = 0x000c000000000000L;

        long result = test.getLong();
        assertEquals(expected, result);
    }

    @Test
    public void testConstructFromLong() throws Exception{
        BitArray expected = new BitArray(new byte[]{0x01, 0x02}, 7, 9);
        BitArray test = new BitArray(258L, 9);

        assertTrue(expected.equals(test));
    }

    @Test
    public void testEquals() throws Exception{
        BitArray val1 = new BitArray(new byte[]{0x01, 0x02}, 7, 9);
        BitArray val2 = new BitArray(new byte[]{0x01, 0x02}, 7, 9);

        assertTrue(val1.equals(val2));
    }

    @Test
    public void testHex2Bytes() throws Exception{
        String hex = "00ab000af88000";

        byte[] expected = new byte[]{0x00, (byte)0xab, 0x00, 0x0a, (byte)0xf8, (byte)0x80, 0x00};
        byte[] test = BitArray.hex2Bytes(hex);

        assertArrayEquals(expected, test);
    }
}

<|start_filename|>calypso/src/fr/mikado/calypso/CalypsoRecord.java<|end_filename|>
package fr.mikado.calypso;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;

/**
 * This class describes a Calypso Record.
 * A record is basically an array of record fields that are filled from a byte buffer.
 * Send read record APDU, get the response byte buffer, fill the records fields using fillRecord(byte[] buffer);
 */
public class CalypsoRecord {
    BitArray bits;
    CalypsoFile parent;
    int id; // record ID (index in file.getRecords())
    String mappingName;
    ArrayList<CalypsoRecordField> fields;
    HashMap<String, CalypsoRecordField> fieldsByName;

    public CalypsoRecord(ArrayList<CalypsoRecordField> fields, CalypsoFile parent){
        this.parent = parent;
        this.id = parent.getRecords().size();
        this.fields = new ArrayList<>();
        this.fieldsByName = new HashMap<>();
        for(CalypsoRecordField r : fields) {
            CalypsoRecordField field = new CalypsoRecordField(r);
            field.setParentRecord(this);
            this.fields.add(field);
            this.fieldsByName.put(field.getDescription(), field);
        }
    }

    public boolean fillRecord(byte[] buffer){
        int offset = 0; // en bits !
        this.bits = new BitArray(buffer);
        try{
            for(CalypsoRecordField f : this.fields)
                offset += f.fill(buffer, offset);
        }catch(IndexOutOfBoundsException e){
            Logger.getGlobal().severe("Attempting to parse data outside of record !");
            return false;
        }
        return true;
    }

    public void print(int n, boolean debug){
        if(debug) {
            System.out.println(CalypsoFile.nesting(n) + "Raw contents : " + this.getBitsAsHex());
            if (this.mappingName != null)
                System.out.println(CalypsoFile.nesting(n) + "Mapping name : " + this.getMappingName());
        }
        for(CalypsoRecordField f : this.fields)
            f.print(n+1, debug);
    }

    public CalypsoRecordField getRecordField(String description){
        return this.fieldsByName.containsKey(description) ? this.fieldsByName.get(description) : null;
    }

    public ArrayList<CalypsoRecordField> getFields(){
        return this.fields;
    }

    public String getBitsAsHex(){
        return this.bits.toHex();
    }

    public BitArray getBits(){
        return this.bits;
    }

    public CalypsoFile getParent(){
        return this.parent;
    }

    public int getId(){
        return this.id;
    }

    public void setMappingName(String mappingName){
        this.mappingName = mappingName;
    }

    public String getMappingName(){
        return this.mappingName;
    }

    public boolean isEmpty(){
        /*
        return this.bits.isNull();
        */
        for(CalypsoRecordField rf : this.fields)
            if(rf.isFilled())
                return false;
        return true;
    }
}



<|start_filename|>src/fr/mikado/calypsoinspector/CalypsoDump.java<|end_filename|>
package fr.mikado.calypsoinspector;

import fr.mikado.calypso.CalypsoEnvironment;
import fr.mikado.calypso.CalypsoFile;
import fr.mikado.calypso.CalypsoRecord;
import fr.mikado.calypso.CalypsoRecordField;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Created by alexis on 06/04/17.
 */
public class CalypsoDump {

    /**
     * Prints out the contents of the event log of the card. (last trips)
     */
    public static void dumpTrips(CalypsoEnvironment env) {
        CalypsoFile events = env.getFile("Events");
        CalypsoFile contracts = env.getFile("Contracts");

        if (events != null) {
            for(CalypsoRecord rec : events.getRecords()){
                // basic event details
                String date = rec.getRecordField("Event Date").getConvertedValue();
                String time = rec.getRecordField("Event Time").getConvertedValue();
                CalypsoRecordField event = rec.getRecordField("Event");
                String stop = event.getSubfield("EventLocationId").getConvertedValue();
                String eventCode = event.getSubfield("EventCode").getConvertedValue();
                String route = event.getSubfield("EventRouteNumber").getConvertedValue();
                String direction = event.getSubfield("EventData").getSubfield("EventDataRouteDirection").getConvertedValue();

                String fare = "<Contracts not loaded>";
                if(contracts != null) {
                    // which contract for this event ?
                    fare = "Error while decoding contract pointer. - Dog, probably.";
                    int farePointer = event.getSubfield("EventContractPointer").getBits().getInt();
                    int contractIndex = env.getContractIndex(farePointer);
                    if (contractIndex >= 0) {
                        CalypsoRecord contract = contracts.getRecords().get(contractIndex);
                        CalypsoRecordField contractBitmap = contract.getRecordField("PublicTransportContractBitmap");
                        CalypsoRecordField contractType = contractBitmap.getSubfield("ContractType");
                        fare = contractType.getConvertedValue();
                    }
                }

                System.out.println("Event :");
                System.out.println(" Date    : " + date + " " + time);
                System.out.println(" Arrêt   : " + stop);
                System.out.println(" Ligne   : " + route);
                System.out.println(" Sens    : " + direction);
                System.out.println(" Code    : " + eventCode);
                System.out.println(" Contrat : " + fare +"\n");
            }
        }
    }

    public static void dumpProfiles(CalypsoEnvironment env){
        CalypsoFile envHolder = env.getFile("Environment, Holder");
        CalypsoRecord envHolderRec = envHolder.getRecords().get(0);
        CalypsoRecordField holderProfiles = envHolderRec.getRecordField("Holder Bitmap").getSubfield("Holder Profiles(0..4)");

        for(CalypsoRecordField f : holderProfiles.getSubfields()) {
            if(!f.isFilled()) {
                System.out.println("No profiles.");
                break;
            }
            System.out.println("Profile :");
            System.out.println(" Label        : " + f.getSubfield("Profile Number").getConvertedValue());
            System.out.println(" Profile date : " + f.getSubfield("Profile Date").getConvertedValue());
        }
        System.out.print("\n");
    }

    public static void dumpContracts(CalypsoEnvironment env) throws ParseException {
        String fare = "";
        DateFormat format = new SimpleDateFormat("dd/MM/yyy", Locale.FRANCE);
        Date now = new Date();

        CalypsoFile contracts = env.getFile("Contracts");
        if(contracts != null) {
            // which contract for this event ?
            for(CalypsoRecord contract : contracts.getRecords()) {
                CalypsoRecordField contractBitmap = contract.getRecordField("PublicTransportContractBitmap");
                CalypsoRecordField contractType = contractBitmap.getSubfield("ContractType");
                CalypsoRecordField contractStatus= contractBitmap.getSubfield("ContractStatus");
                CalypsoRecordField contractValidity = contractBitmap.getSubfield("ContractValidityInfo");
                CalypsoRecordField contractStart = contractValidity.getSubfield("ContractValidityStartDate");
                CalypsoRecordField contractEnd = contractValidity.getSubfield("ContractValidityEndDate");
                String contractEndStr = contractEnd.getConvertedValue();
                System.out.println("Contract :");
                System.out.println("   Label      : " + contractType.getConvertedValue());
                System.out.println("   Start date : " + contractStart.getConvertedValue());
                System.out.print("   End date   : " + contractEndStr);
                if(contractEndStr.length() > 0 && now.after(format.parse(contractEndStr)))
                    System.out.print(" (EXPIRED)");
                System.out.print("\n");
                System.out.println("   Status     : " + contractStatus.getConvertedValue());
            }
        }else
            System.out.println("<Contracts not loaded>");
        System.out.print("\n");
    }
}


<|start_filename|>calypso/src/fr/mikado/xmlio/XMLIOImpl.java<|end_filename|>
package fr.mikado.xmlio;

import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Logger;

public class XMLIOImpl implements XMLIOInterface {

    @Override
    public Document loadDocument(String filename) {
        SAXBuilder sax = new SAXBuilder();
        Document doc;
        try {
            doc = sax.build(new File(filename));
        } catch (JDOMException e) {
            Logger.getGlobal().severe("Error while parsing file \""+filename+"\"");
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            Logger.getGlobal().severe("Cannot read file \""+filename+"\"");
            return null;
        }
        return doc;
    }

    @Override
    public boolean writeDocument(Document doc, String filename) {
        XMLOutputter outputter = new XMLOutputter();
        outputter.setFormat(Format.getPrettyFormat());
        try {
            outputter.output(doc, new FileWriter(new File(filename)));
        } catch (IOException e) {
            return false;
        }
        return true;
    }
}


<|start_filename|>src/fr/mikado/isodepimpl/IsoDepImpl.java<|end_filename|>
package fr.mikado.isodepimpl;

import fr.mikado.isodep.CardException;
import fr.mikado.isodep.CommandAPDU;
import fr.mikado.isodep.IsoDepInterface;
import fr.mikado.isodep.ResponseAPDU;

import javax.smartcardio.*;

/**
 * Created by alexis on 05/04/17.
 */
public class IsoDepImpl implements IsoDepInterface {

    private Card card;

    public IsoDepImpl(Card card){
        this.card = card;
    }

    @Override
    public ResponseAPDU transmit(CommandAPDU cAPDU) throws CardException {
        javax.smartcardio.CommandAPDU c = new javax.smartcardio.CommandAPDU(cAPDU.getBytes());
        javax.smartcardio.ResponseAPDU r;
        try {
            r = card.getBasicChannel().transmit(c);
        } catch (javax.smartcardio.CardException e) {
            throw new CardException(e.getMessage());
        }
        byte[] rb = r.getBytes();
        return new ResponseAPDU(rb);
    }

    @Override
    public byte[] getATR() {
        return this.card.getATR().getBytes();
    }

    @Override
    public void disconnect() throws CardException {
        try {
            this.card.disconnect(false);
        } catch (javax.smartcardio.CardException e) {
            throw new CardException(e.getMessage());
        }
    }

    public static IsoDepImpl getDefaultCard() throws Exception {
        CardTerminal term = null;
        try {
            term = TerminalFactory.getDefault().terminals().list().get(0);
        } catch (Exception e) {
            System.out.println("No terminal plugged... : " + e.getMessage());
        }
        if(term == null)
            throw new CardException("No terminal plugged.");

        System.out.println("Waiting for card...");
        term.waitForCardPresent(0);
        System.out.println("Card found !");

        return new IsoDepImpl(term.connect("T=1"));
    }
}





































<|start_filename|>calypso/src/fr/mikado/calypso/CalypsoRecordField.java<|end_filename|>
package fr.mikado.calypso;

import org.jdom2.Document;
import org.jdom2.Element;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;

import static fr.mikado.calypso.CalypsoRecordField.FieldType.*;


/**
 * This class describes a field inside a calypso record.
 * A Calypso Record is basically an array of fields.
 */
public class CalypsoRecordField {

    enum FieldType {
        Bitmap, Pointer, Date, Time, DateTime, Amount, Number, NetworkId, BcdDate, String, Repeat, Route, Stop, Vehicle, Direction, PayMethod, YesNo, Name, Gender, ContractStatus, ContractType, ContractTariff, ContractPointer, Profile, EventCode, CardStatus, Undefined;
        public static String[] n = {"Bitmap", "Pointer", "Date", "Time", "DateTime", "Amount", "Number", "NetworkId", "BcdDate", "String", "Repeat", "Route", "Stop", "Vehicle", "Direction", "PayMethod", "YesNo", "Name", "Gender", "ContractStatus", "ContractType", "ContractTariff", "ContractPointer", "Profile", "EventCode", "CardStatus", "Undefined"};
        public static int c= 0;
        private int cc= 0;
        FieldType(){ this.cc = FieldType.getC(); }
        static int getC(){ return c++;}
        public String getTypeName(){
            return n[this.cc];
        }
    }

    private static HashMap<Integer, String> payMethods;
    static{ payMethods = new HashMap<>();
            payMethods.put(128, "Débit PME");
            payMethods.put(144, "Espèce");
            payMethods.put(160, "Chèque Mobilités");
            payMethods.put(164, "Chèque");
            payMethods.put(165, "Chèque vacances");
            payMethods.put(179, "Carte de paiement");
            payMethods.put(183, "Télépaiement");
            payMethods.put(208, "Télérèglement");
            payMethods.put(215, "Bon de caisse");
            payMethods.put(217, "Bon de réduction");
    }

    private static HashMap<Integer, String> countryCodes;
    static{
        countryCodes = new HashMap<>();
        Document cc = CalypsoEnvironment.openDocument("CountryCodes.xml");
        if(cc != null) {
            if (!cc.getRootElement().getName().equals("countryCodes")) {
                System.out.println("CountryCodes.xml does not contain any country codes !");
            } else {
                for (Element e : cc.getRootElement().getChildren())
                    countryCodes.put(Integer.parseInt(e.getAttributeValue("id")), e.getAttributeValue("name"));
            }
        }
    }

    private static HashMap<Integer, String> contractStatuses;
    static{
        contractStatuses = new HashMap<>();
        contractStatuses.put(0,"Never validated");
        contractStatuses.put(1,"Used once");
        contractStatuses.put(2,"Validated");
        contractStatuses.put(3,"Renewment notification sent");
        contractStatuses.put(4,"Punched");
        contractStatuses.put(5,"Cancelled");
        contractStatuses.put(6,"Interrupted");
        contractStatuses.put(7,"Status OK");
        contractStatuses.put(13,"Not available for validation");
        contractStatuses.put(14,"Free entry");
        contractStatuses.put(15,"Active");
        contractStatuses.put(16,"Pre-allocated");
        contractStatuses.put(17,"Completed and to be removed");
        contractStatuses.put(18,"Completed and cannot be removed");
        contractStatuses.put(19,"Blocked");
        contractStatuses.put(20,"Data group encrypted flag");
        contractStatuses.put(21,"Data group anonymous flag");
        contractStatuses.put(33,"Pending");
        contractStatuses.put(63,"Suspended");
        contractStatuses.put(88,"Disabled");
        contractStatuses.put(125,"Suspended contract");
        contractStatuses.put(126,"Invalid");
        contractStatuses.put(127,"Invalid et reimbursed");
        contractStatuses.put(255,"Deletable");
    }

    private static String[] eventTypes = {"Non spécifié", "Entrée", "Sortie", "Contrôle", "Correspondance entrante", "Correspondance sortante", "Correspondance"};
    private static String[] vehicleTypes = {"Non spécifié", "Bus urbain", "Bus interrurbain", "Métro", "Tramway", "Train", "Parking"};
    private static String[] cardStatuses = {"Anonyme", "Declarative", "Personnalisée", "Codage spécifique"};

    private CalypsoEnvironment env;
    private CalypsoRecord parentRecord;
    private CalypsoRecordField parentField;
    private String description;
    private int length;
    private FieldType type;
    private ArrayList<CalypsoRecordField> subfields;
    private HashMap<String, CalypsoRecordField> subfieldsByName;
    private String convertedValue;
    private BitArray bits;
    private boolean filled;

    /**
     * Manually add a Record Field (without XML)
     * @param description field description
     * @param size Size in bits
     * @param type Data type of the field
     * @param env Current Calypso Environment (can be null)
     */
    public CalypsoRecordField(String description, int size, CalypsoRecordField.FieldType type, CalypsoRecordField parentField, CalypsoEnvironment env){
        this.description = description;
        this.length = size;
        this.subfields = new ArrayList<>();
        this.subfieldsByName= new HashMap<>();
        this.type = type;
        this.env = env;
        this.parentField = parentField;
    }

    // copy ctor
    public CalypsoRecordField (CalypsoRecordField f){
        this.filled = false;
        this.env = f.env;
        this.length = f.length;
        this.type = f.type;
        this.convertedValue = "";
        this.description = f.description;
        this.subfields = new ArrayList<>();
        this.subfieldsByName = new HashMap<>();
        for(CalypsoRecordField ff : f.subfields) {
            CalypsoRecordField subfield = new CalypsoRecordField(ff);
            subfield.setParentField(this);
            this.subfields.add(subfield);
            this.subfieldsByName.put(ff.getDescription(), subfield);
        }
    }

    public FieldType getFieldTypeFromString(String fieldType){
        String s = fieldType.toLowerCase();
        switch (s) {
            case "pointer":
                return Pointer;
            case "bitmap":
                return Bitmap;
            case "bcddate":
                return BcdDate;
            case "date":
                return Date;
            case "time":
                return Time;
            case "datetime":
                return DateTime;
            case "amount":
                return Amount;
            case "number":
                return Number;
            case "networkid":
                return NetworkId;
            case "string":
                return String;
            case "repeat":
                return Repeat;
            case "route":
                return Route;
            case "stop":
                return Stop;
            case "vehicle":
                return Vehicle;
            case "direction":
                return Direction;
            case "yesno":
                return YesNo;
            case "name":
                return Name;
            case "gender":
                return Gender;
            case "contractstatus":
                return ContractStatus;
            case "contracttype":
                return ContractType;
            case "contracttariff":
                return ContractTariff;
            case "contractpointer":
                return ContractPointer;
            case "profile":
                return Profile;
            case "eventcode":
                return EventCode;
            case "cardstatus":
                return CardStatus;
            default:
                return Undefined;
        }
    }

    /**
     * Creates a Record field using an XML node.
     * @param e XML DOM node
     * @param env Current Calypso Environment (can be null)
     */
    public CalypsoRecordField(Element e, CalypsoEnvironment env){
        this.description = e.getAttributeValue("description");
        this.type = this.getFieldTypeFromString(e.getAttributeValue("type"));
        this.length = Integer.parseInt(e.getAttributeValue("length"));
        this.subfields = new ArrayList<>();
        this.subfieldsByName = new HashMap<>();
        this.env = env;
        for(Element ee : e.getChildren()) {
            CalypsoRecordField subfield = new CalypsoRecordField(ee, env);
            this.subfields.add(subfield);
            this.subfieldsByName.put(subfield.getDescription(), new CalypsoRecordField(ee, env));
        }
    }

    /**
     * Fills the current record field using a bit buffer from the file read.
     * @param buffer Array of bits
     * @param offset Offset in bits
     * @return amount of bits consumed
     * @throws IndexOutOfBoundsException
     */
    public int fill(byte[] buffer, int offset) throws IndexOutOfBoundsException{
        if(this.length == 0)
            return 0;

        this.bits = new BitArray(buffer, offset, this.length);
        if(this.bits.isNull()) // if bitmap is empty, do nothing more
            return this.length;

        int consumed = this.length;
        this.filled = true;

        switch (this.type) {
            case Bitmap:
                this.convertedValue = this.bits.toString();
                for (int i = 0; i < this.length; i++)
                    if (this.bits.get(i))
                        consumed += this.subfields.get(i).fill(buffer, offset + consumed);
                break;
            case Repeat:
                int count = this.bits.getInt();
                this.convertedValue = "" + count;

                for (int i = 0; i < count - 1; i++) // on ajoute les répétitions des champs.
                    this.subfields.add(new CalypsoRecordField(this.subfields.get(0)));

                for (int i = 0; i < count; i++)
                    consumed += this.subfields.get(i).fill(buffer, offset + consumed);
                break;
            case String:
                this.convertedValue = new String(this.bits.getChars());
                break;
            case Pointer:
                break;
            case Date:
                int timestamp = 852073200 + this.bits.getInt() * 24 * 3600; // or maybe 852091200 ?
                java.util.Date date = new Date((long) timestamp * 1000);
                this.convertedValue = new SimpleDateFormat("dd/MM/yyyy").format(date);
                break;
            case Time:
                int minutes = this.bits.getInt();
                this.convertedValue = ""+(minutes/60)%24+":"+minutes%60;
                break;
            case BcdDate:
                assert (this.length == 32);
                StringBuilder sb = new StringBuilder();
                String rawDate = this.bits.toHex();
                sb.append(rawDate.substring(9, 11));
                sb.append('/');
                sb.append(rawDate.substring(6, 8));
                sb.append('/');
                sb.append(rawDate.substring(0, 2));
                sb.append(rawDate.substring(3, 5));
                this.convertedValue = sb.toString();
                break;
            case PayMethod:
                int methNo = this.bits.getInt();
                if (payMethods.containsKey(methNo))
                    this.convertedValue = payMethods.get(methNo);
                else
                    this.convertedValue = "Méthode de paiement inconnue !";
                break;
            case DateTime:
                break;
            case Amount:
                break;
            case Number:
                if (this.length < 8) { // < et non <= car types signés.
                    this.convertedValue = "" +  (int) this.bits.getBytes()[0];
                } else if (this.length < 32) {
                    this.convertedValue = "" + (int) this.bits.getChars()[0];
                } else if (this.length < 64) {
                    this.convertedValue = "" + this.bits.getLong();
                } else {
                    System.out.println("INTEGER TOO BIG !");
                    this.convertedValue = "XXXXXXXXXXXX";
                }
                break;
            case NetworkId:
                // Network ID might be BCD encoded in 2000/2001 Env>EnvNetworkId
                String net = this.bits.toHex();
                net = net.replace(" ", "");
                int country = Integer.parseInt(net.substring(0, 3));
                if(env != null && country != this.env.getCountryId() && country != 0)
                    System.out.println("The environment configuration does not suit this card ! Wrong country id.");
                int netId = Integer.parseInt(net.substring(4, 6));
                if(env != null && netId != this.env.getNetworkId() && netId != 0)
                    System.out.println("The environment configuration does not suit this card ! Wrong network id.");

                this.convertedValue = (countryCodes.containsKey(country) ? countryCodes.get(country) : ""+country) + " - " + netId;
                break;
            case Stop:
                if(this.env != null && this.env.isTopologyConfigured())
                    this.convertedValue = this.env.getStopName(this.bits.getInt());
                else
                    this.convertedValue = this.bits.toHex();
                break;
            case Route:
                if(this.env != null && this.env.isTopologyConfigured())
                    this.convertedValue = this.env.getRouteName(this.bits.getInt()).replace("/--/", "<>");
                else
                    this.convertedValue = this.bits.toHex();
                break;
            case YesNo:
                this.convertedValue = this.bits.getInt() == 0 ? "Yes":"No";
                break;
            case Name:
                StringBuilder nameSB = new StringBuilder();
                for(int i = 0;i<bits.getSize();i+=5){
                    int car = bits.get(i, 5).getInt();
                    switch(car){
                        case 0x1B:
                            nameSB.append(' ');
                            break;
                        case 0x1C:
                            nameSB.append('´');
                            break;
                        case 0x1D:
                            nameSB.append('`');
                            break;
                        case 0x1E:
                            nameSB.append('^');
                            break;
                        case 0x1F:
                            nameSB.append('\'');
                            break;
                        case 0x00:
                            nameSB.append('-');
                            break;
                        default:
                            nameSB.append('A' + car);
                            break;
                    }
                    this.convertedValue = nameSB.toString();
                }
                this.convertedValue = this.bits.getInt() == 0 ? "Yes":"No";
                break;
            case Gender:
                switch(bits.getInt()){
                    case 1:
                        this.convertedValue = "M.";
                        break;
                    case 2:
                        this.convertedValue = "Mme.";
                        break;
                    case 3:
                        this.convertedValue = "Réservé";
                        break;
                    default:
                        this.convertedValue = "Inconnu";
                }
                break;
            case ContractStatus:
                int cStatus = bits.getInt();
                if(contractStatuses.containsKey(cStatus))
                    this.convertedValue = contractStatuses.get(bits.getInt());
                else
                    this.convertedValue = ""+bits.getInt();
                break;
            case ContractType:
                this.convertedValue = null;
                if(env.areFaresConfigured())
                    this.convertedValue = this.env.getFareName(bits.getInt());
                if(this.convertedValue == null)
                    this.convertedValue = bits.toHex();
                break;
            case ContractTariff:
                int exploitant = bits.get( 0, 4).getInt();
                int type       = bits.get( 4, 8).getInt();
                int priorite   = bits.get(12, 4).getInt();
                this.convertedValue = "" + exploitant + " " + Integer.toHexString(type) + "h " + priorite;
                break;
            case ContractPointer:
                int pointer = bits.getInt();
                this.env.contractPointers.add(pointer);
                Collections.sort(this.env.contractPointers);
                this.convertedValue = ""+Integer.toHexString(pointer);

                CalypsoRecordField tariff = this.getParentField().getSubfield("BestContractsTariff");
                if(tariff != null) {
                    String mappingName = tariff.getConvertedValue().split(" ")[1];
                    CalypsoFile contracts = this.env.getFile("Contracts");
                    contracts.addFileMapping(pointer, mappingName);
                    this.convertedValue = Integer.toHexString(pointer) + " -> " + mappingName;
                }
                break;
            case Vehicle:
                switch(bits.getInt()){
                    case 0:
                        this.convertedValue = "unspecified";
                        break;
                    case 1:
                        this.convertedValue = "bus urbain";
                        break;
                    case 2:
                        this.convertedValue = "bus interurbain";
                        break;
                    case 3:
                        this.convertedValue = "metro";
                        break;
                    case 4:
                        this.convertedValue = "tramway";
                        break;
                    case 5:
                        this.convertedValue = "train";
                        break;
                    case 6:
                        this.convertedValue = "TGV";
                        break;
                    case 7:
                        this.convertedValue = "busTrain";
                        break;
                    default:
                        this.convertedValue = "unknown";
                }
                break;
            case Profile:
                if(this.env.areProfilesConfigured())
                    this.convertedValue = this.env.getProfileName(bits.getInt());
                else
                    this.convertedValue = ""+bits.getInt();
                break;
            case EventCode:
                int vehicleType = (bits.getInt() >> 4) & 0xf;
                int eventType = bits.getInt() & 0xf;
                this.convertedValue = "";
                if(eventType < eventTypes.length)
                    this.convertedValue = eventTypes[eventType];
                this.convertedValue += ":";
                if(eventType < vehicleTypes.length)
                    this.convertedValue += vehicleTypes[vehicleType];
                break;
            case CardStatus:
                int status = bits.getInt();
                if(status < cardStatuses.length)
                    this.convertedValue = cardStatuses[status];
                else
                    this.convertedValue = bits.toHex() + "h ???";
                break;
            case Undefined:
            default:
                this.convertedValue = this.bits.toHex();
                break;
        }
        return consumed;
    }

    public BitArray getBits(){
        return this.bits;
    }

    public static String nesting(int level){
        StringBuilder r = new StringBuilder();
        for (int j = 0; j < level; j++)
            r.append("    ");
        return r.toString();
    }

    /**
     * Prints the contents of the record field
     * @param n Indent level
     */
    public void print(int n, boolean debug){
        if(!this.filled)
            return;
        System.out.println(nesting(n)+"> "+this.description);
        if(debug) {
            System.out.println(nesting(n) + "   - Type : " + this.type.getTypeName());
            System.out.println(nesting(n) + "   - Size : " + this.length);
        }
        if(this.type != Bitmap && this.type != Repeat)
            System.out.println(nesting(n) + "   - Converted value : " + this.convertedValue);
        if(this.type == Bitmap || this.type == Repeat) {
            System.out.println(nesting(n) + "   - Subfields :");
            for (CalypsoRecordField f : this.subfields)
                f.print(n+1, debug);
        }
    }
    public void print(){
        this.print(0, true);
    }
    public java.lang.String getConvertedValue() {
        return convertedValue;
    }
    public void setParentRecord(CalypsoRecord r){
        this.parentRecord = r;
        for(CalypsoRecordField sub : this.subfields)
            sub.setParentRecord(r);
    }
    public java.lang.String getDescription() {
        return description;
    }
    public ArrayList<CalypsoRecordField> getSubfields(){
        return this.subfields;
    }
    public CalypsoRecordField getSubfield(String description){
        return this.subfieldsByName.containsKey(description) ? this.subfieldsByName.get(description) : null;
    }
    public void setParentField(CalypsoRecordField field){
        this.parentField = field;
    }
    public CalypsoRecordField getParentField(){
        return this.parentField;
    }
    public CalypsoRecord getParentRecord(){
        return this.parentRecord;
    }
    public boolean isFilled(){
        return this.filled;
    }
}


<|start_filename|>src/fr/mikado/calypsofilefinder/Main.java<|end_filename|>
package fr.mikado.calypsofilefinder;

import fr.mikado.calypso.BitArray;
import fr.mikado.calypso.CalypsoEnvironment;
import fr.mikado.isodep.CardException;
import fr.mikado.isodep.CommandAPDU;
import fr.mikado.isodep.IsoDepInterface;
import fr.mikado.isodepimpl.IsoDepImpl;
import org.jdom2.Comment;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;


public class Main {

    public static void main(String[] args) throws Exception {

        Scanner s = new Scanner(System.in);

        System.out.println("\nCalypso File Finder\n");
        System.out.println("1. Find all files, save output to fileList (take some time)");
        System.out.println("2. Find in files using fileList.xml");

        boolean invalidChoice;
        do {
            invalidChoice = false;
            System.out.print("\nChoice : ");
            int choice = Integer.parseInt(s.nextLine());
            switch (choice) {
                case 1:
                    findAllFiles(IsoDepImpl.getDefaultCard());
                    break;
                case 2:
                    System.out.print("Bit pattern (must be a long as hex) : 0x");
                    long pattern = Long.parseLong(s.nextLine(), 16);
                    int patternSize;
                    boolean firstAttempt = true;
                    do {
                        if(!firstAttempt)
                            System.out.println("Invalid pattern size (must be at least 2 bits");
                        System.out.print("\nBit pattern size : ");
                        patternSize = Integer.parseInt(s.nextLine());
                        firstAttempt = false;
                    }while(patternSize <= 1);

                    CalypsoEnvironment cardEnv = new CalypsoEnvironment();
                    cardEnv.setCardStructure("fileList.xml");
                    CalypsoCardSearch search = new CalypsoCardSearch(IsoDepImpl.getDefaultCard(), cardEnv, new BitArray(pattern, patternSize));
                    search.search();
                    search.dumpResults();
                    break;
                default:
                    System.out.println("Invalid input");
                    invalidChoice = true;
            }
        }while(invalidChoice);
    }

    public static void findAllFiles(IsoDepInterface c) throws CardException, IOException {
        Element root = new Element("calypsoEnvironment");
        Element card = new Element("card");
        root.addContent(card);
        for(int i =0;i<0xffff;i++)
            if(selectFile(c, i)) {
                System.out.println("\nFound file : " + i);
                Element file = new Element("file");
                file.setAttribute("identifier", ""+i);
                file.setAttribute("description", "unknown");
                file.setAttribute("type", "EF");
                card.addContent(file);
            }
            else if(i%100 == 0)
                System.out.println("\n"+i);
            else
                System.out.print(".");

        Document doc = new Document();
        doc.addContent(new Comment("If you are happy with those results, the rename this file as fileList.xml, and rerun (option 2) to search known values on the card."));
        doc.addContent(new Comment("Tweak this file so the structure doesn't generate any errors (no DF selected, security status...)"));
        doc.setRootElement(root);
        XMLOutputter outputter = new XMLOutputter();
        outputter.setFormat(Format.getPrettyFormat());
        outputter.output(doc, new FileWriter(new File("fileList.out.xml")));
    }

    public static boolean selectFile(IsoDepInterface c, int id) throws CardException {

        CommandAPDU cAPDU = new CommandAPDU(new byte[]{(byte) 0x94, (byte) 0xA4, 0x00, 0x00, 0x02, (byte) ((id>>8)&0xff), (byte) (id&0xff), 0x00});

        return c.transmit(cAPDU).getSW() == 0x9000;
    }
}























<|start_filename|>calypso/src/fr/mikado/isodep/ResponseAPDU.java<|end_filename|>
package fr.mikado.isodep;

import java.util.Arrays;

/**
 * Created by alexis on 05/04/17.
 */
public class ResponseAPDU {
    private final byte[] apdu;

    public ResponseAPDU(byte[] rAdpu){
        this.apdu = rAdpu;
    }

    public String toString(){
        return "ResponseAPDU: " + this.apdu.length + " bytes, SW=" + Integer.toHexString(getSW());
    }

    public byte[] getBytes(){
        return this.apdu.clone();
    }

    public int getSW1(){
        return this.apdu[apdu.length - 2] & 0xff;
    }

    public int getSW2(){
        return this.apdu[apdu.length - 1] & 0xff;
    }

    public int getSW(){
        int apdulen = this.apdu.length;
        if(this.apdu.length<2)
            return -1;
        return (this.getSW1() << 8) | this.getSW2();
    }

    public byte[] getData(){
        if(this.apdu.length <= 2)
            return null;
        return Arrays.copyOfRange(this.apdu, 0, this.apdu.length-2);
    }
}


<|start_filename|>calypso/src/fr/mikado/calypso/CalypsoRawDump.java<|end_filename|>
package fr.mikado.calypso;

import fr.mikado.xmlio.XMLIOImpl;
import fr.mikado.xmlio.XMLIOInterface;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

import java.io.*;
import java.util.ArrayList;

import static fr.mikado.calypso.CalypsoFile.CalypsoFileType.EF;

public class CalypsoRawDump {

    public static class CalypsoFileDump implements Serializable {
        private String filename;
        private ArrayList<CalypsoFileDump> children;
        private ArrayList<byte[]> records;
        public CalypsoFileDump(){
            this.records = new ArrayList<>();
            this.children = new ArrayList<>();
        }
        public CalypsoFileDump(Element e){
            this.filename = e.getAttributeValue("filename");
            this.records = new ArrayList<>();
            this.children = new ArrayList<>();

            this.fromXMLElement(this, e);
        }
        public void fromXMLElement(CalypsoFileDump fd, Element e){
            this.filename = e.getAttributeValue("filename");
            if(!e.getChildren().isEmpty()) {
                for (Element re : e.getChildren("record"))
                    fd.records.add(BitArray.hex2Bytes(re.getValue()));
                for(Element fe : e.getChildren("file")) {
                    CalypsoFileDump child = new CalypsoFileDump();
                    child.fromXMLElement(child, fe);
                    fd.children.add(child);
                }
            }
        }
        private Element getXmlElement(){
            Element e = new Element("file");
            e.setAttribute("filename", this.filename);
            if(this.children.isEmpty()){ // add the record contents
                for(byte[] rec : this.records) {
                    Element recElem = new Element("record");
                    StringBuilder sb = new StringBuilder();
                    for(byte b : rec)
                        sb.append(String.format("%02x", b));
                    recElem.addContent(sb.toString());
                    e.addContent(recElem);
                }
            }else { // add the embedded files
                for (CalypsoFileDump fd : this.children)
                    e.addContent(fd.getXmlElement());
            }
            return e;
        }
        public String getFilename() {
            return filename;
        }
        public ArrayList<CalypsoFileDump> getChildren() {
            return children;
        }
        public ArrayList<byte[]> getRecords() {
            return records;
        }
    }

    protected CalypsoEnvironment env;
    protected ArrayList<CalypsoFileDump> dump;

    public CalypsoRawDump(CalypsoEnvironment env){
        this.env = env;
        getRawDump();
    }

    public CalypsoRawDump(XMLIOInterface xmlio, String filename) throws IOException {
        this.env = null;
        loadXML(xmlio, filename);
    }

    public CalypsoFileDump getFileDump(CalypsoFile f){
        CalypsoFileDump fd = new CalypsoFileDump();
        fd.filename = f.getDescription();
        if(f.getType() == EF)
            fd.records = f.getRawRecords();
        else
            for(CalypsoFile ff : f.getChildren())
                fd.children.add(getFileDump(ff));
        return fd;
    }

    public ArrayList<CalypsoFileDump> getRawDump(){
        this.dump = new ArrayList<>();

        for(CalypsoFile f : this.env.getFiles())
            this.dump.add(getFileDump(f));

        return this.dump;
    }

    public Document getXMLDump(){
        Element root = new Element("calypsoDump");

        ArrayList<CalypsoFileDump> dump = this.getRawDump();
        for(CalypsoFileDump fd : dump)
            root.addContent(fd.getXmlElement());
        return new Document(root);
    }

    public void writeXML(XMLIOInterface xmlio, String filename) throws IOException {
        xmlio.writeDocument(this.getXMLDump(), filename);
    }

    public void loadXML(XMLIOInterface xmlio, String filename) throws IOException {
        Document dump = xmlio.loadDocument(filename);
        Element root = dump.getRootElement();

        this.dump = new ArrayList<>();
        for(Element f : root.getChildren("file"))
            this.dump.add(new CalypsoFileDump(f));
    }

    private String indent(int level){
        StringBuilder sb = new StringBuilder();
        for(int i = 0;i<level;i++)
            sb.append(" ");
        return sb.toString();
    }

    private void debugPrintFile(CalypsoFileDump fd, int i){
        System.out.println(indent(i) + "File " + fd.filename + " :");
        for(byte[] bytes : fd.records)
            System.out.println(indent(i+4) + "Record : " + BitArray.bytes2Hex(bytes));
        for(CalypsoFileDump child : fd.children)
            debugPrintFile(child, i+4);
    }

    public void debugPrint(){
        for(CalypsoFileDump fd : this.dump)
            debugPrintFile(fd, 0);
    }

    public ArrayList<CalypsoFileDump> getFiles() {
        return dump;
    }
}







































<|start_filename|>calypso/src/fr/mikado/calypso/CalypsoFile.java<|end_filename|>
package fr.mikado.calypso;

import org.jdom2.Element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * This class describes a Calypso File.
 * A file has a structure (fields) which describes how each records for this file are structured.
 * CalypsoFile structures can (and should) be loaded from an XML file.
 */
public class CalypsoFile {
    public String getDescription() {
        return description;
    }

    public enum CalypsoFileType{
        MF, DF, EF
    }

    public class MappingTuple<X, Y> {
        public X recordId;
        public Y mappingName;
        public MappingTuple(X recordId, Y mappingName){
            this.recordId = recordId;
            this.mappingName = mappingName;
        }
    }

    private ArrayList<Integer> LFIs;
    private Integer SFI;
    private String description;
    private CalypsoFile parent;
    private ArrayList<CalypsoFile> children;
    private CalypsoFileType type;

    private HashMap<String, ArrayList<CalypsoRecordField>> fileMappings; // available mappings, by name
    private ArrayList<MappingTuple<Integer, String>> mappings; // mapping names for each record, card ordered is preserved
    private ArrayList<CalypsoRecordField> activeMapping; // mapping for the next record
    private ArrayList<CalypsoRecordField> defaultMapping;
    private ArrayList<CalypsoRecord> records;

    /**
     * Creates a Calypso File from an XML node.
     * @param e An XML node
     * @param env Current Calypso Environment
     */
    public CalypsoFile(Element e, CalypsoEnvironment env){
        this.children = new ArrayList<>();
        this.records = new ArrayList<>();
        this.LFIs = new ArrayList<>();

        this.fileMappings = new HashMap<>();
        this.mappings = new ArrayList<>();
        this.activeMapping = null;
        this.defaultMapping = null;

        String LFIStr = e.getAttributeValue("LFI");
        if(LFIStr != null)
            for(String LFIe : LFIStr.split(";"))
                    this.LFIs.add(Integer.parseInt(LFIe, 16));

        String SFIStr = e.getAttributeValue("SFI");
        if(SFIStr != null)
            this.SFI = Integer.parseInt(SFIStr, 16);

        this.description = e.getAttributeValue("description");
        this.type = (e.getAttributeValue("type").equals("DF") ? CalypsoFileType.DF : CalypsoFileType.EF);

        String mappingsAttr = e.getAttributeValue("multipleMappings");
        if(mappingsAttr != null && (mappingsAttr.equals("yes") || mappingsAttr.equals("true")))
            for(Element ee : e.getChildren()) {
                ArrayList<CalypsoRecordField> mapping = parseFileStructure(ee, env);
                this.fileMappings.put(ee.getAttributeValue("name"), mapping);
                String isDefaultMappingStr = ee.getAttributeValue("default");
                if (isDefaultMappingStr != null && (isDefaultMappingStr.equals("yes") || isDefaultMappingStr.equals("true"))) {
                    this.activeMapping = mapping;
                    this.defaultMapping = mapping;
                }
            }
        else
            if (this.type == CalypsoFileType.EF)
                this.activeMapping = parseFileStructure(e, env);
    }

    private ArrayList<CalypsoRecordField> parseFileStructure(Element e, CalypsoEnvironment env){
        ArrayList<CalypsoRecordField> fields = new ArrayList<>();
        for (Element ee : e.getChildren())
            fields.add(new CalypsoRecordField(ee, env));
        return fields;
    }

    public String getFullPath(){
        if(this.parent != null)
            return this.parent.getFullPath() + "/" + this.getIdentifier();
        return this.getIdentifier();
    }

    public void addChild(CalypsoFile cf){
        this.children.add(cf);
        cf.setParent(this);
    }

    public void setParent(CalypsoFile f){
        this.parent = f;
    }

    /**
     * Creates a new record for this file, from the byte buffer received in the READ RECORD APDU response.
     * @param buffer Byte buffer in the READ RECORD APDU response.
     */
    public void newRecord(byte[] buffer){
        String activeMappingName = "";
        if(this.mappings.size() > 0)
            if(this.mappings.size() > this.records.size()){
                // pour HashMap<Integer, String> : activeMappingName = this.mappings.get(new ArrayList<>(this.mappings.keySet()).get(this.records.size()));
                MappingTuple<Integer, String> mapping = this.mappings.get(this.records.size());
                activeMappingName = mapping.mappingName;

                this.activeMapping = this.fileMappings.get(activeMappingName);
            }
        if(this.activeMapping == null){
            System.out.println("Oops.");
        }

        CalypsoRecord r = new CalypsoRecord(this.activeMapping, this);
        r.fillRecord(buffer);
        r.setMappingName(activeMappingName);
        if(!r.isEmpty())
            this.records.add(r);
    }

    public void deleteRecords(){
        this.records = new ArrayList<>();
        for(CalypsoFile f : this.children)
            f.deleteRecords();
    }

    public ArrayList<byte[]> getRawRecords(){
        if(this.type != CalypsoFileType.EF)
            return null;
        ArrayList<byte[]> records = new ArrayList<>();

        for(CalypsoRecord r : this.records)
            records.add(r.getBits().getBytes());

        return records;
    }

    public static String nesting(int level){
        StringBuilder r = new StringBuilder();
        for (int j = 0; j < level; j++)
            r.append("    ");
        return r.toString();
    }

    public void dumpStructure(int n){
        System.out.println(nesting(n)+"File " + this.getFullPath() + " : " + this.description);
        if(this.type == CalypsoFileType.EF)
            for(Map.Entry<String, ArrayList<CalypsoRecordField>> mapping : this.fileMappings.entrySet()) {
                System.out.println(nesting(n+1) + "File mapping \"" + mapping.getKey() + "\" :");
                for (CalypsoRecordField f : mapping.getValue())
                    f.print(n + 2, true);
            }
        else
            for(CalypsoFile c : this.children)
                c.dumpStructure(n+1);
    }

    public void dump(int n, boolean debug){
        System.out.println(nesting(n)+"File " + this.getFullPath() + " " + this.description + " :");
        if(this.type == CalypsoFileType.DF || this.type == CalypsoFileType.MF)
            for (CalypsoFile c : this.children)
                c.dump(n+1, debug);
        else
            if(this.records.size() == 0)
                System.out.println(nesting(n+1)+"(empty)");
            else
                for(int i =0;i<this.records.size();i++){
                    System.out.println(nesting(n+1)+"Record #"+i);
                    this.records.get(i).print(n+1, debug);
                }
    }

    public String getIdentifier(){
        if(this.LFIs.size() > 0) {
            StringBuilder sb = new StringBuilder();
            boolean firstInLine = true;
            for(Integer LFI : this.LFIs) {
                sb.append( (firstInLine ? "":",") + Integer.toHexString(LFI));
                firstInLine = false;
            }
            return sb.toString();
        }
        if(this.SFI != null)
            return Integer.toHexString(this.SFI);
        return "NO_ID";
    }
    public boolean isSFIAddressable(){
        return this.SFI != null;
    }
    public Integer getSFI(){
        return this.SFI;
    }
    public ArrayList<Integer> getLFIs(){
        return this.LFIs;
    }
    public CalypsoFileType getType(){
        return this.type;
    }
    public ArrayList<CalypsoFile> getChildren(){
        return this.children;
    }
    public ArrayList<CalypsoRecord> getRecords() {
        return records;
    }

    public boolean addFileMapping(int index, String mappingName){
        ArrayList<CalypsoRecordField> mapping = this.fileMappings.get(mappingName);
        if(mapping == null)
            return false;
        if(defaultMapping != null) {
            this.mappings.clear();
            defaultMapping = null;
        }
        this.mappings.add(new MappingTuple<>(index, mappingName));
        return true;
    }
}

































<|start_filename|>src/fr/mikado/calypsoinspector/Main.java<|end_filename|>
package fr.mikado.calypsoinspector;

import fr.mikado.calypso.CalypsoCard;
import fr.mikado.calypso.CalypsoEnvironment;
import fr.mikado.calypso.CalypsoRawDump;
import fr.mikado.isodepimpl.IsoDepImpl;
import fr.mikado.xmlio.XMLIOImpl;

import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.TerminalFactory;
import java.io.IOException;

public class Main {

    public static void main(String[] args) throws Exception {

        CalypsoEnvironment env = new CalypsoEnvironment("Transpole");

        CalypsoCard passPass = new CalypsoCard(IsoDepImpl.getDefaultCard(), env);
        passPass.read();
        passPass.disconnect();
        passPass.dump(false);
        CalypsoDump.dumpProfiles(env);
        CalypsoDump.dumpContracts(env);
        CalypsoDump.dumpTrips(env);

        // save the card contents to a raw dump file :
        new CalypsoRawDump(env).writeXML(new XMLIOImpl(), "rawDumpTest.xml");

        // say you want to load a card dump :
        // loadDump()
    }

    public static void loadDump() throws Exception {
        CalypsoRawDump rawImport = new CalypsoRawDump(new XMLIOImpl(), "rawDumpTest.xml");
        CalypsoEnvironment envImport = new CalypsoEnvironment("Transpole");
        envImport.loadDump(rawImport);

        // now print the contents :
        CalypsoDump.dumpProfiles(envImport);
        CalypsoDump.dumpContracts(envImport);
        CalypsoDump.dumpTrips(envImport);
    }
}


<|start_filename|>calypso/src/fr/mikado/xmlio/XMLIOInterface.java<|end_filename|>
package fr.mikado.xmlio;

import org.jdom2.Document;

import java.io.IOException;

public interface XMLIOInterface {
    public Document loadDocument(String filename) throws IOException;
    public boolean writeDocument(Document doc, String filename) throws IOException;
}
