Amadis

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Current »

Basic transaction

A basic OLA transaction includes the following steps:

OLA classes instantiation (once at startup)

private val ola: Ola
private val contact: OlaContact
private val contactless: OlaContactless
private val emv: OlaEmv
private val dev: Dev
private val publicKey: OlaPublicKey
...

init {
    ola = Ola.getInstance()
    contact = OlaContact.getInstance()
    contactless = OlaContactless.getInstance()
    emv = OlaEmv.getInstance()
    publicKey = OlaPublicKey()
    ...
}

OLA component initialisation (once at startup)

/**
 * Configures OLA layer (key files paths, trace, etc...)
 */
private fun olaConfig() {
    val path = byteArrayOf(0xdf.toByte(), 0x01, 28)
    val pathValue = "/data/data/ca.amadis.tester/"
    val cakeys = byteArrayOf(0xdf.toByte(), 0x02, 34)
    val cakeysValue = "/data/data/ca.amadis.tester/CAKeys"
    val crl = byteArrayOf(0xdf.toByte(), 0x03, 31)
    val crlValue = "/data/data/ca.amadis.tester/CRL"
    val efl = byteArrayOf(0xdf.toByte(), 0x04, 31)
    val eflValue = "/data/data/ca.amadis.tester/EFL"
    val pcsc = byteArrayOf(0xdf.toByte(), 0x05, 0x00) // pcsc not used
    val lang = byteArrayOf(0xdf.toByte(), 0x06, 0x00) // lang not used
    val runtimeParams = byteArrayOf(0xdf.toByte(), 0x07, 0x07, 0x00, 0x0F, 0x04, 0x0F, 0x0A, 0x00, 0x01)
    val traceLevel = byteArrayOf(0xdf.toByte(), 0x08, 0x01, 0x05)
    val pollingTechno = byteArrayOf(0xdf.toByte(), 0x10, 0x01, 0x04) // Contactless only
    var sredMode: ByteArray
    if (BuildConfig.FLAVOR.lowercase().contains("nosred")) {
        sredMode = byteArrayOf(0xdf.toByte(), 0x17, 0x01, 0x00) // SRED mode not active
    } else {
        sredMode = byteArrayOf(0xdf.toByte(), 0x17, 0x01, 0x01) // SRED mode active
    }
  
    try {
        val out = ByteArrayOutputStream()
        out.write(path)
        out.write(pathValue.toByteArray())
        out.write(cakeys)
        out.write(cakeysValue.toByteArray())
        out.write(crl)
        out.write(crlValue.toByteArray())
        out.write(efl)
        out.write(eflValue.toByteArray())
        out.write(pcsc)
        out.write(lang)
        out.write(runtimeParams)
        out.write(traceLevel)
        out.write(pollingTechno)
        out.write(sredMode)
        
        ola.initializeAtStartUp(out.toByteArray())
  
    } catch (e: IOException) {
    }
}

AID configuration

private fun terminalContactlessConfig() {

    Log.v("Transaction", "terminalContactlessConfig")
    
    val contactlessCfg = arrayOf(
        ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x10, 0x10)), 
                       true, 
                       2, 
                       mcCfg),
        ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x20, 0x10)),
                       true, 
                       2, 
                       mcCfg),
        ...)
    
    contactless.flushAIDSupported()
    
    for (config in contactlessCfg) {
        contactless.addAIDSupported(
            config.aid,
            config.partial,
            config.kernelId.toByte(),
            config.additionalData
        )
    }
    
    contactless.commitSupportedAIDs()
}

Keys configuration

private fun terminalPublicKeyConfig() {

    Log.v("Transaction", "terminalPublicKeyConfig")
    val publicKeyCfg = arrayOf(
        /* MasterCard */
        PublicKeyCfg(byteArrayOf(0xa0.toByte(), 0x00, 0x00, 0x00, 0x03),
                     0x92.toByte(),
                     byteArrayOf(0x99.toByte(), 0x6a, 0xf5.toByte(), 0x6f, 0x56, 0x91.toByte(), 0x87.toByte(), 0xd0.toByte(), 0x92.toByte(), 0x93.toByte(), 0xc1.toByte(), 0x48, 0x10, 0x45, 0x0e, 0xd8.toByte(), 0xee.toByte(), 0x33, 0x57, 0x39, 0x7b, 0x18, 0xa2.toByte(), 0x45, 0x8e.toByte(), 0xfa.toByte(), 0xa9.toByte(), 0x2d, 0xa3.toByte(), 0xb6.toByte(), 0xdf.toByte(), 0x65, 0x14, 0xec.toByte(), 0x06, 0x01, 0x95.toByte(), 0x31, 0x8f.toByte(), 0xd4.toByte(), 0x3b, 0xe9.toByte(), 0xb8.toByte(), 0xf0.toByte(), 0xcc.toByte(), 0x66, 0x9e.toByte(), 0x3f, 0x84.toByte(), 0x40, 0x57, 0xcb.toByte(), 0xdd.toByte(), 0xf8.toByte(), 0xbd.toByte(), 0xa1.toByte(), 0x91.toByte(), 0xbb.toByte(), 0x64, 0x47, 0x3b, 0xc8.toByte(), 0xdc.toByte(), 0x9a.toByte(), 0x73, 0x0d, 0xb8.toByte(), 0xf6.toByte(), 0xb4.toByte(), 0xed.toByte(), 0xe3.toByte(), 0x92.toByte(), 0x41, 0x86.toByte(), 0xff.toByte(), 0xd9.toByte(), 0xb8.toByte(), 0xc7.toByte(), 0x73, 0x57, 0x89.toByte(), 0xc2.toByte(), 0x3a, 0x36, 0xba.toByte(), 0x0b, 0x8a.toByte(), 0xf6.toByte(), 0x53, 0x72, 0xeb.toByte(), 0x57, 0xea.toByte(), 0x5d, 0x89.toByte(), 0xe7.toByte(), 0xd1.toByte(), 0x4e, 0x9c.toByte(), 0x7b, 0x6b, 0x55, 0x74, 0x60, 0xf1.toByte(), 0x08, 0x85.toByte(), 0xda.toByte(), 0x16, 0xac.toByte(), 0x92.toByte(), 0x3f, 0x15, 0xaf.toByte(), 0x37, 0x58, 0xf0.toByte(), 0xf0.toByte(), 0x3e, 0xbd.toByte(), 0x3c, 0x5c, 0x2c, 0x94.toByte(), 0x9c.toByte(), 0xba.toByte(), 0x30, 0x6d, 0xb4.toByte(), 0x4e, 0x6a, 0x2c, 0x07, 0x6c, 0x5f, 0x67, 0xe2.toByte(), 0x81.toByte(), 0xd7.toByte(), 0xef.toByte(), 0x56, 0x78, 0x5d, 0xc4.toByte(), 0xd7.toByte(), 0x59, 0x45, 0xe4.toByte(), 0x91.toByte(), 0xf0.toByte(), 0x19, 0x18, 0x80.toByte(), 0x0a, 0x9e.toByte(), 0x2d, 0xc6.toByte(), 0x6f, 0x60, 0x08, 0x05, 0x66, 0xce.toByte(), 0x0d, 0xaf.toByte(), 0x8d.toByte(), 0x17, 0xea.toByte(), 0xd4.toByte(), 0x6a, 0xd8.toByte(), 0xe3.toByte(), 0x0a, 0x24, 0x7c, 0x9f.toByte()),
                     byteArrayOf(0x03),
                     byteArrayOf(0x24, 0x12, 0x31)),
        ...)
        
    publicKey.flush()

    for (config in publicKeyCfg) {
        val maxReached = SingleObjectHolder(false)
        val key: PublicKeyData = publicKey.PublicKeyData(
            config.rid,
            config.idx,
            config.modulus,
            config.exponentValue,
            config.expirDate
        )
        publicKey.add(key, null, maxReached)
    }

    publicKey.commit()
}

Transaction related data ()

private fun txnSetTransactionRelatedData() {

    Log.v("Transaction", "txnSetTransactionRelatedData")
    emv.setTag(OlaTag.TRANSACTION_DATE, byteArrayOf(0x20, 0x10, 0x28))                          // 9A
    emv.setTag(OlaTag.TRANSACTION_TIME, byteArrayOf(0x12, 0x00, 0x00))                          // 9F21
    emv.setTag(OlaTag.AMOUNT_AUTHORISED, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x01, 0x50))       // 9F02
    emv.setTag(OlaTag.AMOUNT_OTHER_NUM, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x50))        // 9F03
    emv.setTag(OlaTag.TRANSACTION_CURRENCY_CODE, byteArrayOf(0x08, 0x40))                       // 5F2A
    emv.setTag(OlaTag.TRANSACTION_CURRENCY_EXPONENT, byteArrayOf(0x02))                         // 5F36
    emv.setTag(OlaTag.TRANSACTION_TYPE, byteArrayOf(0x00))                                      // 9C

    /*
      * Other possible tags to set here:
      * Account Type (5F57)
      * Transaction Category Code (9F53)
      *
      * All other tags must be set through the combinations!
      */
    emv.setTag(0x9F53, byteArrayOf(0x11));
}

Preprocessing

private fun txnPreProcess(): OlaError {

    Log.v("Transaction", "txnPreProcess")
    val ret = contactless.preprocess()
    if (ret != OlaError.OLA_OK) {
        Log.e("Transaction", "txnPreProcess - contactless pre-process failed")
    }
    return ret
}

Card detection

private fun txnGetCard(): Int {

    Log.v("Transaction", "txnGetCard - **** PRESENT CARD ****")
    val foundTechnos = dev.technoPolling(30)
    Log.v("Transaction", "txnGetCard - result of foundTechnos: $foundTechnos")
    return if (foundTechnos == 4) {
        clcEMV_CARD //TODO remove hard-coding
    } else {
        0
    }
}

Do transaction

private fun doTransaction(): OlaError {

    Log.v("Transaction", "doTransaction - buildCandidateList")
    val nbCandidatesHolder = SingleObjectHolder<Int>()
    var ret = contactless.buildCandidateList(nbCandidatesHolder)
    if (ret != OlaError.OLA_OK) {
        Outcome.showOutcome(contactless, ret)
    }
    
    Log.v("Transaction", "doTransaction - nb of candidates: ${nbCandidatesHolder.get()}")
    Log.v("Transaction", "doTransaction - finalSelectCandidate")
    val kernel_id_holder = SingleObjectHolder<Byte>()
    ret = contactless.finalSelectCandidate(1, kernel_id_holder)
    if (ret != OlaError.OLA_OK) {
        Outcome.showOutcome(contactless, ret)
    }

    Log.v("Transaction", "doTransaction - doTransaction")
    ret = contactless.doTransaction()
    Outcome.showOutcome(contactless, ret)
    if (ret == OlaError.OLA_OK) {
        val bytesHolder = SingleObjectHolder<ByteArray>()
        for (olaTag in batch) {
            val result = emv.getTag(olaTag, bytesHolder)
            if (result == OlaError.OLA_OK) {
                Log.v("Transaction", "doTransaction - result for getTag(): $result => " +
                      Utils.bytesToHex(bytesHolder.get()))
            } else {
                Log.v("Transaction", "doTransaction - result for getTag(): $result => tag missing")

            }
        }
    }

    Log.d("Transaction", "doTransaction - **** REMOVE CARD **** ")

    contactless.clean();

    return ret
}

Outcome and errors management

At the end of a transaction, the OlaOutcomeParameter structure should be retrieved for analysis. To retrieve the outcome, use the following code:

val olaOutcomeParameterHolder = Holders.SingleObjectHolder<OlaOutcomeParameter>()
val ret = agnos.olaContactlessGetOutcome(olaOutcomeParameterHolder)
val olaOutcomeParameter = olaOutcomeParameterHolder.get()

Please refer to the Javadoc/Kotlindoc for more information on the OlaOutcomeParameter class.

The first element to verify is the value of the outcome member. In a Tap to Phone environement, its value should be either of the following:

  • SelectNext,

  • TryAgain,

  • Declined

  • OnlineRequest,

  • EndApplication

The transaction should never be accepted offline.

EndApplication outcome

In case the outcome is EndApplication, the application should look into OlaErrorIndicator element to understand what possibly caused the premature end of the transaction. To retrieve that information, one should use the following API:

val olaErrorIndicatorHolder = Holders.SingleObjectHolder<OlaErrorIndicator>()
val ret = agnos.olaContactlessGetErrorIndicator(olaErrorIndicatorHolder)
val olaErrorIndicator: OlaErrorIndicator = errorHolder.get()

The OlaErrorIndicator class contains different levels of errors represented by the following members:

OlaErrorL2 errorL2
OlaErrorL3 errorL3

Only one of them can be filled at once.

Please refer to the Javadoc/Kotlindoc for more information on the OlaErrorL2 and OlaErrorL3 values.

  • No labels