diff --git a/.gitignore b/.gitignore index 51359f7..f6892fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,89 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ *.iml -.gradle -/local.properties /.idea -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/*/build/ -/*/*.iml -/captures +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later .externalNativeBuild -.cxx +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof \ No newline at end of file diff --git a/keyple-parser/src/main/kotlin/org/eclipse/keyple/parser/utils/DateUtils.kt b/keyple-parser/src/main/kotlin/org/eclipse/keyple/parser/utils/DateUtils.kt index a90b01c..ed9afed 100644 --- a/keyple-parser/src/main/kotlin/org/eclipse/keyple/parser/utils/DateUtils.kt +++ b/keyple-parser/src/main/kotlin/org/eclipse/keyple/parser/utils/DateUtils.kt @@ -37,7 +37,7 @@ object DateUtils { const val DATE_01_01_1997 = "01/01/1997" const val DATE_01_01_2010 = "01/01/2010" const val DD_MM_YYYY = "dd/MM/yyyy" - const val DATE_FORMAT_DISPLAY = "dd/MM/yyyy, HH:mm" + const val DATE_FORMAT_DISPLAY = "dd MMMM yyyy, HH:mm" const val MILLIS_PER_DAY = 1000 * 60 * 60 * 24 const val MILLIS_PER_MINUTE = 60 * 24 diff --git a/validator-app/build.gradle b/validator-app/build.gradle index 08ae5b2..5721bcd 100644 --- a/validator-app/build.gradle +++ b/validator-app/build.gradle @@ -54,10 +54,10 @@ android { resValue "string", "app_name", "Keyple Validation OMAPI" applicationIdSuffix ".omapi" } - copernic { + coppernic { dimension 'device' - resValue "string", "app_name", "Keyple Validation Copernic" - applicationIdSuffix ".copernic" + resValue "string", "app_name", "Keyple Validation coppernic" + applicationIdSuffix ".coppernic" } famoco { dimension 'device' @@ -81,7 +81,7 @@ android { test.java.srcDirs += 'src/test/kotlin' omapi.java.srcDirs += 'src/omapi/kotlin' - copernic.java.srcDirs += 'src/copernic/kotlin' + coppernic.java.srcDirs += 'src/coppernic/kotlin' famoco.java.srcDirs += 'src/famoco/kotlin' mockSam.java.srcDirs += 'src/mockSam/kotlin' bluebird.java.srcDirs += 'src/bluebird/kotlin' @@ -115,7 +115,7 @@ dependencies { mockSamImplementation "org.eclipse.keyple:keyple-android-plugin-nfc:$keyple_version" - copernicImplementation "org.eclipse.keyple:keyple-android-plugin-coppernic-ask:$coppernic_plugin_version" + coppernicImplementation "org.eclipse.keyple:keyple-android-plugin-coppernic-ask:$coppernic_plugin_version" bluebirdImplementation "org.eclipse.keyple:keyple-android-plugin-bluebird:$bluebird_plugin_version" diff --git a/validator-app/src/copernic/kotlin/org/eclipse/keyple/demo/validator/di/CoppernicReaderRepositoryImpl.kt b/validator-app/src/coppernic/kotlin/org/eclipse/keyple/demo/validator/di/CoppernicReaderRepositoryImpl.kt similarity index 100% rename from validator-app/src/copernic/kotlin/org/eclipse/keyple/demo/validator/di/CoppernicReaderRepositoryImpl.kt rename to validator-app/src/coppernic/kotlin/org/eclipse/keyple/demo/validator/di/CoppernicReaderRepositoryImpl.kt diff --git a/validator-app/src/copernic/kotlin/org/eclipse/keyple/demo/validator/di/ReaderModule.kt b/validator-app/src/coppernic/kotlin/org/eclipse/keyple/demo/validator/di/ReaderModule.kt similarity index 100% rename from validator-app/src/copernic/kotlin/org/eclipse/keyple/demo/validator/di/ReaderModule.kt rename to validator-app/src/coppernic/kotlin/org/eclipse/keyple/demo/validator/di/ReaderModule.kt diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/CardReaderApi.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/CardReaderApi.kt index c986172..e47e478 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/CardReaderApi.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/CardReaderApi.kt @@ -12,7 +12,6 @@ package org.eclipse.keyple.demo.validator.data import android.app.Activity -import javax.inject.Inject import org.eclipse.keyple.core.service.Reader import org.eclipse.keyple.core.service.SmartCardService import org.eclipse.keyple.core.service.event.ObservableReader @@ -22,15 +21,15 @@ import org.eclipse.keyple.core.service.exception.KeyplePluginNotFoundException import org.eclipse.keyple.core.service.exception.KeypleReaderIOException import org.eclipse.keyple.demo.validator.di.scopes.AppScoped import org.eclipse.keyple.demo.validator.reader.IReaderRepository +import org.eclipse.keyple.demo.validator.ticketing.ITicketingSession import org.eclipse.keyple.demo.validator.ticketing.TicketingSession -import org.eclipse.keyple.demo.validator.ticketing.TicketingSessionManager import timber.log.Timber +import javax.inject.Inject @AppScoped class CardReaderApi @Inject constructor(private var readerRepository: IReaderRepository) { - private lateinit var ticketingSessionManager: TicketingSessionManager - private var ticketingSession: TicketingSession? = null + private var ticketingSession: ITicketingSession? = null @Throws( KeyplePluginInstantiationException::class, @@ -83,10 +82,7 @@ class CardReaderApi @Inject constructor(private var readerRepository: IReaderRep /* remove the observer if it already exist */ (reader as ObservableReader).addObserver(observer) - ticketingSessionManager = TicketingSessionManager() - - ticketingSession = - ticketingSessionManager.createTicketingSession(readerRepository) as TicketingSession + ticketingSession = TicketingSession(readerRepository) } } @@ -109,7 +105,7 @@ class CardReaderApi @Inject constructor(private var readerRepository: IReaderRep } } - fun getTicketingSession(): TicketingSession? { + fun getTicketingSession(): ITicketingSession? { return ticketingSession } diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/LocationFileManager.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/LocationFileManager.kt index 881622d..b588ed2 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/LocationFileManager.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/data/LocationFileManager.kt @@ -18,9 +18,11 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import org.eclipse.keyple.demo.validator.models.Location import org.eclipse.keyple.demo.validator.util.FileHelper +import timber.log.Timber import java.io.BufferedReader import java.io.File import java.io.FileInputStream +import java.io.FileNotFoundException import java.io.IOException import java.io.InputStream import java.io.InputStreamReader @@ -62,12 +64,18 @@ class LocationFileManager @Inject constructor(context: Context) { ) } } - fun getLocations(): List { if (locationList == null) { - val file = getFileFromSdCard() - locationList = getGson().fromJson(file, Array::class.java).toList() + try{ + val file = getFileFromSdCard() + locationList = getGson().fromJson(file, Array::class.java).toList() + } + catch (e: FileNotFoundException){ + Timber.e(e) + locationList = getGson().fromJson(locationsFromResources, Array::class.java).toList() + } } + return locationList!! } diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EnvironmentException.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EnvironmentException.kt index 52a7127..1ff4dd9 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EnvironmentException.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EnvironmentException.kt @@ -15,6 +15,6 @@ class EnvironmentException(key: EnvironmentExceptionKey) : ValidationException(key.value) enum class EnvironmentExceptionKey constructor(val key: Int, val value: String) { - WRONG_VERSION_NUMBER(0, "Environment Error : wrong version number"), - EXPIRED(1, "Environment Error : end date expired"); + WRONG_VERSION_NUMBER(0, "Environment Error: wrong version number"), + EXPIRED(1, "Environment Error: end date expired"); } \ No newline at end of file diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EventException.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EventException.kt index 772769a..e0bb4f5 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EventException.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/exception/EventException.kt @@ -15,4 +15,5 @@ class EventException(key: EventExceptionKey) : ValidationException(key.value) enum class EventExceptionKey constructor(val key: Int, val value: String) { WRONG_VERSION_NUMBER(0, "Event - Wrong version number"), + CLEAN_CARD(1, "No valid title detected"); } \ No newline at end of file diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/models/StructureEnum.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/models/StructureEnum.kt new file mode 100644 index 0000000..1ef6d5e --- /dev/null +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/models/StructureEnum.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 Calypso Networks Association https://www.calypsonet-asso.org/ + * + * See the NOTICE file(s) distributed with this work for additional information + * regarding copyright ownership. + * + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.keyple.demo.validator.models + +enum class StructureEnum(val key: Int) { + STRUCTURE_05H(0x5), + STRUCTURE_13H(0x13), + STRUCTURE_32H(0x32), + STRUCTURE_42H(0x42); + + override fun toString(): String { + return "Structure ${Integer.toHexString(key)}h" + } + + + companion object { + fun findEnumByKey(key : Int): StructureEnum? { + val values = values() + return values.find { + it.key == key + } + } + } + +} diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/AbstractTicketingSession.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/AbstractTicketingSession.kt deleted file mode 100644 index 37f3000..0000000 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/AbstractTicketingSession.kt +++ /dev/null @@ -1,170 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 Calypso Networks Association https://www.calypsonet-asso.org/ - * - * See the NOTICE file(s) distributed with this work for additional information regarding copyright - * ownership. - * - * This program and the accompanying materials are made available under the terms of the Eclipse - * Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ -package org.eclipse.keyple.demo.validator.ticketing - -import org.eclipse.keyple.calypso.command.sam.SamRevision -import org.eclipse.keyple.calypso.transaction.CalypsoPo -import org.eclipse.keyple.calypso.transaction.CalypsoSam -import org.eclipse.keyple.calypso.transaction.ElementaryFile -import org.eclipse.keyple.calypso.transaction.PoSecuritySettings -import org.eclipse.keyple.calypso.transaction.PoSecuritySettings.PoSecuritySettingsBuilder -import org.eclipse.keyple.calypso.transaction.PoTransaction.SessionSetting.AccessLevel -import org.eclipse.keyple.calypso.transaction.SamSelection -import org.eclipse.keyple.calypso.transaction.SamSelector -import org.eclipse.keyple.core.card.selection.CardResource -import org.eclipse.keyple.core.card.selection.CardSelectionsResult -import org.eclipse.keyple.core.card.selection.CardSelectionsService -import org.eclipse.keyple.core.card.selection.MultiSelectionProcessing -import org.eclipse.keyple.core.service.Reader -import org.eclipse.keyple.core.service.event.ObservableReader -import org.eclipse.keyple.core.service.exception.KeypleReaderException -import org.eclipse.keyple.demo.validator.reader.IReaderRepository -import org.eclipse.keyple.parser.dto.CardletInputDto -import org.eclipse.keyple.parser.keyple.CardletParser -import org.eclipse.keyple.parser.model.CardletDto -import timber.log.Timber - -abstract class AbstractTicketingSession protected constructor( - protected val readerRepository: IReaderRepository -) { - - val poReader: Reader? - get() = readerRepository.poReader - - protected lateinit var calypsoPo: CalypsoPo - protected lateinit var cardSelection: CardSelectionsService - var poTypeName: String? = null - protected set - var cardContent: CardContent = CardContent() - protected set - protected var currentPoSN: ByteArray? = null - protected var calypsoPoIndex = 0 - protected lateinit var efEnvironmentHolder: ElementaryFile - protected lateinit var efEventLog: ElementaryFile - protected lateinit var efCounter: ElementaryFile - protected lateinit var efContractParser: ElementaryFile - - protected fun pad(text: String, c: Char, length: Int): String { - val sb = StringBuffer(length) - sb.append(text) - for (i in text.length until length) { - sb.append(c) - } - return sb.toString() - } - - fun processSelectionsResult(selectionsResult: CardSelectionsResult) { - val selectionIndex = selectionsResult.smartCards.keys.first() - - if (selectionIndex == calypsoPoIndex) { - calypsoPo = selectionsResult.activeSmartCard as CalypsoPo - poTypeName = "CALYPSO" - efEnvironmentHolder = calypsoPo.getFileBySfi(CalypsoInfo.SFI_EnvironmentAndHolder) - efEventLog = calypsoPo.getFileBySfi(CalypsoInfo.SFI_EventLog) - efCounter = calypsoPo.getFileBySfi(CalypsoInfo.SFI_Counter) - efContractParser = calypsoPo.getFileBySfi(CalypsoInfo.SFI_Contracts) - } else { - poTypeName = "OTHER" - } - Timber.i("PO type = $poTypeName") - } - - val poIdentification: String - get() = (calypsoPo.applicationSerialNumber + ", " + - calypsoPo.revision.toString()) - - /** - * initial PO content analysis - * - * @return - */ - fun analyzePoProfile(): Boolean { - var status = false - if (calypsoPo.startupInfo != null) { - currentPoSN = calypsoPo.applicationSerialNumberBytes - cardContent.serialNumber = currentPoSN - cardContent.poRevision = calypsoPo.revision.toString() - status = true - } - return status - } - - fun notifySeProcessed() { - (readerRepository.poReader as ObservableReader).finalizeCardProcessing() - } - - @Throws(KeypleReaderException::class, IllegalStateException::class) - open fun checkSamAndOpenChannel(samReader: Reader): CardResource { - /* - * check the availability of the SAM doing a ATR based selection, open its physical and - * logical channels and keep it open - */ - val samSelection = CardSelectionsService(MultiSelectionProcessing.FIRST_MATCH) - - val samSelector = SamSelector.builder() - .cardProtocol(readerRepository.getSamReaderProtocol()) - .samRevision(SamRevision.C1) - .build() - - samSelection.prepareSelection(SamSelection(samSelector)) - - return try { - if (samReader.isCardPresent) { - val selectionResult = samSelection.processExplicitSelections(samReader) - if (selectionResult.hasActiveSelection()) { - val calypsoSam = selectionResult.activeSmartCard as CalypsoSam - CardResource(samReader, calypsoSam) - } else { - throw IllegalStateException("Sam selection failed") - } - } else { - throw IllegalStateException("Sam is not present in the reader") - } - } catch (e: KeypleReaderException) { - throw IllegalStateException("Reader exception: " + e.message) - } - } - - open fun getSecuritySettings(samResource: CardResource?): PoSecuritySettings? { - - // The default KIF values for personalization, loading and debiting - val DEFAULT_KIF_PERSO = 0x21.toByte() - val DEFAULT_KIF_LOAD = 0x27.toByte() - val DEFAULT_KIF_DEBIT = 0x30.toByte() - // The default key record number values for personalization, loading and debiting - // The actual value should be adjusted. - val DEFAULT_KEY_RECORD_NUMBER_PERSO = 0x01.toByte() - val DEFAULT_KEY_RECORD_NUMBER_LOAD = 0x02.toByte() - val DEFAULT_KEY_RECORD_NUMBER_DEBIT = 0x03.toByte() - - /* define the security parameters to provide when creating PoTransaction */ - return PoSecuritySettingsBuilder(samResource) // - .sessionDefaultKif(AccessLevel.SESSION_LVL_PERSO, DEFAULT_KIF_PERSO) // - .sessionDefaultKif(AccessLevel.SESSION_LVL_LOAD, DEFAULT_KIF_LOAD) // - .sessionDefaultKif(AccessLevel.SESSION_LVL_DEBIT, DEFAULT_KIF_DEBIT) // - .sessionDefaultKeyRecordNumber( - AccessLevel.SESSION_LVL_PERSO, - DEFAULT_KEY_RECORD_NUMBER_PERSO - ) // - .sessionDefaultKeyRecordNumber( - AccessLevel.SESSION_LVL_LOAD, - DEFAULT_KEY_RECORD_NUMBER_LOAD - ) // - .sessionDefaultKeyRecordNumber( - AccessLevel.SESSION_LVL_DEBIT, - DEFAULT_KEY_RECORD_NUMBER_DEBIT - ) - .build() - } - - fun parseCardlet(cardletDto: CardletInputDto): CardletDto? = CardletParser().parseCardlet(cardletDto) -} diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/CalypsoInfo.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/CalypsoInfo.kt index 1d56064..204bc89 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/CalypsoInfo.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/CalypsoInfo.kt @@ -34,22 +34,19 @@ object CalypsoInfo { const val AID_HISTORIC = "315449432e494341" /** AID Intercode * */ - const val AID_HIS_STRUCTURE_13H = "315449432e49434132" const val AID_HIS_STRUCTURE_5H = "315449432e49434131" + const val AID_HIS_STRUCTURE_13H = "315449432e49434132" + const val AID_HIS_STRUCTURE_32H = "315449432E49434133" /** AID NORMALIZED IDF * */ - const val AID_NORMALIZED_IDF = "A0000004040125090101" - - /** AID BANKING * */ - const val AID_BANKING = "325041592e5359532e4444463031" - + const val AID_NORMALIZED_IDF_05H = "A0000004040125090101" /********************************* * Card types *********************************/ - const val PO_TYPE_NAME_CALYPSO = "Calypso" - const val PO_TYPE_NAME_NAVIGO = "Navigo" - const val PO_TYPE_NAME_BANKING = "Banking" + const val PO_TYPE_NAME_CALYPSO_05h = "Calypso_05h" + const val PO_TYPE_NAME_CALYPSO_32h = "Calypso_32h" + const val PO_TYPE_NAME_NAVIGO_05h = "Navigo_05h" const val PO_TYPE_NAME_OTHER = "Unknown" /** Audit-C0 */ // public final static String AID = "315449432E4943414C54"; diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/ITicketingSession.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/ITicketingSession.kt index 15885c9..10ae678 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/ITicketingSession.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/ITicketingSession.kt @@ -11,24 +11,27 @@ ********************************************************************************/ package org.eclipse.keyple.demo.validator.ticketing +import android.content.Context +import org.eclipse.keyple.calypso.transaction.CalypsoSam +import org.eclipse.keyple.calypso.transaction.PoSecuritySettings +import org.eclipse.keyple.core.card.selection.CardResource +import org.eclipse.keyple.core.card.selection.CardSelectionsResult import org.eclipse.keyple.core.service.Reader +import org.eclipse.keyple.core.service.event.AbstractDefaultSelectionsResponse import org.eclipse.keyple.core.service.exception.KeypleReaderException +import org.eclipse.keyple.demo.validator.models.CardReaderResponse +import org.eclipse.keyple.demo.validator.models.Location interface ITicketingSession { val poReader: Reader? - val cardContent: CardContent + val samReader: Reader? val poTypeName: String? - fun analyzePoProfile(): Boolean - val poIdentification: String? - @Throws(KeypleReaderException::class) - fun loadTickets(ticketNumber: Int): Int - fun notifySeProcessed() - - companion object { - const val STATUS_OK = 0 - const val STATUS_UNKNOWN_ERROR = 1 - const val STATUS_CARD_SWITCHED = 2 - const val STATUS_SESSION_ERROR = 3 - } + fun prepareAndSetPoDefaultSelection() + fun processDefaultSelection(selectionResponse: AbstractDefaultSelectionsResponse?): CardSelectionsResult + fun checkStructure(): Boolean + fun checkStartupInfo(): Boolean + fun launchValidationProcedure(context: Context, locations: List): CardReaderResponse + fun checkSamAndOpenChannel(samReader: Reader): CardResource + fun getSecuritySettings(samResource: CardResource?): PoSecuritySettings? } diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSession.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSession.kt index 927c890..3ad7b95 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSession.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSession.kt @@ -12,17 +12,15 @@ package org.eclipse.keyple.demo.validator.ticketing import android.content.Context -import org.eclipse.keyple.calypso.command.po.exception.CalypsoPoCommandException -import org.eclipse.keyple.calypso.command.sam.exception.CalypsoSamCommandException +import org.eclipse.keyple.calypso.command.sam.SamRevision import org.eclipse.keyple.calypso.transaction.CalypsoPo +import org.eclipse.keyple.calypso.transaction.CalypsoSam +import org.eclipse.keyple.calypso.transaction.PoSecuritySettings import org.eclipse.keyple.calypso.transaction.PoSelection import org.eclipse.keyple.calypso.transaction.PoSelector import org.eclipse.keyple.calypso.transaction.PoTransaction -import org.eclipse.keyple.calypso.transaction.exception.CalypsoPoTransactionException -import org.eclipse.keyple.core.card.command.AbstractApduCommandBuilder -import org.eclipse.keyple.core.card.message.CardSelectionResponse -import org.eclipse.keyple.core.card.selection.AbstractCardSelection -import org.eclipse.keyple.core.card.selection.AbstractSmartCard +import org.eclipse.keyple.calypso.transaction.SamSelection +import org.eclipse.keyple.calypso.transaction.SamSelector import org.eclipse.keyple.core.card.selection.CardResource import org.eclipse.keyple.core.card.selection.CardSelectionsResult import org.eclipse.keyple.core.card.selection.CardSelectionsService @@ -32,47 +30,67 @@ import org.eclipse.keyple.core.service.Reader import org.eclipse.keyple.core.service.event.AbstractDefaultSelectionsResponse import org.eclipse.keyple.core.service.event.ObservableReader import org.eclipse.keyple.core.service.exception.KeypleReaderException -import org.eclipse.keyple.core.util.ByteArrayUtil import org.eclipse.keyple.demo.validator.models.CardReaderResponse import org.eclipse.keyple.demo.validator.models.Location +import org.eclipse.keyple.demo.validator.models.StructureEnum import org.eclipse.keyple.demo.validator.reader.IReaderRepository -import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.AID_BANKING +import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.AID_HIS_STRUCTURE_32H import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.AID_HIS_STRUCTURE_5H -import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.AID_NORMALIZED_IDF -import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_BANKING -import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_CALYPSO -import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_NAVIGO +import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.AID_NORMALIZED_IDF_05H +import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_CALYPSO_05h +import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_CALYPSO_32h +import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_NAVIGO_05h import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.PO_TYPE_NAME_OTHER import org.eclipse.keyple.demo.validator.ticketing.procedure.ValidationProcedure import timber.log.Timber -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.Arrays -import java.util.Date +import java.util.EnumMap -class TicketingSession(readerRepository: IReaderRepository) : - AbstractTicketingSession(readerRepository), ITicketingSession { +class TicketingSession(private val readerRepository: IReaderRepository) : ITicketingSession { - private var bankingCardIndex = 0 - private var navigoCardIndex = 0 + private var calypsoPoIndex05h = 0 + private var calypsoPoIndex32h = 0 + private var navigoCardIndex05h = 0 - private var samReader: Reader? = null + private lateinit var calypsoPo: CalypsoPo - init { - samReader = readerRepository.getSamReader() - } + private lateinit var cardSelection: CardSelectionsService + + override var poTypeName: String? = null + private set + + override val poReader: Reader? + get() = readerRepository.poReader + + override val samReader: Reader? + get() = readerRepository.getSamReader() + + var poStructure: StructureEnum? = null + private set + + private val allowedStructures: EnumMap> = + EnumMap(StructureEnum::class.java) /* * Should be instanciated through the ticketing session mananger */ init { + allowedStructures[StructureEnum.STRUCTURE_05H] = + listOf( + PO_TYPE_NAME_CALYPSO_05h, + PO_TYPE_NAME_NAVIGO_05h + ) + allowedStructures[StructureEnum.STRUCTURE_32H] = + listOf( + PO_TYPE_NAME_CALYPSO_32h + ) + prepareAndSetPoDefaultSelection() } /** * prepare the default selection */ - fun prepareAndSetPoDefaultSelection() { + override fun prepareAndSetPoDefaultSelection() { /* * Prepare a PO selection */ @@ -90,35 +108,32 @@ class TicketingSession(readerRepository: IReaderRepository) : ) .invalidatedPo(PoSelector.InvalidatedPo.REJECT).build() ) + calypsoPoIndex05h = cardSelection.prepareSelection(poSelectionRequest) - calypsoPoIndex = cardSelection.prepareSelection(poSelectionRequest) - - /* - * NAVIGO - */ - val navigoCardSelectionRequest = GenericSeSelectionRequest( + val poSelectionRequest32h = PoSelection( PoSelector.builder() .cardProtocol(readerRepository.getContactlessIsoProtocol()!!.applicationProtocolName) .aidSelector( - CardSelector.AidSelector.builder().aidToSelect(AID_NORMALIZED_IDF).build() + CardSelector.AidSelector.builder() + .aidToSelect(AID_HIS_STRUCTURE_32H).build() ) .invalidatedPo(PoSelector.InvalidatedPo.REJECT).build() ) - navigoCardIndex = cardSelection.prepareSelection(navigoCardSelectionRequest) + calypsoPoIndex32h = cardSelection.prepareSelection(poSelectionRequest32h) /* - * Banking + * NAVIGO */ - val bankingCardSelectionRequest = GenericSeSelectionRequest( + val navigoCardSelectionRequest = PoSelection( PoSelector.builder() .cardProtocol(readerRepository.getContactlessIsoProtocol()!!.applicationProtocolName) .aidSelector( - CardSelector.AidSelector.builder().aidToSelect(AID_BANKING) - .build() + CardSelector.AidSelector.builder() + .aidToSelect(AID_NORMALIZED_IDF_05H).build() ) .invalidatedPo(PoSelector.InvalidatedPo.REJECT).build() ) - bankingCardIndex = cardSelection.prepareSelection(bankingCardSelectionRequest) + navigoCardIndex05h = cardSelection.prepareSelection(navigoCardSelectionRequest) /* * Provide the Reader with the selection operation to be processed when a PO is inserted. @@ -128,18 +143,27 @@ class TicketingSession(readerRepository: IReaderRepository) : ) } - fun processDefaultSelection(selectionResponse: AbstractDefaultSelectionsResponse?): CardSelectionsResult { + override fun processDefaultSelection(selectionResponse: AbstractDefaultSelectionsResponse?): CardSelectionsResult { Timber.i("selectionResponse = $selectionResponse") val selectionsResult: CardSelectionsResult = cardSelection.processDefaultSelectionsResponse(selectionResponse) if (selectionsResult.hasActiveSelection()) { when (selectionsResult.smartCards.keys.first()) { - calypsoPoIndex -> { + calypsoPoIndex05h -> { + calypsoPo = selectionsResult.activeSmartCard as CalypsoPo + poTypeName = PO_TYPE_NAME_CALYPSO_05h + poStructure = StructureEnum.findEnumByKey(calypsoPo.applicationSubtype.toInt()) + } + calypsoPoIndex32h -> { + calypsoPo = selectionsResult.activeSmartCard as CalypsoPo + poTypeName = PO_TYPE_NAME_CALYPSO_32h + poStructure = StructureEnum.findEnumByKey(calypsoPo.applicationSubtype.toInt()) + } + navigoCardIndex05h -> { calypsoPo = selectionsResult.activeSmartCard as CalypsoPo - poTypeName = PO_TYPE_NAME_CALYPSO + poTypeName = PO_TYPE_NAME_NAVIGO_05h + poStructure = StructureEnum.findEnumByKey(calypsoPo.applicationSubtype.toInt()) } - bankingCardIndex -> poTypeName = PO_TYPE_NAME_BANKING - navigoCardIndex -> poTypeName = PO_TYPE_NAME_NAVIGO else -> poTypeName = PO_TYPE_NAME_OTHER } } @@ -147,8 +171,12 @@ class TicketingSession(readerRepository: IReaderRepository) : return selectionsResult } - - fun launchValidationProcedure(context: Context, locations: List): CardReaderResponse { + /** + * Launch the control procedure of the current PO + * + * @return [CardReaderResponse] + */ + override fun launchValidationProcedure(context: Context, locations: List): CardReaderResponse { return ValidationProcedure().launch( context = context, validationAmount = 1, @@ -160,270 +188,86 @@ class TicketingSession(readerRepository: IReaderRepository) : } /** - * do the personalization of the PO according to the specified profile + * initial PO content analysis * - * @param profile * @return */ - @Throws( - CalypsoPoTransactionException::class, - CalypsoPoCommandException::class, - CalypsoSamCommandException::class - ) - fun personalize(profile: String): Boolean { - try { - // Should block poTransaction without Sam? - val poTransaction = if (samReader != null) - PoTransaction( - CardResource(poReader, calypsoPo), - getSecuritySettings(checkSamAndOpenChannel(samReader!!)) - ) - else - PoTransaction(CardResource(poReader, calypsoPo)) - poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_PERSO) + override fun checkStartupInfo(): Boolean = calypsoPo.startupInfo != null - if ("PROFILE1" == profile) { - poTransaction.prepareUpdateRecord( - CalypsoInfo.SFI_EnvironmentAndHolder, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - pad("John Smith", ' ', 29).toByteArray() - ) - poTransaction.prepareUpdateRecord( - CalypsoInfo.SFI_Contracts, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - pad("NO CONTRACT", ' ', 29).toByteArray() - ) - } else { - poTransaction.prepareUpdateRecord( - CalypsoInfo.SFI_EnvironmentAndHolder, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - pad("Harry Potter", ' ', 29).toByteArray() - ) - poTransaction.prepareUpdateRecord( - CalypsoInfo.SFI_Contracts, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - pad("1 MONTH SEASON TICKET", ' ', 29).toByteArray() - ) - } - val dateFormat: DateFormat = SimpleDateFormat("yyMMdd HH:mm:ss") - val dateTime = dateFormat.format(Date()) - poTransaction.prepareAppendRecord( - CalypsoInfo.SFI_EventLog, - pad("$dateTime OP = PERSO", ' ', 29).toByteArray() - ) - poTransaction.prepareUpdateRecord( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - ByteArrayUtil.fromHex(pad("", '0', 29 * 2)) - ) - cardSelection.prepareReleaseChannel() - return true - } catch (e: CalypsoPoTransactionException) { - Timber.e(e) - } catch (e: CalypsoPoCommandException) { - Timber.e(e) - } catch (e: CalypsoSamCommandException) { - Timber.e(e) - } - return false - } - /* - * public void forceCloseChannel() throws KeypleReaderException { - * logger.debug("Force close logical channel (hack for nfc reader)"); List - * requestList = new ArrayList<>(); ((ProxyReader)poReader).transmit(new - * SeRequest(requestList)); } - */ /** - * load the PO according to the choice provided as an argument - * - * @param ticketNumber - * @return - * @throws KeypleReaderException + * Check card Structure */ - @Throws(KeypleReaderException::class) - override fun loadTickets(ticketNumber: Int): Int { - return try { - // Should block poTransaction without Sam? - val poTransaction = if (samReader != null) - PoTransaction( - CardResource(poReader, calypsoPo), - getSecuritySettings(checkSamAndOpenChannel(samReader!!)) - ) - else - PoTransaction(CardResource(poReader, calypsoPo)) - - if (!Arrays.equals(currentPoSN, calypsoPo.applicationSerialNumberBytes)) { - Timber.i("Load ticket status : STATUS_CARD_SWITCHED") - return ITicketingSession.STATUS_CARD_SWITCHED - } - /* - * Open a transaction to read/write the Calypso PO - */ - poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_LOAD) - - /* - * Read actual ticket number - */ - poTransaction.prepareReadRecordFile( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poTransaction.processPoCommands() - poTransaction.prepareIncreaseCounter( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - ticketNumber - ) - - /* - * Prepare record to be sent to Calypso PO log journal - */ - val dateFormat: DateFormat = SimpleDateFormat("yyMMdd HH:mm:ss") - val dateTime = dateFormat.format(Date()) - var event = "" - event = if (ticketNumber > 0) { - pad("$dateTime OP = +$ticketNumber", ' ', 29) - } else { - pad("$dateTime T1", ' ', 29) - } - poTransaction.prepareAppendRecord(CalypsoInfo.SFI_EventLog, event.toByteArray()) - - /* - * Process transaction - */ - cardSelection.prepareReleaseChannel() - Timber.i("Load ticket status : STATUS_OK") - ITicketingSession.STATUS_OK - } catch (e: CalypsoSamCommandException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoCommandException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoTransactionException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR + override fun checkStructure(): Boolean { + if (!allowedStructures.containsKey(poStructure)) { + return false + } + if (!allowedStructures[poStructure]!!.contains(poTypeName)) { + return false } + return true } - fun debitTickets(ticketNumber: Int): Int { - return try { - // Should block poTransaction without Sam? - val poTransaction = - if (samReader != null) - PoTransaction( - CardResource(poReader, calypsoPo), - getSecuritySettings(checkSamAndOpenChannel(samReader!!)) - ) - else - PoTransaction(CardResource(poReader, calypsoPo)) - - /* - * Open a transaction to read/write the Calypso PO - */ - poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_DEBIT) - - /* allow to determine the anticipated response */ - poTransaction.prepareReadRecordFile( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poTransaction.processPoCommands() - - /* - * Prepare decrease command - */ - poTransaction.prepareDecreaseCounter( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - ticketNumber - ) + @Throws(KeypleReaderException::class, IllegalStateException::class) + override fun checkSamAndOpenChannel(samReader: Reader): CardResource { + /* + * check the availability of the SAM doing a ATR based selection, open its physical and + * logical channels and keep it open + */ + val samSelection = CardSelectionsService(MultiSelectionProcessing.FIRST_MATCH) - /* - * Process transaction and close session - */ - poTransaction.processClosing() + val samSelector = SamSelector.builder() + .cardProtocol(readerRepository.getSamReaderProtocol()) + .samRevision(SamRevision.C1) + .build() - Timber.i("Debit ticket status : STATUS_OK") - ITicketingSession.STATUS_OK - } catch (e: CalypsoSamCommandException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoCommandException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoTransactionException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } - } + samSelection.prepareSelection(SamSelection(samSelector)) - /** - * Load a season ticket contract - * - * @return - * @throws KeypleReaderException - */ - @Throws(KeypleReaderException::class) - fun loadContract(): Int { return try { - // Should block poTransaction without Sam? - val poTransaction = if (samReader != null) - PoTransaction( - CardResource(poReader, calypsoPo), - getSecuritySettings(checkSamAndOpenChannel(samReader!!)) - ) - else - PoTransaction(CardResource(poReader, calypsoPo)) - - if (!Arrays.equals(currentPoSN, calypsoPo.applicationSerialNumberBytes)) { - return ITicketingSession.STATUS_CARD_SWITCHED + if (samReader.isCardPresent) { + val selectionResult = samSelection.processExplicitSelections(samReader) + if (selectionResult.hasActiveSelection()) { + val calypsoSam = selectionResult.activeSmartCard as CalypsoSam + CardResource(samReader, calypsoSam) + } else { + throw IllegalStateException("Sam selection failed") + } + } else { + throw IllegalStateException("Sam is not present in the reader") } - - poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_LOAD) - - /* allow to determine the anticipated response */ - poTransaction.prepareReadRecordFile( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poTransaction.processPoCommands() - poTransaction.prepareUpdateRecord( - CalypsoInfo.SFI_Contracts, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - pad("1 MONTH SEASON TICKET", ' ', 29).toByteArray() - ) - - // DateTimeFormatter formatter = DateTimeFormatter.ofPattern(""); - // String dateTime = LocalDateTime.now().format(formatter); - val dateFormat: DateFormat = SimpleDateFormat("yyMMdd HH:mm:ss") - val event = - pad(dateFormat.format(Date()) + " OP = +ST", ' ', 29) - poTransaction.prepareAppendRecord(CalypsoInfo.SFI_EventLog, event.toByteArray()) - poTransaction.processClosing() - ITicketingSession.STATUS_OK - } catch (e: CalypsoSamCommandException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoCommandException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoTransactionException) { - Timber.e(e) - ITicketingSession.STATUS_SESSION_ERROR + } catch (e: KeypleReaderException) { + throw IllegalStateException("Reader exception: " + e.message) } } - /** - * Create a new class extending AbstractSeSelectionRequest - */ - inner class GenericSeSelectionRequest(seSelector: CardSelector) : - AbstractCardSelection(seSelector) { - override fun parse(seResponse: CardSelectionResponse): AbstractSmartCard { - class GenericMatchingSe( - selectionResponse: CardSelectionResponse? - ) : AbstractSmartCard(selectionResponse) - return GenericMatchingSe(seResponse) - } + override fun getSecuritySettings(samResource: CardResource?): PoSecuritySettings? { + // The default KIF values for personalization, loading and debiting + val DEFAULT_KIF_PERSO = 0x21.toByte() + val DEFAULT_KIF_LOAD = 0x27.toByte() + val DEFAULT_KIF_DEBIT = 0x30.toByte() + // The default key record number values for personalization, loading and debiting + // The actual value should be adjusted. + val DEFAULT_KEY_RECORD_NUMBER_PERSO = 0x01.toByte() + val DEFAULT_KEY_RECORD_NUMBER_LOAD = 0x02.toByte() + val DEFAULT_KEY_RECORD_NUMBER_DEBIT = 0x03.toByte() + + /* define the security parameters to provide when creating PoTransaction */ + return PoSecuritySettings.PoSecuritySettingsBuilder(samResource) // + .sessionDefaultKif(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_PERSO, DEFAULT_KIF_PERSO) // + .sessionDefaultKif(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_LOAD, DEFAULT_KIF_LOAD) // + .sessionDefaultKif(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_DEBIT, DEFAULT_KIF_DEBIT) // + .sessionDefaultKeyRecordNumber( + PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_PERSO, + DEFAULT_KEY_RECORD_NUMBER_PERSO + ) // + .sessionDefaultKeyRecordNumber( + PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_LOAD, + DEFAULT_KEY_RECORD_NUMBER_LOAD + ) // + .sessionDefaultKeyRecordNumber( + PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_DEBIT, + DEFAULT_KEY_RECORD_NUMBER_DEBIT + ) + .build() } + } diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSessionExplicitSelection.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSessionExplicitSelection.kt deleted file mode 100644 index 490ca62..0000000 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSessionExplicitSelection.kt +++ /dev/null @@ -1,178 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 Calypso Networks Association https://www.calypsonet-asso.org/ - * - * See the NOTICE file(s) distributed with this work for additional information regarding copyright - * ownership. - * - * This program and the accompanying materials are made available under the terms of the Eclipse - * Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ -package org.eclipse.keyple.demo.validator.ticketing - -import org.eclipse.keyple.calypso.command.po.exception.CalypsoPoCommandException -import org.eclipse.keyple.calypso.command.sam.exception.CalypsoSamCommandException -import org.eclipse.keyple.calypso.transaction.PoSelection -import org.eclipse.keyple.calypso.transaction.PoSelector -import org.eclipse.keyple.calypso.transaction.PoTransaction -import org.eclipse.keyple.calypso.transaction.exception.CalypsoPoTransactionException -import org.eclipse.keyple.core.card.selection.CardResource -import org.eclipse.keyple.core.card.selection.CardSelectionsResult -import org.eclipse.keyple.core.card.selection.CardSelectionsService -import org.eclipse.keyple.core.card.selection.CardSelector -import org.eclipse.keyple.core.service.Reader -import org.eclipse.keyple.core.service.exception.KeypleReaderException -import org.eclipse.keyple.demo.validator.reader.IReaderRepository -import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.AID_HISTORIC -import timber.log.Timber -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.Arrays -import java.util.Date -import java.util.Locale - -class TicketingSessionExplicitSelection(readerRepository: IReaderRepository) : - AbstractTicketingSession(readerRepository), ITicketingSession { - - private var samReader: Reader? = null - - init { - samReader = readerRepository.getSamReader() - } - - /** - * prepare the default selection - */ - @Throws(KeypleReaderException::class) - fun processExplicitSelection(): CardSelectionsResult { - /* - * Prepare a PO selection - */ - cardSelection = CardSelectionsService() - - /* Select Calypso */ - val poSelectionRequest = PoSelection( - PoSelector.builder() - .cardProtocol(readerRepository.getContactlessIsoProtocol()!!.applicationProtocolName) - .aidSelector( - CardSelector.AidSelector.builder().aidToSelect(AID_HISTORIC).build() - ) - .invalidatedPo(PoSelector.InvalidatedPo.REJECT).build() - ) - - // Prepare the reading of the Environment and Holder file. - poSelectionRequest.prepareReadRecordFile( - CalypsoInfo.SFI_EnvironmentAndHolder, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poSelectionRequest.prepareReadRecordFile( - CalypsoInfo.SFI_Contracts, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poSelectionRequest.prepareReadRecordFile( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poSelectionRequest.prepareReadRecordFile( - CalypsoInfo.SFI_EventLog, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - - /* - * Add the selection case to the current selection (we could have added other cases here) - */ - calypsoPoIndex = cardSelection.prepareSelection(poSelectionRequest) - return cardSelection.processExplicitSelections(poReader) - } - - /** - * load the PO according to the choice provided as an argument - * - * @param ticketNumber - * @return - * @throws KeypleReaderException - */ - @Throws(KeypleReaderException::class) - override fun loadTickets(ticketNumber: Int): Int { - return try { - /** - * Open channel (again?) - */ - val selectionsResult = processExplicitSelection() - - /* No sucessful selection */ - if (!selectionsResult.hasActiveSelection()) { - Timber.e("PO Not selected") - return ITicketingSession.STATUS_SESSION_ERROR - } - - val poTransaction = - if (samReader != null) - PoTransaction( - CardResource(poReader, calypsoPo), - getSecuritySettings(checkSamAndOpenChannel(samReader!!)) - ) - else - PoTransaction(CardResource(poReader, calypsoPo)) - - if (!Arrays.equals(currentPoSN, calypsoPo.applicationSerialNumberBytes)) { - Timber.i("Load ticket status : STATUS_CARD_SWITCHED") - return ITicketingSession.STATUS_CARD_SWITCHED - } - - /* - * Open a transaction to read/write the Calypso PO - */ - poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_LOAD) - - /* - * Read actual ticket number - */ - poTransaction.prepareReadRecordFile( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt() - ) - poTransaction.processPoCommands() - - poTransaction.prepareIncreaseCounter( - CalypsoInfo.SFI_Counter, - CalypsoInfo.RECORD_NUMBER_1.toInt(), - ticketNumber - ) - - /* - * Prepare record to be sent to Calypso PO log journal - */ - val dateFormat: DateFormat = SimpleDateFormat("yyMMdd HH:mm:ss", Locale.getDefault()) - val dateTime = dateFormat.format(Date()) - val event = if (ticketNumber > 0) { - pad("$dateTime OP = +$ticketNumber", ' ', 29) - } else { - pad("$dateTime T1", ' ', 29) - } - - poTransaction.prepareAppendRecord(CalypsoInfo.SFI_EventLog, event.toByteArray()) - - /* - * Process transaction - */ - poTransaction.processClosing() - - Timber.i("Load ticket status : STATUS_OK") - ITicketingSession.STATUS_OK - } catch (e: CalypsoSamCommandException) { - e.printStackTrace() - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoCommandException) { - e.printStackTrace() - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: CalypsoPoTransactionException) { - e.printStackTrace() - ITicketingSession.STATUS_SESSION_ERROR - } catch (e: KeypleReaderException) { - e.printStackTrace() - ITicketingSession.STATUS_UNKNOWN_ERROR - } - } -} diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSessionManager.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSessionManager.kt deleted file mode 100644 index 7314d5e..0000000 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/TicketingSessionManager.kt +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 Calypso Networks Association https://www.calypsonet-asso.org/ - * - * See the NOTICE file(s) distributed with this work for additional information regarding copyright - * ownership. - * - * This program and the accompanying materials are made available under the terms of the Eclipse - * Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ -package org.eclipse.keyple.demo.validator.ticketing - -import org.eclipse.keyple.core.service.exception.KeypleReaderException -import org.eclipse.keyple.core.service.exception.KeypleReaderNotFoundException -import org.eclipse.keyple.demo.validator.reader.IReaderRepository -import timber.log.Timber - -class TicketingSessionManager(private val ticketingSessions: ArrayList = ArrayList()) { - - @Throws(KeypleReaderException::class) - fun createTicketingSession(readerRepository: IReaderRepository, explicitSelection: Boolean = false): ITicketingSession { - val ticketingSession: ITicketingSession - if (explicitSelection) { - Timber.d("Create a new TicketingSessionExplicitSelection for reader ${readerRepository.poReader?.name}") - ticketingSession = TicketingSessionExplicitSelection(readerRepository) - } else { - Timber.d("Created a new TicketingSession for reader ${readerRepository.poReader?.name}") - ticketingSession = TicketingSession(readerRepository) - } - ticketingSessions.add(ticketingSession) - return ticketingSession - } - - @Throws(KeypleReaderNotFoundException::class) - fun destroyAll() { - Timber.d("Destroy all TicketingSession") - ticketingSessions.clear() - } - - @Throws(KeypleReaderNotFoundException::class) - fun destroyTicketingSession(poReaderName: String): Boolean { - Timber.d("Destroy a the TicketingSession for reader $poReaderName") - for (ticketingSession in ticketingSessions) { - if (ticketingSession.poReader?.name == poReaderName) { - ticketingSessions.remove(ticketingSession) - Timber.d("Session removed for reader ${ticketingSession.poReader} - $ticketingSession") - return true - } - } - Timber.d("No TicketingSession found for reader $poReaderName") - return false - } - - fun getTicketingSession(poReaderName: String): ITicketingSession? { - Timber.d("Retrieve the TicketingSession of reader $poReaderName") - for (ticketingSession in ticketingSessions) { - if (ticketingSession.poReader?.name == poReaderName) { - Timber.d("TicketingSession found for reader $poReaderName") - return ticketingSession - } - } - Timber.d("No TicketingSession found for reader $poReaderName") - return null - } - - fun findAll(): List { - return ticketingSessions - } -} diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/procedure/ValidationProcedure.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/procedure/ValidationProcedure.kt index 18d327a..127caea 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/procedure/ValidationProcedure.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ticketing/procedure/ValidationProcedure.kt @@ -36,7 +36,6 @@ import org.eclipse.keyple.demo.validator.models.Location import org.eclipse.keyple.demo.validator.models.Status import org.eclipse.keyple.demo.validator.models.Validation import org.eclipse.keyple.demo.validator.models.mapper.ValidationMapper -import org.eclipse.keyple.demo.validator.ticketing.AbstractTicketingSession import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.RECORD_NUMBER_1 import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.RECORD_NUMBER_2 import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.RECORD_NUMBER_3 @@ -49,6 +48,7 @@ import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.SFI_Counter_0C import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.SFI_Counter_0D import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.SFI_EnvironmentAndHolder import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo.SFI_EventLog +import org.eclipse.keyple.demo.validator.ticketing.ITicketingSession import org.eclipse.keyple.parser.keyple.ContractStructureParser import org.eclipse.keyple.parser.keyple.CounterStructureParser import org.eclipse.keyple.parser.keyple.EnvironmentHolderStructureParser @@ -73,7 +73,7 @@ class ValidationProcedure { locations: List, calypsoPo: CalypsoPo, samReader: Reader?, - ticketingSession: AbstractTicketingSession + ticketingSession: ITicketingSession ): CardReaderResponse { val now = DateTime.now() // val now = DateTime() @@ -116,11 +116,9 @@ class ValidationProcedure { if (poTransaction != null) { try { - /* - * Open a transaction to read/write the Calypso PO - */ - poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_DEBIT) - + /******************* + * Event and Environment Analysis + *******************/ /* * Step 2 - Unpack environment structure from the binary present in the environment record. */ @@ -128,7 +126,11 @@ class ValidationProcedure { SFI_EnvironmentAndHolder, RECORD_NUMBER_1.toInt() ) - poTransaction.processPoCommands() + + /* + * Open a transaction to read/write the Calypso PO and read the Environment file + */ + poTransaction.processOpening(PoTransaction.SessionSetting.AccessLevel.SESSION_LVL_DEBIT) val efEnvironmentHolder = calypsoPo.getFileBySfi(SFI_EnvironmentAndHolder) @@ -170,9 +172,15 @@ class ValidationProcedure { * */ val eventVersionNumber = event.eventVersionNumber + if (eventVersionNumber != VersionNumberEnum.CURRENT_VERSION.key) { - status = Status.INVALID_CARD - throw EventException(EventExceptionKey.WRONG_VERSION_NUMBER) + if (eventVersionNumber == VersionNumberEnum.UNDEFINED.key) { + status = Status.EMPTY_CARD + throw EventException(EventExceptionKey.CLEAN_CARD) + } else { + status = Status.INVALID_CARD + throw EventException(EventExceptionKey.WRONG_VERSION_NUMBER) + } } /* @@ -180,6 +188,13 @@ class ValidationProcedure { */ val contractPriorities = mutableListOf>() + + /******************* + * Best Contract Search + *******************/ + /* + * Step 7 - Create a list of ContractPriority fields that are different from 0 or 31. + */ if (event.contractPriority1 != ContractPriorityEnum.FORBIDDEN && event.contractPriority1 != ContractPriorityEnum.EXPIRED ) { @@ -242,8 +257,7 @@ class ValidationProcedure { poTransaction.processPoCommands() val efContractParser = calypsoPo.getFileBySfi(SFI_Contracts) - val dataContent = - efContractParser.data.allRecordsContent[RECORD_NUMBER_1.toInt()]!! + val dataContent = efContractParser.data.allRecordsContent[record]!! val contract = ContractStructureParser().parse(dataContent) /* @@ -391,6 +405,7 @@ class ValidationProcedure { */ contractUsed = record writeEvent = true + break } if (writeEvent) { diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardReaderActivity.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardReaderActivity.kt index 166c0a8..fa1a3e6 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardReaderActivity.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardReaderActivity.kt @@ -16,6 +16,7 @@ import android.content.Intent import android.os.Bundle import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat import com.airbnb.lottie.LottieDrawable import kotlinx.android.synthetic.main.activity_card_reader.animation import kotlinx.android.synthetic.main.activity_card_reader.mainView @@ -27,7 +28,6 @@ import kotlinx.coroutines.withContext import org.eclipse.keyple.core.service.event.ObservableReader import org.eclipse.keyple.core.service.event.ReaderEvent import org.eclipse.keyple.core.service.exception.KeyplePluginInstantiationException -import org.eclipse.keyple.demo.validator.BuildConfig import org.eclipse.keyple.demo.validator.R import org.eclipse.keyple.demo.validator.data.CardReaderApi import org.eclipse.keyple.demo.validator.di.scopes.ActivityScoped @@ -35,7 +35,7 @@ import org.eclipse.keyple.demo.validator.models.CardReaderResponse import org.eclipse.keyple.demo.validator.models.KeypleSettings import org.eclipse.keyple.demo.validator.models.Status import org.eclipse.keyple.demo.validator.ticketing.CalypsoInfo -import org.eclipse.keyple.demo.validator.ticketing.TicketingSession +import org.eclipse.keyple.demo.validator.ticketing.ITicketingSession import timber.log.Timber import java.util.Date import java.util.Timer @@ -50,21 +50,23 @@ class CardReaderActivity : BaseActivity() { private var poReaderObserver: PoObserver? = null + @Suppress("DEPRECATION") private lateinit var progress: ProgressDialog + private var timer = Timer() private var readersInitialized = false - lateinit var ticketingSession: TicketingSession + lateinit var ticketingSession: ITicketingSession var currentAppState = AppState.WAIT_SYSTEM_READY /* application states */ enum class AppState { - UNSPECIFIED, WAIT_SYSTEM_READY, WAIT_CARD, CARD_STATUS + WAIT_SYSTEM_READY, WAIT_CARD, CARD_STATUS } + @Suppress("DEPRECATION") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_card_reader) - progress = ProgressDialog(this) progress.setMessage(getString(R.string.please_wait)) progress.setCancelable(false) @@ -105,7 +107,6 @@ class CardReaderActivity : BaseActivity() { if (readersInitialized) { withContext(Dispatchers.Main) { dismissProgress() - updateReaderInfos() } } } @@ -129,16 +130,6 @@ class CardReaderActivity : BaseActivity() { super.onDestroy() } - private fun updateReaderInfos() { - - @Suppress("ConstantConditionIf") - val readerPlugin = if (BuildConfig.FLAVOR == "copernic") { - BuildConfig.FLAVOR - } else { - "Android NFC - ${BuildConfig.FLAVOR}" - } - } - override fun onPause() { super.onPause() animation.cancelAnimation() @@ -153,7 +144,8 @@ class CardReaderActivity : BaseActivity() { if (cardReaderResponse != null) { if (cardReaderResponse.status === Status.LOADING) { presentCardTv.visibility = View.GONE - mainView.setBackgroundColor(resources.getColor(R.color.turquoise)) + mainView.setBackgroundColor(ContextCompat.getColor(this, + R.color.turquoise)) supportActionBar?.show() animation.playAnimation() animation.repeatCount = LottieDrawable.INFINITE @@ -164,12 +156,7 @@ class CardReaderActivity : BaseActivity() { val intent = Intent(this, CardSummaryActivity::class.java) val bundle = Bundle() bundle.putParcelable(CardReaderResponse::class.simpleName, cardReaderResponse) - intent.putExtra("bundle", bundle) -// intent.putExtra(CardSummaryActivity.STATUS_KEY, cardReaderResponse.status.toString()) -// intent.putExtra(CardSummaryActivity.TICKETS_KEY, cardReaderResponse.nbTicketsLeft) -// intent.putExtra(CardSummaryActivity.CONTRACT, cardReaderResponse.contract) -// intent.putExtra(CardSummaryActivity.CARD_TYPE, cardReaderResponse.cardType) - + intent.putExtra(Bundle::class.java.simpleName, bundle) startActivity(intent) } } else { @@ -200,10 +187,7 @@ class CardReaderActivity : BaseActivity() { if (!seSelectionResult.hasActiveSelection()) { Timber.e("PO Not selected") - val error = String.format( - getString(R.string.card_invalid_desc), - "a case of PO Not selected" - ) + val error = getString(R.string.card_invalid_desc) changeDisplay( CardReaderResponse( status = Status.INVALID_CARD, @@ -217,12 +201,12 @@ class CardReaderActivity : BaseActivity() { } Timber.i("PO Type = ${ticketingSession.poTypeName}") - if (CalypsoInfo.PO_TYPE_NAME_CALYPSO != ticketingSession.poTypeName) { + if (CalypsoInfo.PO_TYPE_NAME_CALYPSO_05h != ticketingSession.poTypeName && + CalypsoInfo.PO_TYPE_NAME_CALYPSO_32h != ticketingSession.poTypeName && + CalypsoInfo.PO_TYPE_NAME_NAVIGO_05h != ticketingSession.poTypeName + ) { val cardType = ticketingSession.poTypeName ?: "Unknown card" - val error = String.format( - getString(R.string.card_invalid_small_desc), - cardType.trim { it <= ' ' } - ) + val error = getString(R.string.card_invalid_desc) changeDisplay( CardReaderResponse( status = Status.INVALID_CARD, @@ -233,10 +217,24 @@ class CardReaderActivity : BaseActivity() { ) ) return - } else { - Timber.i("A Calypso PO selection succeeded.") - newAppState = AppState.CARD_STATUS } + + if (!ticketingSession.checkStructure()) { + val error = getString(R.string.card_invalid_structure) + changeDisplay( + CardReaderResponse( + status = Status.INVALID_CARD, + cardType = null, + contract = null, + validation = null, + errorMessage = error + ) + ) + return + } + + Timber.i("A Calypso PO selection succeeded.") + newAppState = AppState.CARD_STATUS } ReaderEvent.EventType.CARD_REMOVED -> { currentAppState = AppState.WAIT_SYSTEM_READY @@ -256,20 +254,22 @@ class CardReaderActivity : BaseActivity() { ReaderEvent.EventType.CARD_INSERTED, ReaderEvent.EventType.CARD_MATCHED -> { GlobalScope.launch { try { - withContext(Dispatchers.Main){ + withContext(Dispatchers.Main) { progress.show() } - val validationResult = withContext(Dispatchers.IO){ - if (ticketingSession.analyzePoProfile()) { - ticketingSession.launchValidationProcedure(this@CardReaderActivity, locationFileManager.getLocations()) - } - else{ + val validationResult = withContext(Dispatchers.IO) { + if (ticketingSession.checkStartupInfo()) { + ticketingSession.launchValidationProcedure( + this@CardReaderActivity, + locationFileManager.getLocations() + ) + } else { null } } - withContext(Dispatchers.Main){ + withContext(Dispatchers.Main) { progress.dismiss() changeDisplay(validationResult) } @@ -294,8 +294,6 @@ class CardReaderActivity : BaseActivity() { } } } - else -> { - } } Timber.i("New state = $currentAppState") } @@ -306,13 +304,13 @@ class CardReaderActivity : BaseActivity() { } } - fun dismissProgress() { + private fun dismissProgress() { if (progress.isShowing) { progress.dismiss() } } - fun showNoProxyReaderDialog(t: Throwable) { + private fun showNoProxyReaderDialog(t: Throwable) { val builder = AlertDialog.Builder(this) builder.setTitle(R.string.error_title) builder.setMessage(t.message) @@ -356,7 +354,7 @@ class CardReaderActivity : BaseActivity() { status = status, nbTicketsLeft = 7, contract = "Season Pass", - cardType = CalypsoInfo.PO_TYPE_NAME_CALYPSO, + cardType = CalypsoInfo.PO_TYPE_NAME_CALYPSO_05h, validation = null, eventDate = Date() ) diff --git a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardSummaryActivity.kt b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardSummaryActivity.kt index 5eb87c6..fc84162 100644 --- a/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardSummaryActivity.kt +++ b/validator-app/src/main/kotlin/org/eclipse/keyple/demo/validator/ui/activities/CardSummaryActivity.kt @@ -14,6 +14,7 @@ package org.eclipse.keyple.demo.validator.ui.activities import android.media.MediaPlayer import android.os.Bundle import android.view.View +import androidx.core.content.ContextCompat import kotlinx.android.synthetic.main.activity_card_summary.animation import kotlinx.android.synthetic.main.activity_card_summary.bigText import kotlinx.android.synthetic.main.activity_card_summary.location_time @@ -34,14 +35,14 @@ class CardSummaryActivity : BaseActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_card_summary) - val bundle = intent.getBundleExtra("bundle")!! + val bundle = intent.getBundleExtra(Bundle::class.java.simpleName)!! val cardReaderResponse = bundle.getParcelable(CardReaderResponse::class.simpleName) when (cardReaderResponse?.status) { Status.SUCCESS -> { animation.setAnimation("tick_white.json") - mainView.setBackgroundColor(resources.getColor(R.color.green)) + mainView.setBackgroundColor(ContextCompat.getColor(this, R.color.green)) bigText.setText(R.string.valid_main_desc) @@ -70,7 +71,7 @@ class CardSummaryActivity : BaseActivity() { } Status.INVALID_CARD -> { animation.setAnimation("error_white.json") - mainView.setBackgroundColor(resources.getColor(R.color.orange)) + mainView.setBackgroundColor(ContextCompat.getColor(this, R.color.orange)) bigText.setText(R.string.card_invalid_main_desc) location_time.text = cardReaderResponse.errorMessage @@ -79,7 +80,7 @@ class CardSummaryActivity : BaseActivity() { smallDesc.visibility = View.INVISIBLE } Status.EMPTY_CARD -> { - mainView.setBackgroundColor(resources.getColor(R.color.red)) + mainView.setBackgroundColor(ContextCompat.getColor(this, R.color.red)) animation.setAnimation("error_white.json") bigText.text = cardReaderResponse.errorMessage @@ -89,7 +90,7 @@ class CardSummaryActivity : BaseActivity() { smallDesc.visibility = View.INVISIBLE } else -> { - mainView.setBackgroundColor(resources.getColor(R.color.red)) + mainView.setBackgroundColor(ContextCompat.getColor(this, R.color.red)) animation.setAnimation("error_white.json") bigText.setText(R.string.error_main_desc) diff --git a/validator-app/src/main/res/drawable/ic_logo_calypso.xml b/validator-app/src/main/res/drawable/ic_logo_calypso.xml new file mode 100644 index 0000000..d6a085e --- /dev/null +++ b/validator-app/src/main/res/drawable/ic_logo_calypso.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/validator-app/src/main/res/layout/activity_card_reader.xml b/validator-app/src/main/res/layout/activity_card_reader.xml index 5a86c2d..f832657 100644 --- a/validator-app/src/main/res/layout/activity_card_reader.xml +++ b/validator-app/src/main/res/layout/activity_card_reader.xml @@ -88,9 +88,9 @@ 280dp 250dp - - 183dp - 72dp \ No newline at end of file diff --git a/validator-app/src/main/res/values/dimens.xml b/validator-app/src/main/res/values/dimens.xml index 8b68c79..6207b41 100644 --- a/validator-app/src/main/res/values/dimens.xml +++ b/validator-app/src/main/res/values/dimens.xml @@ -25,9 +25,6 @@ 100dp 10dp - 163dp - 52dp - 20dp 100dp 210dp diff --git a/validator-app/src/main/res/values/strings.xml b/validator-app/src/main/res/values/strings.xml index 97b8cab..b994696 100644 --- a/validator-app/src/main/res/values/strings.xml +++ b/validator-app/src/main/res/values/strings.xml @@ -12,8 +12,6 @@ Reader plugin : %s Sam plugin : %s Please present your contactless support - Invalid on this network - This is a \"%s\" card. No trips left Expired title No valid title detected @@ -45,8 +43,11 @@ Card not valid - This is %s. + Selection error : AID not found. Invalid card + Invalid on this network + Selection error : Unknown structure. + This is a \"%s\" card. You must accept the requested permissions to continue. Please relaunch the app.