Switcloud L2

SwitcloudL2 is the lowest item of the Switcloud infrastructure. It simply wraps native EMV L2 frameworks into a Kotlin glue exposing GLA interfaces. This allows SwitcloudClt (or payment applications) to always use the exact same API to drive any EMV L2 framework.

Warning

Directly integrating SwitcloudL2 into a payment application doesn't benefit from the the Switcloud infrastructure.

Note

To integrate Switcloud L2 into your application, please follow the instruction in deliverables.

Data model

Switcloud L2 follows pretty much the Moka data model (or rather the GLA data model).

Specificities

Delegated security

On its way...

User messages

Switcloud L2 uses a Kotlin Flow mechanism to broadcast events such as user messages (UIRD). Any application that wants to receive those events should simply collect them from the SwitcloudL2.userInfo variable, using, for example, the below code:

scope.launch(Dispatchers.IO) {
    SwitcloudL2.getInstance().userInfo.collect { userInfo ->
        println(userInfo)
    }
}

The data collected for the UIRD event is in a TLV format.

The following tags are currently in use:

  • DFA01B for LED management: 1 byte long, high nibble indicates the LED to switch, low nibble indicates 'on' or 'off'.
  • DF8116 for UIRD: see Mastercard specfification for its content.

Note

For the sake of simplicity, Moka only sends 'on' LED events. That means that if a "LED n°2 + 'on'" event is received the application should make sure that all preceeding LEDs are also switched on (if not already) and all following LEDs should be switched off (if not already).

Samples

Here is a very simple transaction based on SwitcloudL2 with delegated security (optional):

// Retrieve the SwitcloudL2 singlton
val switcloudL2 = SwitcloudL2.getInstance()

// Set the security callback (optional)
switcloudL2.setSecCallback(this)

// Get a pointer to 2 services (GLAse and Reader)
val glase = switcloudL2.glase()
val reader = switcloudL2.reader()

// Configure the Reader service.
// The most important parameter here is the polling timeout (NFC)
val readerParams = byteArrayOf(
    // Trace (on)
    0xDF.toByte(), 0xA0.toByte(), 0x18, 0x01, 0x01,
    // Timeout interfaces detection (30 sec)
    0xDF.toByte(), 0xA0.toByte(), 0x08, 0x01, 0x1e,
)
reader.configure(readerParams)

// Transaction loop can start here
// The code above those lines can be called only once at the application
// startup. The code below those lines though should be called for each
// transaction.

switcloudL2.initializeServices()

// Configure the GLAse service (see the other snippets for more information)
glase.configureEntryPoint(entryPointConfiguration)
// One call to 'addCombination' per required combination
glase.addCombination(combinationMastercard)
glase.addCombination(combinationVisa)
// One call to 'addCAKey' per required CAPK
glase.addCAKey(caKey)

// Start A (see other snippets for information about TRD)
val preProcessingResult = glase.preProcessing(trd)
// Start B
val detectedInterface = glase.protocolActivation(null)
// Start C
val combinationSelectionResult = glase.combinationSelection()
// Start D
val kernelActivationResult = glase.kernelActivation(null)

// Fetch something at the end of the transaction
var tag = byteArrayOf(0x95.toByte())
var tlv = glase.getTag(tag)

switcloudL2.cleanupServices()

The snippets below show some configuration bits:

// Mastercard combination
val combinationMastercard = byteArrayOf(
    0xE1.toByte(), 0x32,
    // AID
    0x9F.toByte(), 0x06, 0x07, 0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x10, 0x10,
    // Kernel ID
    0xDF.toByte(), 0x81.toByte(), 0x0C, 0x01, 0x02,
    // Partial Selection flag
    0xDF.toByte(), 0xA0.toByte(), 0x10, 0x01, 0x01,
    // Transaction Type
    0x9c.toByte(), 0x01, 0x00,
    // Configuration container + length
    0xE2.toByte(), 0x19,
    // Reader Contactless Floor Limit
    0xDF.toByte(), 0x81.toByte(), 0x23, 0x06, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
    // Reader Contactless Transaction Limit (No On-Device CVM)
    0xDF.toByte(), 0x81.toByte(), 0x24, 0x06, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00,
    // Security Capability
    0xDF.toByte(), 0x81.toByte(), 0x1F, 0x01, 0x08
)

// Visa combination
val combinationVisa = byteArrayOf(
    0xE1.toByte(), 0x52,
    // Kernel ID
    0xDF.toByte(), 0x81.toByte(), 0x0C, 0x01, 0x03,
    // AID
    0x9F.toByte(), 0x06, 0x07, 0xA0.toByte(), 0x00, 0x00, 0x00, 0x03, 0x10, 0x10,
    // Partial Selection flag
    0xDF.toByte(), 0xA0.toByte(), 0x10, 0x01, 0x01,
    // Transaction Type
    0x9c.toByte(), 0x01, 0x00,
    // Configuration container + length
    0xE2.toByte(), 0x45,
    // TTQ
    0x9F.toByte(), 0x66, 0x04, 0x27, 0x00, 0x40, 0x00,
    0x9F.toByte(), 0x1B, 0x04, 0x00, 0x00, 0x27, 0x10,
    0x9F.toByte(), 0x82.toByte(), 0x0D, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
    0x9F.toByte(), 0x82.toByte(), 0x0E, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
    0xDF.toByte(), 0xA0.toByte(), 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
    0xDF.toByte(), 0xA0.toByte(), 0x14, 0x01, 0x01,
    0xDF.toByte(), 0xA0.toByte(), 0x15, 0x01, 0x01,
    0xDF.toByte(), 0xA0.toByte(), 0x16, 0x01, 0x01,
    0xDF.toByte(), 0xA0.toByte(), 0x17, 0x01, 0x01,
    0x9F.toByte(), 0x1A, 0x02, 0x08, 0x40
)

// CA Key
val caKey = byteArrayOf(
    0xDF.toByte(), 0xA0.toByte(), 0x23, 0x82.toByte(), 0x01, 0x22,
    // RID
    0xA0.toByte(), 0x00, 0x00, 0x00, 0x04,
    // Index
    0x00,
    // Hash indicator (RFU)
    0x00,
    // Algo indicator (RFU)
    0x00,
    // Modulus length
    0x00,
    0xA0.toByte(),
    // Modulus
    0x9C.toByte(), 0x6B, 0xE5.toByte(), 0xAD.toByte(), 0xB1.toByte(), 0x0B, 0x4B, 0xE3.toByte(),
    0xDC.toByte(), 0xE2.toByte(), 0x09, 0x9B.toByte(), 0x4B, 0x21, 0x06, 0x72, 0xB8.toByte(),
    0x96.toByte(), 0x56, 0xEB.toByte(), 0xA0.toByte(), 0x91.toByte(), 0x20, 0x4F, 0x61, 0x3E,
    0xCC.toByte(), 0x62, 0x3B, 0xED.toByte(), 0xC9.toByte(), 0xC6.toByte(), 0xD7.toByte(), 0x7B,
    0x66, 0x0E, 0x8B.toByte(), 0xAE.toByte(), 0xEA.toByte(), 0x7F, 0x7C, 0xE3.toByte(), 0x0F,
    0x1B, 0x15, 0x38, 0x79, 0xA4.toByte(), 0xE3.toByte(), 0x64, 0x59, 0x34, 0x3D, 0x1F,
    0xE4.toByte(), 0x7A, 0xCD.toByte(), 0xBD.toByte(), 0x41, 0xFC.toByte(), 0xD7.toByte(), 0x10,
    0x03, 0x0C, 0x2B, 0xA1.toByte(), 0xD9.toByte(), 0x46, 0x15, 0x97.toByte(), 0x98.toByte(),
    0x2C, 0x6E, 0x1B, 0xDD.toByte(), 0x08, 0x55, 0x4B, 0x72, 0x6F, 0x5E, 0xFF.toByte(), 0x79,
    0x13, 0xCE.toByte(), 0x59, 0xE7.toByte(), 0x9E.toByte(), 0x35, 0x72, 0x95.toByte(),
    0xC3.toByte(), 0x21, 0xE2.toByte(), 0x6D, 0x0B, 0x8B.toByte(), 0xE2.toByte(), 0x70,
    0xA9.toByte(), 0x44, 0x23, 0x45, 0xC7.toByte(), 0x53, 0xE2.toByte(), 0xAA.toByte(), 0x2A,
    0xCF.toByte(), 0xC9.toByte(), 0xD3.toByte(), 0x08, 0x50, 0x60, 0x2F, 0xE6.toByte(),
    0xCA.toByte(), 0xC0.toByte(), 0x0C, 0x6D, 0xDF.toByte(), 0x6B, 0x8D.toByte(), 0x9D.toByte(),
    0x9B.toByte(), 0x48, 0x79, 0x0B, 0x82.toByte(), 0x6B, 0x04, 0x2A, 0x07, 0xF0.toByte(),
    0xE5.toByte(), 0xAE.toByte(), 0x52, 0x6A, 0x3D, 0x3C, 0x4D, 0x22, 0xC7.toByte(), 0x2B,
    0x9E.toByte(), 0xAA.toByte(), 0x52, 0xEE.toByte(), 0xD8.toByte(), 0x89.toByte(), 0x38, 0x66,
    0xF8.toByte(), 0x66, 0x38, 0x7A, 0xC0.toByte(), 0x5A, 0x13, 0x99.toByte(),
    // Padding zeros
    *ByteArray(96) { 0x00 },
    // Exponent length
    0x01,
    // Exponent
    0x03,
    0x00,
    0x00,
    // Hash
    *ByteArray(20) { 0x00 }
)

// Transaction Related Data
val trd = byteArrayOf(
    // Amount
    0x9F.toByte(), 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00,
    // Date
    0x9A.toByte(), 0x03, 0x23, 0x08, 0x01,
    // Transaction type
    0x9c.toByte(), 0x01, 0x00
)