ADK-SDI-Client Programmers Guide
Audience
The SDI Server compat client libsdiclient is for existing projects migrating from direct ADK use to SDI server approach.
Organization
This guide relies on the ADK-SDI Programmers Guide. For the EMV part see ADK-EMV Contact Programmers Guide and ADK-EMV Contactless Programmers Guide. Other details can be found in ADK-TEC Programmers Guide, ADK-MSR Programmers Guide and ADK-NFC Programmers Guide.
Introduction
libsdiclient provides an API based approach for interfacing the SDI server. It was designed to reuse the EMV-ADK API, its client libraries, libEMV_CT_client and libEMV_CTLS_Client for serialisation. The EMV-ADK client libraries call the shared EMV link libraries that are configured to use SDI mode via libsdiprotocol.
Non-EMV-ADK extensions have been added using the same infrastructure since the SDI server was not able to handle multiple connections at time of design. The original library name was libsdiemvclient which is still available but deprecated. With the extension the new name was introduced: libsdiclient.
For more sample code please refer to EMV Test App from which most examples in this document originate from. You can find its source in VDE sample (ADK full package) in directory doc/vdedemo/EmvTestApp.
Getting Started
Dependencies
libsdiclient depends on
- libTLV_Util
- libEMV_CT_Client
- libEMV_CTLS_Client
The static clients call the EMV-ADK link libraries which results in more dependencies to these shared libraries
- libEMV_CT_Link
- libEMV_CTLS_Link
- libTLV_Util
- libsdiprotocol
- libipc
- libipctls
- liblog (Android: libvfilog)
# Makefile example based on ADK make rules# Should be added to applications or libraries using libsdiclient and libsdiprotocolLIBS += -lsdiclient -lEMV_CT_Client -lEMV_CTLS_ClientLIBS += -lsdiprotocol -lEMV_CT_Link -lEMV_CTLS_Link -lTLV_Util
Programming
Header files of libsdiclient are located in import/<platform>/include/sdiclient. Therefore they are included this way:
#include <sdiclient/sdi_emv.h> // for migration of EMV-ADK API#include <sdiclient/sdi_data.h> // provides SDI_fetchTxnTags#include <sdiclient/sdi_if.h> // SDI client interface for non-EMV// #define SDI_NFC_NAMESPACE_ONLY uncomment if applicable#include <sdiclient/sdi_nfc.h> // compatiblity to NFC_Interface.h
EMV-API
By including sdi_emv.h EMV-ADK API functions are available as SDI counterpart. Their prefix changes from EMV_ to SDI_, e.g. EMV_SetTermData becomes SDI_SetTermData. There are some differences to note:
- On responses with transaction results the SDI Server will hold back card holder sensitive data unless the card's PAN appears to be whitelisted. SDI_CT_ContinueOffline() and SDI_CT_ContinueOnline() have an additional ouput parameter of type EMV_SDI_CT_TRANSRES_STRUCT containing SDI Server added obfuscated data. The same applies to SDI_CTLS_ContinueOffline() and SDI_CTLS_ContinueOnline() with EMV_SDI_CTLS_TRANSRES_STRUCT.
- SDI_fetchTxnTags() replaces both, EMV_CT_fetchTxnTags() and EMV_CTLS_fetchTxnTags(). The SDI server will switch to appropriate technology and apply its sensitive data filters.
- SDI_CT_SmartISO() and SDI_CTLS_SmartISO() as well as SDI_CT_SER_SmartISO() and SDI_CTLS_SER_SmartISO() are blocked by SDI server and can't be used. For custom direct chip card communication an SDI plugin should be used.
Excerpt from EMV TestApp supporting both, direct ADK and SDI compat client:
EMV_ADK_INFO CTLS_fetchTxnTags(unsigned long options, unsigned long* requestedTags, unsigned short noOfRequestedTags, unsigned char* tlvBuffer, unsigned short bufferLength, unsigned short* tlvDataLength ){ if(SdiMode) return SDI_fetchTxnTags(options, requestedTags, noOfRequestedTags, tlvBuffer, bufferLength, tlvDataLength); else return EMV_CTLS_fetchTxnTags(options, requestedTags, noOfRequestedTags, tlvBuffer, bufferLength, tlvDataLength);} EMV_ADK_INFO CTLS_EndTransaction(unsigned long options){ if(SdiMode) return SDI_CTLS_EndTransaction(options); else return EMV_CTLS_EndTransaction(options);}
SDI-API
Other SDI-API is provided by sdi_if.h. Most SDI commands, especially those with fewer parameters are provided by libsdi::SDI. In most cases the functions just return a success flag and in case of failure libsdi::SdiBase::getSdiSw12(), libsdi::SdiBase::getAdditionalResultValue() and libsdi::SdiBase::getClientError() can be called for fine-grained error handling. More complex command invocation is performed by instantiation of other libsdi::SdiBase subclasses and calling their setters for providing the input data, then starting command execution and finally invoking getters for accessing the result data from the SDI server response. These are
- libsdi::CardDetection for Technology Selection (SDI class 23)
- libsdi::SdiCrypt for cryptographic and some data interface functions (SDI classes 70 and 29)
- libsdi::ManualEntry for manual card data input (SDI command 21-02)
- libsdi::PED for PIN input functions (SDI class 22)
- libsdi::Dialog for display control of External PIN pad (SDI class 24)
- libsdi::SdiCmd Generic command for command and tags not (yet) supported by this library
Example:
libsdi::SdiCrypt crypt;crypt.open("myProvider");crypt.setInitialVector(myIntialVector);crypt.encrypt(myInputData, myCipherData);crypt.close();
- In general libsdiclient does not provide deprecated SDI commands. If other commands are missing because not yet provided here, the SDI_SendReceive() method from libsdiprotocol can be directly called but the input data has to be set up by your application and the output data has to be parsed. There is a generic class libsdi::SdiCmd supporting convenient getters and setters for TLV based SDI commands.
NFC-API
The header file sdi_nfc.h has a compiler switch SDI_NFC_NAMESPACE_ONLY that deactivates the NFC-ADK complient function aliases for the case of name conflicts. In case of client side error or negative SDI response the return value will be EMB_APP_COMM_ERROR or VAS_COMM_ERR. In this case the negative SDI SW12 value or the client side error can be read with libsdi::getNfcSW12() and libsdi::getNfcClientError().
VOS3 API
For compatibility reasons these (VOS2-)interfaces are provided on VOS3. Parts of this functionality require VOS3-CARDS plugin.
- If the plugin is installed SDI is no more P2PE domain.
- ADK-EMV (ADK-EMV Contact Programmers Guide, ADK-EMV Contactless Programmers Guide)
- ADK-TEC (ADK-TEC Programmers Guide)
- ADK-MSR (ADK-MSR Programmers Guide)
- ADK-NFC (ADK-NFC Programmers Guide)
- libsdi::SDI::setManualPAN() replaces ADK-SEC secPutTransactionData() for the use case that PAN from manual input is required for ISO-0 PIN block.
Connection Handling
The connection to the SDI Server is implicitly established when required and will not be closed unless libsdiprotocol's SDI_Disconnect() is called. The default destination is 127.0.0.1::12000 but other IP addresses or connection types can be configured with SDI_ProtocolInit() before a connection is set-up or SDI_Disconnect() can be used to apply a configuration change.
Example 1 - Simple TCP/IP connection:
// {"server":{"host":"127.0.0.1","port":12000}}vfiipc::JSObject server, config;server("host") = "127.0.0.1";server("port") = 12000;config("server") = server;return SDI_ProtocolInit(0, config.dump().c_str());
Example 2 - TLS connection:
// {"server":{"ca":["CA_1.pem","CA_2.pem"],"host":"vfi-terminal","port":12000}}vfiipc::JSObject server, config;server("host") = "vfi-terminal";server("port") = 12000;server("ca")[0] = "CA_1.pem";server("ca")[1] = "CA_2.pem";config("server") = server;return SDI_ProtocolInit(0, config.dump().c_str());
Example 3 - Unix Domain Socket connection:
// {"server":{"socket":"/tmp/sdi.socket"}}vfiipc::JSObject server, config;server("socket") = "/tmp/sdi.socket"config("server") = server;return SDI_ProtocolInit(0, config.dump().c_str());
Example 4 - Serial connection:
// {"server":{"serial":"/dev/ttyACM0"}}vfiipc::JSObject server, config;server("serial") = "/dev/ttyACM0"config("server") = server;return SDI_ProtocolInit(0, config.dump().c_str());
Sample Code
Card Detection
The card detection demo code starts all three SDI card detection modes for illustrating the differences.
bool startCardDetection(libsdi::CardDetection& cardDetection, unsigned char tech, int variant){ libsdi::CardDetection::DetectionMode mode = libsdi::CardDetection::DETECTION_MODE_BLOCKING; if (variant == SDI_BLOCKING) { mode = libsdi::CardDetection::DETECTION_MODE_BLOCKING; SDI_SetDataAvailableCallback(sdiDataAvailableCallback, NULL); } else if (variant == SDI_POLLING) { mode = libsdi::CardDetection::DETECTION_MODE_POLLING; } else if (variant == SDI_CALLBACK) { mode = libsdi::CardDetection::DETECTION_MODE_CALLBACK; cardDetection.setCallback(sdiCardDetectionCallback, NULL); } cardDetection.setDetectionMode(mode); std::vector<unsigned char> ucOptions(16, 0); ucOptions[4] = EMV_CT_TRY_PPS|EMV_CT_DETECT_WRONG_ATR; cardDetection.setTecStartOptions(ucOptions); return cardDetection.startSelection(tech, 30 == 0);}
Chip Card Processing
After card detection results in CTS_CHIP (contact chip card detected) the card should be powered and in this example the PSE is read. As written above card communications is blocked by SDI server but the EMV TestApp has a plugin that sends a Select PSE command for command id SDI_PLUGIN_SELECT_PSE.
unsigned char atr[40];unsigned long atrLen;unsigned char status = isSdiMode() ? SDI_CT_SmartReset(EMV_CT_TRY_PPS | EMV_CT_DETECT_WRONG_ATR, atr, &atrLen); : EMV_CT_SmartReset(EMV_CT_TRY_PPS | EMV_CT_DETECT_WRONG_ATR, atr, &atrLen); if (status == EMV_ADK_SMART_STATUS_OK){ unsigned short rspLen; unsigned char rsp[261]; APP_TRACE_HEX(atr, atrLen, "ATR: \n"); unsigned char cmd[] = "\x00\xA4\x04\x00\x0E" "1PAY.SYS.DDF01"; APP_TRACE_HEX(cmd, sizeof cmd, "CMD: "); if (isSdiMode()) { std::vector<unsigned char> inOut; libsdi::SDI sdi; libsdi::SDI_SW12 sw12 = sdi.pluginCommand(SDI_PLUGIN_ID_EMVTESTAPP, SDI_PLUGIN_SELECT_PSE, inOut, inOut); if (sw12 == libsdi::SDI_SW12_SUCCESS) { if (inOut.size() > 2) { struct BTLVNode root; struct BTLVNode* rapdu; vBTLVInit(&root, NULL); if (iBTLVImport(&root, inOut.data() + 2, inOut.size() - 2, NULL, NULL) == 0 && (rapdu = pxBTLVFindTag(&root, "F0/DF01")) != NULL) { if (rapdu->uSize > sizeof rsp) { APP_TRACE("R-APDU too long: ", rapdu->uSize); status = EMV_ADK_SMART_STATUS_OVERFLOW; } else { rspLen = (unsigned short) rapdu->uSize; memcpy(rsp, rapdu->pucData, rapdu->uSize); } } vBTLVClear(&root); } } else { APP_TRACE("SDI plugin result: %04x", sw12); status = (sw12 & 0xFF00) == 0x9000 ? (unsigned char) sw12 : EMV_ADK_SMART_STATUS_EXCHG_ERR; } } else { status = EMV_CT_SmartISO(0, (unsigned short) sizeof cmd, cmd, &rspLen, rsp, sizeof rsp); } APP_TRACE("CT_SmartISO returns %d", status);}
SDI command mapping table
Callback Functions
Callbacks sent by SDI Server do not end a running command, instead the client API has to wait for the response. Meanwhile some types callback require a response which blocks the command execution. Therefore libsdiprotocol and libsdiclient support setting callback functions that are called by separate threads. This chapter lists up the SDI callbacks, the way a callback function can be registered and in which context it is invoked.
The Data Available callback is no SDI Server callback but sent once the SDI Server response is available on a command sent out by SDI_Send(). It is listed here for completeness.
- For callback types that do not expect a response the output parameter sizeOut of SDI_CALLBACK_TYPE has to be set to zero by the callback function.