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:
DFA01Bfor LED management: 1 byte long, high nibble indicates the LED to switch, low nibble indicates 'on' or 'off'.DF8116for 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
)