diff --git a/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java b/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java index 78ddeb7..a046505 100755 --- a/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java +++ b/app/src/main/java/org/dslul/ticketreader/ChipOnPaper.java @@ -1,121 +1,119 @@ package org.dslul.ticketreader; -import android.util.Log; - import org.dslul.ticketreader.util.GttDate; -import org.dslul.ticketreader.util.HelperFunctions; -import java.nio.ByteBuffer; import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; import static org.dslul.ticketreader.util.GttDate.addMinutesToDate; import static org.dslul.ticketreader.util.HelperFunctions.getBytesFromPage; public class ChipOnPaper { private Date date; private int type; private long remainingMins; private int remainingRides; ChipOnPaper(List dumplist) { dumplist = dumplist; type = (int)getBytesFromPage(dumplist.get(5), 2, 2); long minutes = getBytesFromPage(dumplist.get(10), 0, 3); if(type == 9521) minutes = getBytesFromPage(dumplist.get(12), 0, 3); date = addMinutesToDate(minutes, GttDate.getGttEpoch()); //calcola minuti rimanenti Calendar c = Calendar.getInstance(); long diff = (c.getTime().getTime() - date.getTime()) / 60000; long maxtime = 90; //city 100 if(type == 302 || type == 304) { maxtime = 100; } - //daily - if(type == 303 || type == 305) { - maxtime = GttDate.getMinutesUntilMidnight(); - } + //Tour TODO: make a distinction between the two types if(type == 704) { maxtime = 2*24*60; } - if(diff >= maxtime) { + + //daily + if(type == 303 || type == 305) { + remainingMins = GttDate.getMinutesUntilEndOfService(date); + } + else if(diff >= maxtime) { remainingMins = 0; } else { remainingMins = maxtime - diff; } //calcola le corse rimanenti //TODO: corse in metropolitana (forse bit piĆ¹ significativo pag. 3) int tickets; if(type == 300) { //extraurbano tickets = (int) (~getBytesFromPage(dumplist.get(3), 0, 4)); } else { tickets = (int)(~getBytesFromPage(dumplist.get(3), 2, 2)) & 0xFFFF; } remainingRides = Integer.bitCount(tickets); } public String getTypeName() { //http://www.gtt.to.it/cms/biglietti-abbonamenti/biglietti/biglietti-carnet switch (type) { case 302: case 304: return "City 100"; case 303: case 305: return "Daily"; case 704: return "Tour"; case 301: return "Multicorsa extraurbano"; case 702: case 706: return "Carnet 5 corse"; case 701: case 705: return "Carnet 15 corse"; case 300: return "Extraurbano"; case 9521: return "Sadem Aeroporto Torino"; default: return "Non riconosciuto"; } } public String getDate() { return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT) .format(date); } public int getRemainingRides() { return remainingRides; } public long getRemainingMinutes() { - return remainingMins; + if(remainingMins < 0) + return 0; + else + return remainingMins; } } diff --git a/app/src/main/java/org/dslul/ticketreader/SmartCard.java b/app/src/main/java/org/dslul/ticketreader/SmartCard.java index 8822354..50c713e 100644 --- a/app/src/main/java/org/dslul/ticketreader/SmartCard.java +++ b/app/src/main/java/org/dslul/ticketreader/SmartCard.java @@ -1,334 +1,372 @@ package org.dslul.ticketreader; import android.util.Log; import org.dslul.ticketreader.util.GttDate; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import static java.lang.Math.abs; import static org.dslul.ticketreader.util.HelperFunctions.getBytesFromPage; public class SmartCard { public enum Type { BIP, PYOU, EDISU } static final Map subscriptionCodes = new HashMap() {{ put(68, "Mensile UNDER 26"); put(72, "Mensile Studenti Rete Urbana"); put(101, "Settimanale Formula 1"); put(102, "Settimanale Formula 2"); put(103, "Settimanale Formula 3"); put(104, "Settimanale Formula 4"); put(105, "Settimanale Formula 5"); put(106, "Settimanale Formula 6"); put(107, "Settimanale Formula 7"); put(108, "Settimanale Intera Area Formula"); put(109, "Settimanale Personale Rete Urbana"); put(199, "Mensile Personale Rete Urbana (Formula U)"); put(201, "Mensile Formula 1"); put(202, "Mensile Formula 2"); put(203, "Mensile Formula 3"); put(204, "Mensile Formula 4"); put(205, "Mensile Formula 5"); put(206, "Mensile Formula 6"); put(207, "Mensile Formula 7"); put(208, "Mensile Intera Area Formula"); put(261, "Mensile Studenti Urbano+Suburbano"); put(290, "Mensile 65+ Urbano Orario Ridotto"); put(291, "Mensile 65+ Urbano"); put(307, "Annuale Ivrea Rete Urbana e Dintorni"); put(308, "Annuale Extraurbano O/D"); put(310, "Plurimensile Studenti Extraurbano O/D"); put(721, "Annuale UNDER 26"); put(722, "Annuale UNDER 26 Fascia A"); put(723, "Annuale UNDER 26 Fascia B"); put(724, "Annuale UNDER 26 Fascia C"); put(730, "Mensile urbano Over 65"); put(761, "Annuale Over A"); put(731, "Annuale Over B"); put(732, "Annuale Over C"); put(733, "Annuale Over D"); put(911, "10 Mesi Studenti"); put(912, "Annuale Studenti"); put(990, "Junior"); put(993, "Annuale Formula U"); put(4001, "Settimanale Formula 4"); + put(4002, "Mensile Formula 3 U+A"); put(4003, "Annuale Formula U a Zone"); }}; static final Map ticketCodes = new HashMap() {{ put(712, "Ordinario Urbano"); put(714, "City 100"); put(715, "Daily"); put(716, "Multidaily"); }}; private class Contract { private int code; + private int counters; private boolean isValid; private boolean isTicket; private boolean isSubscription; private Date startDate; private Date endDate; - public Contract(byte[] data) { + public Contract(byte[] data, int counters) { int company = data[0]; + this.counters = counters; //get contract type code = ((data[4] & 0xff) << 8) | data[5] & 0xff; //support for GTT S.p.A. tickets only for now if(code == 0 || company != 1) { isValid = false; } else { isValid = true; } if(ticketCodes.containsKey(code)) { isTicket = true; isSubscription = false; } else if(subscriptionCodes.containsKey(code)) { isTicket = false; isSubscription = true; } else { isTicket = false; isSubscription = false; } long minutes = ~(data[9] << 16 & 0xff0000 | data[10] << 8 & 0xff00 | data[11] & 0xff) & 0xffffff; startDate = GttDate.decode(minutes); minutes = ~(data[12] << 16 & 0xff0000 | data[13] << 8 & 0xff00 | data[14] & 0xff) & 0xffffff; endDate = GttDate.decode(minutes); } public int getCode() { return code; } public Date getStartDate() { return startDate; } + public int getRides() { + if(code == 712 || code == 714) + return (((counters & 0x0000ff)&0x78) >> 3); + else + return (counters >> 19); + } + public Date getEndDate() { return endDate; } public boolean isContract() { return isValid; } public boolean isSubscription() { if(isSubscription) return true; else return false; } public boolean isTicket() { if(isTicket) return true; else return false; } public String getTypeName() { if(isTicket) return ticketCodes.get(code); else if(isSubscription) return subscriptionCodes.get(code); else return "Sconosciuto"; } } - - - private byte[] efEnvironment; - private Date validationDate; private Date creationDate; private Type type; private List tickets = new ArrayList<>(); private List subscriptions = new ArrayList<>(); private Contract subscription; private int ridesLeft = 0; private long remainingMins; SmartCard(List dumplist) { - efEnvironment = dumplist.get(1); + byte[] selectApplication = dumplist.get(0); + byte[] efEnvironment = dumplist.get(1); byte[] efContractList = dumplist.get(2); byte[] efEventLogs1 = dumplist.get(11); byte[] efEventLogs2 = dumplist.get(12); byte[] efEventLogs3 = dumplist.get(13); - byte[] validations = dumplist.get(14); - - byte[] minutes = new byte[3]; - System.arraycopy(efEnvironment, 9, minutes, 0, 3); - creationDate = GttDate.decode(minutes); + byte[] efCounters = dumplist.get(14); if(efEnvironment[28] == (byte)0xC0) type = Type.BIP; else if(efEnvironment[28] == (byte)0xC1) type = Type.PYOU; else if(efEnvironment[28] == (byte)0xC2) type = Type.EDISU; + byte[] minutes = new byte[3]; + System.arraycopy(efEnvironment, 9, minutes, 0, 3); + creationDate = GttDate.decode(minutes); + + //scan contractlist for tickets and subscriptions + for(int i = 1; i < 23; i+=3) { + //only GTT tickets atm + if(efContractList[i] == 1) { + //check validity + if((efContractList[i+1]&0x0f) == 1) { + //position in counters + int cpos = ((abs(efContractList[i+2]&0xff) >> 4)-1)*3; + int counter = 0; + if(cpos >= 0) + counter = (efCounters[cpos+2] & 0xff) | ((efCounters[cpos+1] & 0xff) << 8) + | ((efCounters[cpos] & 0xff) << 16); + Log.d("card", String.valueOf(counter >> 19)); + Contract contract = new Contract(dumplist.get(i/3+1 + 2), counter); + if(contract.isContract()) { + if(contract.isSubscription()) { + subscriptions.add(contract); + } + if(contract.isTicket()) { + tickets.add(contract); + ridesLeft += contract.getRides(); + } + } + } + } + } + + /* for (int i = 0; i < 8; i++) { Contract contract = new Contract(dumplist.get(i+3)); if(contract.isContract()) { if(contract.isSubscription()) { subscriptions.add(contract); } if(contract.isTicket()) { tickets.add(contract); } } - } + */ //get a valid subscription, if there's any Date latestExpireDate = GttDate.getGttEpoch(); for (Contract sub : subscriptions) { if (latestExpireDate.before(sub.getEndDate())) { latestExpireDate = sub.getEndDate(); subscription = sub; } } //actual tickets count - ridesLeft = countTickets(validations, efContractList); + //ridesLeft = countTickets(efCounters, efContractList); //get last validation time long mins = getBytesFromPage(efEventLogs1, 20, 3); if(mins == 0) mins = getBytesFromPage(efEventLogs2, 20, 3); if(mins == 0) mins = getBytesFromPage(efEventLogs3, 20, 3); validationDate = GttDate.addMinutesToDate(mins, GttDate.getGttEpoch()); Calendar c = Calendar.getInstance(); long diff = (c.getTime().getTime() - validationDate.getTime()) / 60000; int num = (int)(getBytesFromPage(efEventLogs1, 25, 1) >> 4); int tickettype = (int)getBytesFromPage(dumplist.get(num+2), 4, 2); long maxtime = 90; //city 100 if(tickettype == 714) { maxtime = 100; } //daily if(tickettype == 715 || tickettype == 716) { - maxtime = GttDate.getMinutesUntilMidnight(); + remainingMins = GttDate.getMinutesUntilEndOfService(validationDate); } - if(diff >= maxtime) { + else if(diff >= maxtime) { remainingMins = 0; } else { remainingMins = maxtime - diff; } } private int countTickets(byte[] validations, byte[] contractsList) { int count = 0; for (int i = 2; i < 24; i+=3) { int valpos = abs(contractsList[i+1]) >> 4; //position in contractslist int pos = i/3 + 1; //check if it's a subscription int sub = abs(contractsList[i]&0xf0) >> 4; if(pos > 0 && pos <= 8 && (contractsList[i]&0x0f) != 0 && sub < 0xA) { int rides = (abs(validations[valpos*3-3] & 0xff) >> 3); count += rides; } } return count; } public String getTicketName() { if(hasTickets()) return type + " - " + tickets.get(0).getTypeName(); else return type.toString(); } public String getSubscriptionName() { if(hasSubscriptions()) return type + " - " + subscription.getTypeName(); else return type.toString(); } public boolean hasTickets() { return ridesLeft != 0; } public boolean hasSubscriptions() { return subscriptions.size() != 0; } public String getExpireDate() { return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT) .format(subscription.getEndDate()); } public String getValidationDate() { return DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT) .format(validationDate); } public boolean isSubscriptionExpired() { Calendar c = Calendar.getInstance(); return c.getTime().after(subscription.getEndDate()); } private boolean isExpired(Date date) { Calendar c = Calendar.getInstance(); return c.getTime().after(date); } public int getRemainingRides() { return ridesLeft; } public long getRemainingMinutes() { - return remainingMins; + if(remainingMins < 0) + return 0; + else + return remainingMins; } } diff --git a/app/src/main/java/org/dslul/ticketreader/util/GttDate.java b/app/src/main/java/org/dslul/ticketreader/util/GttDate.java index 252fc08..f730b9c 100644 --- a/app/src/main/java/org/dslul/ticketreader/util/GttDate.java +++ b/app/src/main/java/org/dslul/ticketreader/util/GttDate.java @@ -1,59 +1,67 @@ package org.dslul.ticketreader.util; +import android.util.Log; + import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; public final class GttDate { public static Date decode(byte[] minutes) { return addMinutesToDate(byteArrayToLong(minutes), getGttEpoch()); } public static Date decode(long minutes) { return addMinutesToDate(minutes, getGttEpoch()); } public static Date getGttEpoch() { String startingDate = "05/01/01 00:00:00"; SimpleDateFormat format = new SimpleDateFormat("yy/MM/dd HH:mm:ss"); Date date = null; try { date = format.parse(startingDate); } catch (ParseException e) { e.printStackTrace(); } return date; } - public static long getMinutesUntilMidnight() { - Calendar c = Calendar.getInstance(); - c.add(Calendar.DAY_OF_MONTH, 1); - c.set(Calendar.HOUR_OF_DAY, 0); - c.set(Calendar.MINUTE, 0); - c.set(Calendar.SECOND, 0); - c.set(Calendar.MILLISECOND, 0); - return (int)(c.getTimeInMillis()-System.currentTimeMillis()/60000); + public static long getMinutesUntilEndOfService(Date startDate) { + Calendar curr = Calendar.getInstance(); + Calendar after = Calendar.getInstance(); + Calendar start = Calendar.getInstance(); + start.setTime(startDate); + int dayoff = 0; + if(start.get(Calendar.DAY_OF_MONTH) == curr.get(Calendar.DAY_OF_MONTH)) + dayoff = 1; + after.add(Calendar.DAY_OF_MONTH, dayoff); + after.set(Calendar.HOUR_OF_DAY, 3); + after.set(Calendar.MINUTE, 0); + after.set(Calendar.SECOND, 0); + after.set(Calendar.MILLISECOND, 0); + return (after.getTimeInMillis() - curr.getTimeInMillis())/60000; } public static Date addMinutesToDate(long minutes, Date beforeTime) { final long ONE_MINUTE_IN_MILLIS = 60000; long curTimeInMs = beforeTime.getTime(); return new Date(curTimeInMs + (minutes * ONE_MINUTE_IN_MILLIS)); } private static long byteArrayToLong(byte[] bytes) { long value = 0; for (int i = 0; i < bytes.length; i++) { value = (value << 8) + (bytes[i] & 0xff); } return value; } }