Разработка приложений для платёжных терминалов Ingenico
Приветствую всех!
Скажите: интересовались ли вы хоть раз тем, как устроены и работают банковские платёжные терминалы, к которым вы прикладываете свою карту едва ли не ежедневно? Хотели ли вы узнать, как написать что-то своё под какое-нибудь из данных устройств?
Если ваш ответ — «Да», то этот пост определённо для вас.
Обычно тема программирования POS-terminal’ов покрыта завесой тайны, но сейчас мы постараемся её развеять. В ходе данной статьи разберёмся с азами разработки под такие девайсы. Узнаем, где скачать нужный софт, как его установить, а также, собственно, как скомпилировать и запустить нашу первую программу. Традиционно будет много интересного.
❯ Погнали!
В ходе сегодняшней статьи речь пойдёт о терминалах Ingenico на платформе Telium. Почему именно о них? Дело в том, что для получения софта для разработки обычно необходимо обратиться к вендору в вашем регионе и купить его там. В обычном доступе его нет.
Единственное, что удалось откопать, так это таковой для Ingenico. Именно поэтому писать мы будем именно для них. Повышает интерес и то, что это не какое-то древнее старьё, а всё ещё использующиеся в банках устройства.
Сразу предупреждаю, что порог вхождения в разработку под терминалы крайне высокий, а минимальное количество документации в открытом доступе ещё сильнее повышает его. Так что представленное ниже основано на моих собственных экспериментах и их успешных результатах. Оттого это и «ненормальное программирование».
Также напомню, что статья эта написана, как говорится, just for fun и предназначена для таких же энтузиастов как я. Автор не несёт ответственности за укокошенные терминалы, снесённое ПО государственной важности и прочие производственные моменты. Если ваша цель — написать коммерческое приложение, то вам в любом случае понадобится обратиться для этого к дистрибьютору Ingenico в вашей стране (в России это «Арком»).
❯ Обзор оборудования
Как уже было мною упомянуто, писать софт мы будем под терминалы Ingenico, а конкретно — под платформу Telium-2. И для начала — немного истории. Давным-давно во Франции существовала такая ассоциация как SAGEM. Акционерная компания имела большое количество подразделений и выпускала широкий ряд аппаратуры: телекоммуникационное оборудование, военная техника, мобильные телефоны, средства спутниковой связи, шифровальные устройства. Одним из её направлений были и платёжные терминалы, выпускавшиеся под линейкой SAGEM Monetel и работавшие на платформе Telium-1. В конце нулевых SAGEM была упразднена, а подразделение по выпуску платёжных терминалов досталось Ingenico. На тот момент у них уже была своя платформа, Unicapt32, но, заполучив Telium-1, они начали работу над развитием этой, куда более защищённой и совершенной платформы. Так появился Telium-2, вокруг которого и пойдёт наша статья. В настоящее время уже выпущена платформа Telium TETRA, но десятки тысяч устройств на базе Telium-2 всё ещё успешно эксплуатируются по всему миру, выполняя свою главную задачу — приём банковских карт и осуществление электронных транзакций. Терминалы на базе Telium надёжны, компактны, удобны в использовании, хорошо защищены и имеют современный и эргономичный дизайн. Есть также и минусы, но о них мы поговорим отдельно.
Итак, посмотрим на различных представителей POS-terminal’ов данной фирмы.
Старые терминалы на платформе Unicapt32: Ingenico 5100 и Ingenico 7910. Модели достаточно распространённые, под 7910 даже был написан софт для приёма платежей от ОСМП (она же Qiwi).
Пин-пад Ingenico 3050. Использовался совместно с терминалами Unicapt32.
POS-terminal SAGEM Monetel EFTsmart на платформе Telium-1. Тот самый предшественник нынешних Ingenico. Рядом пин-пад PPC30.
А вот ещё один пин-пад, SAGEM Monetel PP 30.
POS-terminal Ingenico ICT220 A98. ICT220 — самая популярная модель, думаю, каждый из вас по-любому хоть раз в жизни прикладывал карту к такому аппарату. Есть также версия ICT250 с цветным дисплеем, а также IPP320/350 — по сути тот же ICT220/250, но без принтера и управляемый извне (именно они стоят на кассах супермаркетов).
ICT220 C98. Это более новая версия предыдущего девайса, оснащённая новой прошивкой, соответствующей более новым стандартам безопасности. К слову говоря, пин-пады тоже имеют такое разделение, поэтому IPP220 A98 не будет работать на POS-terminal’е C98, ну и наоборот.
IPP220. Пин-пад для терминала ICT220/250, подключаемый к нему по USB.
IWL250. Портативный терминал с цветным дисплеем, используется в кафе, ресторанах, барах и других аналогичных заведениях. Увы, мой экземпляр в нерабочем состоянии.
IPA280. Терминал сбора данных на Windows CE с принтером и пин-падом. Железка крайне интересная, скорее всего, я даже напишу про неё отдельный пост.
ISMP. Мобильный POS-terminal, представляющий собой чехол для iPhone 4 со встроенным пин-падом на платформе Telium и сканером штрих-кодов.
Итак, как можно заметить, платформа Telium-2 представляет широкий выбор устройств для любого применения. Несмотря на выход более новых устройств, вы наверняка встретите упомянутые экземпляры на какой-нибудь из торговых точек вашего города.
❯ Внутренности
Остановимся поподробнее на ICT220. Именно для него мы и будем писать софт.
Характеристики терминала такие: процессор ARM9 (плюс отдельно ARM7 для шифрования), по шестнадцать мегабайт ОЗУ и Flash (но это из доступных пользователю, в реальности куда больше). Из интерфейсов — USB, RS-232, Ethernet, модем, USB-хост.
Внимание! Вскрытие терминала приведёт к стопроцентной его порче. Восстановить его после этого смогут только в представительстве вендора за немаленькие деньги (в большинстве случаев заблокированный терминал проще выкинуть, чем разбираться). Не разбирайте устройства, находящиеся в эксплуатации. Показанный далее экземпляр достался мне уже помершим. Про то, какими системами безопасности обладают POS-terminal’ы, я писал отдельный пост.
А вот и подопытный экземпляр. Он в нерабочем состоянии, так что единственное, что остаётся с ним сделать, так это разобрать его на запчасти.
Обратная сторона. USB-B для подключения к компьютеру и USB-A для внешних устройств. COM-порт выведен на разъём miniUSB (но сигнал там никакой не USB!). Порт телефонной линии. Для экономии места и принуждения к покупке проприетарных шнуров вместо стандартного 8P8C стоит 4P4C, соответственно, в кабеле используются только две средние витые пары. Снизу слоты под SAM-карты и незаметные с такого ракурса разъёмы под SIM и microSD.
Разбираем. На материнке куча проприетарных чипов. На отдельной плате размещён GSM-модем от Sagemcom. Дисплей имеет маркировку M0412 SHON58, гугл без понятия, что за модель и что за контроллер там стоит. На плате модема некий элемент, похожий на ячейку литиевого аккумулятора — это суперконденсатор. В выступе в виде половинки цилиндра на корпусе рядом с портами находится батарейка CR2450.
Антенна GSM-модема.
Модем, снятый с платы.
Материнская плата. Давно севшая батарейка, закрытые металлическим экраном компоненты. Считыватель смарт-карт закрыт контуром безопасности.
Из чипов на плате находятся:
- SI3018-FS, SI3056-FS — модемный чипсет
- SMsC LAN8700-AEZG — контроллер Fast Ethernet
- ZT3223E — преобразователь уровней RS-232
- MONEFT3X SPIIIM 1215 1T6239–1 — проприетарный шифропроцессор
- WE-MIDCOM 7090–37 GV1217LF1 — трансформатор Ethernet
- LB1838 — низковольтный драйвер биполярного ШД
Отковыряем защитный экран. Сидит он очень плотно, снять и не погнуть не вышло. Под ним процессор (тоже заказной), чипы памяти и ПЗУ. На базе чипсета MONEFT3X построены все терминалы на платформе Telium-2.
Принтер. Стандартные для терминала параметры — лента на пятьдесят семь миллиметров, триста восемьдесят четыре точки. Произведён фирмой Alps Electric, даташит сходу найти не смог.
Магнитная головка.
❯ Ставим софт
Ну что же, перейдём непосредственно к софту.
Для того, чтобы начать разработку под Telium-2, нам понадобится следующий набор софта:
- IngeDev (среда разработки и компилятор)
- Telium SDK (библиотеки и компоненты)
- SAT (Software authentication tool, средство для подписи ПО)
- LLT (Local loading tool, утилита для загрузки приложения в терминал)
Также будет нужен специальный «девелоперский» терминал. О том, где его взять, поговорим чуть позже.
Сам софт можно взять тут. Когда-то давно он был и тут, но ссылки давно померли. Из всей этой кучи нас интересует архив Installed SDKs, IngeDev.rar. Также нужна лежащая по соседству LLT последней версии. Можно и более старую, но пользоваться ей — одно мучение.
Ставим LLT. Попутно будут накатаны и драйверы. Особых проблем данный этап вызвать не должен.
Теперь очередь большого архива. Распаковываем его в корень вашего системного диска. Нас интересуют папки SDK 9.10.2, TELIUM Tools и Ingenico. Переходим в Ingenico и запускаем IngeDev.exe. Далее необходимо создать рабочее пространство, аналогично тому, как это делается в Eclipse (именно на базе данной IDE основана IngeDev).
Далее нужно подключить Telium SDK, для чего жмём на панели меню «Window», далее «Preferences», открываем «Installed Telium packages». Жмякаем «Add», выбираем SDKDescriptor.xml в папке с нужной вам версией SDK.
Теперь жмём «File», создаём новый «Telium project». Оставляем «Telium application», вводим желаемое имя и выбираем «Finish». У нас создастся новый проект, где в папке Src будут два файла: main.c и entry.c. В дальнейшем мы разберём их поподробнее.
/**
* \file Main.c
*
* Application entry point.
* This file was automatically generated by IngeDev and must be filled out
* by the developer.
*/
#include "SDK30.H"
#include "etat.h"
#include "WGUI.h"
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Prototype declaration to be used with INCENDO (replacing 'more_function' declared in SDK file 'etat.h').
// This new prototype can be used with SDK version >= 6.5.
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// extern int more_function_ext(NO_SEGMENT no, S_ETATOUT *in, void *out);
/**
* Application has to call ServiceRegister for each service with a same
* address Main and using predefined service number.
* \param size (I-) data size.
* \param data (I-) contains data needed between the Manager and application regarding services.
*
* \return service call status.
*
* \see sdk30.h
* etat.h
*/
int Main(unsigned int size, StructPt *data)
{
NO_SEGMENT No;
int ret = FCT_OK;
// Service call management
No = ApplicationGetCurrent(); // Return the application number
switch (data->service)
{
case GIVE_YOUR_DOMAIN: // Return application domain to Manager
ret = give_your_domain(No, NULL, &data->Param.GiveYourType.param_out);
break;
case AFTER_RESET: // Activated on each terminal reset
ret = after_reset(No, NULL, &data->Param.AfterReset.param_out);
break;
case IS_NAME: // Activated when Manager wants to get application name
ret = is_name(No, NULL, &data->Param.IsName.param_out);
break;
case IS_STATE: // Activated at boot and every minute to check if application is initialized
ret = is_state(No, NULL, &data->Param.IsState.param_out);
break;
case IDLE_MESSAGE: // Activated when Manager goes back to idle, to display its message
idle_message(No, NULL, NULL);
break;
case MORE_FUNCTION: // Activated on "F" key and dedicated to navigation menus
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// NOTE: other prototype variant 'int more_function_ext(NO_SEGMENT no, S_ETATOUT *in, void *out)'
// can be used with INCENDO.
// This other prototype is used if the application manages more than one application name.
// The 'S_ETATOUT' structure allows to know the name selected by the user after pressing the "F" key.
// This new prototype can be used with SDK version >= 6.5.
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ret = more_function(No, NULL, NULL);
break;
case KEYBOARD_EVENT: // Activated when key is pressed
ret = keyboard_event(No, &data->Param.KeyboardEvent.param_in,
&data->Param.KeyboardEvent.param_out);
break;
case STATE: // Activated on "F" key: Consultation->State, to print terminal content receipt
ret = state(No, NULL, NULL);
break;
case CONSULT: // Activated on "F" key: Consultation->Transactions, to print transaction totals receipt
ret = consult(No, NULL, NULL);
break;
case MCALL: // Activated on "F" key: Consultation->Call->Planning of Call, to print host call planning receipt
ret = mcall(No, NULL, NULL);
break;
case IS_TIME_FUNCTION: // Activated every minute, do you need the peripherals at the next call of time_function()?
ret = is_time_function(No, NULL, &data->Param.IsTimeFunction.param_out);
break;
case TIME_FUNCTION: // Activated every minute, to execute automatic periodic functions
ret = time_function(No, NULL, NULL);
break;
case IS_CHANGE_INIT: // Activated on "F" key: Initialization->Parameters->List, Conditions for changing Manager parameters?
ret = is_change_init(No, NULL, &data->Param.IsChangeInit.param_out);
break;
case MODIF_PARAM: // Activated on "F" key: Initialization->Parameters->List, Manager reports parameters changed.
ret = modif_param(No, &data->Param.ModifParam.param_in, NULL);
break;
case IS_EVOL_PG: // Activated on "F" key: Evolution->Load->Local or RemoteLoad, Conditions for application downloading?
ret = is_evol_pg(No, NULL, &data->Param.IsEvolPg.param_out);
break;
case IS_DELETE: // Activated on "F" key: Deletion, Conditions for application deletion?
ret = is_delete(No, NULL, &data->Param.IsDelete.param_out);
break;
case FILE_RECEIVED: // Activated each time Manager received a file from a "parameters" downloading session
ret = file_received(No, &data->Param.FileReceived.param_in, NULL);
break;
case MESSAGE_RECEIVED: // Activated each time Manager received a message in its own mailbox for this application
ret = message_received(No, &data->Param.MessageReceived.param_in, NULL);
break;
case IS_CARD_SPECIFIC: // Activated when card inserted card swiped or manually entry, do you want to process the card?
ret = is_card_specific(No, &data->Param.IsCardSpecific.param_in,
&data->Param.IsCardSpecific.param_out);
break;
case CARD_INSIDE: // Activated when the card is specific, the application process the card in transparent mode
ret = card_inside(No, &data->Param.CardInside.param_in,
&data->Param.CardInside. param_out);
break;
case IS_FOR_YOU_AFTER:
ret = is_for_you_after(No, &data->Param.IsForYouAfter.param_in,
&data->Param.IsForYouAfter.param_out);
break;
case DEBIT_NON_EMV:
ret = debit_non_emv(No, &data->Param.DebitNonEmv.param_in,
&data->Param.DebitNonEmv.param_out);
break;
case IS_FOR_YOU_BEFORE: // Activated when chip card inserted, ask application to recognise the chip card in order to a candidate
case TIME_FUNCTION_CHAINE: // French Bank Domain
case GIVE_INFOS_CX: // French Bank Domain
case FALL_BACK:
case DEBIT_OVER:
case AUTO_OVER:
case IS_ORDER: // French Health Care Domain
case ORDER: // French Health Care Domain
case IS_SUPPR_PG: // French Health Care Domain
case IS_INSTALL_PG: // French Health Care Domain
case GET_ORDER: // French Health Care Domain
case IS_LIBELLE: // French Health Care Domain
case EVOL_CONFIG: // French Bank Domain
case GIVE_MONEY: // French Bank Domain
case COM_EVENT:
case MODEM_EVENT:
case GIVE_INTERFACE:
case IS_BIN_CB: // French Bank Domain
case GIVE_AID:
case IS_CARD_EMV_FOR_YOU:
case DEBIT_EMV:
case SELECT_FUNCTION: // French Bank Domain
case SELECT_FUNCTION_EMV: // French Bank Domain
default:
break;
}
return ret;
}
/**
* Entry.c
*
* Application entry point.
* This file was automatically generated by IngeDev and must be filled out
* by the developer.
*
* Purpose:
*
* Each time Manager calls an application, it generates only one service
* call that reaches your application main with the corresponding service
* number.
*
* List of routines in file:
* - give_your_domain: Return application domain.
* - after_reset: Application reset processing.
* - is_name: Report application name to Manager.
* - is_state: Return application status (initialize or not).
* - idle_message: Dedicated to display idle message.
* - more_function: Dedicated to navigation menus.
* - keyboard_event: Return key pressed.
* - state: Print terminal content.
* - consult: Print daily totals.
* - mcall: Print call schedule.
* - is_time_function: Need pheripherals at the next call time_function()
* - time_function: Allow automatic execution of periodic functions.
* - is_change_init: Conditions for changing manager parameters?
* - modif_param: Manager reports parameters changing.
* - is_evol_pg: Conditions for application downloading?
* - is_delete: Conditions for application deletion?
* - file_received: Manager reports parameters file received from LLT.
* - message_received: Inter application messaging.
* - is_card_specific: Card needs a specific process?
* - card_inside: Transaction in progress for a specific card.
* - is_for_you_before: Is chip card as an ISO 7816-3?
* - is_for_you_after: recognise mag, smc or man card in order to be a candidate.
* - give_interface: Services registration and priority.
* - entry: Call by OS for recording services and opening DLL(s).
*/
#include "SDK30.H"
//+++++++++++++ Macros & preprocessor definitions ++++++++++++++
#define __ENTER_KEY -1
#define __BACK_KEY -2
#define __EXIT_KEY -3
#define NUMBER_OF_ITEMS(a) (sizeof(a)/sizeof((a)[0]))
#define SERVICES_LOW_PRIORITY 30
#define SERVICES_HIGH_PRIORITY 10
#define SERVICES_DEFAULT_PRIORITY 20
//++++++++++++++++++++++ Global variables ++++++++++++++++++++++
static service_desc_t Services[] = {
{ 0, GIVE_YOUR_DOMAIN, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, AFTER_RESET, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_NAME, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_STATE, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IDLE_MESSAGE, (SAP)Main, (unsigned char)SERVICES_DEFAULT_PRIORITY },
{ 0, MORE_FUNCTION, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, KEYBOARD_EVENT, (SAP)Main, (unsigned char)SERVICES_DEFAULT_PRIORITY },
{ 0, STATE, (SAP)Main, (unsigned char)SERVICES_DEFAULT_PRIORITY },
{ 0, CONSULT, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, MCALL, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_TIME_FUNCTION, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, TIME_FUNCTION, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_CHANGE_INIT, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, MODIF_PARAM, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_EVOL_PG, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_DELETE, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, FILE_RECEIVED, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, MESSAGE_RECEIVED, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, IS_CARD_SPECIFIC, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, CARD_INSIDE, (SAP)Main, (unsigned char)SERVICES_DEFAULT_PRIORITY },
{ 0, IS_FOR_YOU_AFTER, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY },
{ 0, DEBIT_NON_EMV, (SAP)Main, (unsigned char)SERVICES_HIGH_PRIORITY }
};
typedef struct Params
{
char Old_Date[24+1];
char Old_FmtDate[24+1];
char Old_Language[24+1];
char Old_Pabx[24+1];
char Old_PPad[24+1];
char Old_PPadType[24+1];
char Old_ISOreader[24+1];
char Old_TMSaccess[24+1];
} S_PARAMS;
static char appName[OBJECT_NAME_LEN + 1];
static char fileName[OBJECT_FILE_NAME_LEN + 1];
static const char coldReset[] = "Cold Reset\nFrom ";
static const char warmReset[] = "Warm Reset\nFrom ";
static const char timeToCall[] = "Time to call\nFrom ";
static const char idleMsg[] = "\nPlease Insert\nA Smart Card...";
static const char szDate[] = "Date:%.2s/%.2s/%.2s %.2s:%.2s\n";
static const char *MenuUser[] =
{
"Function 1",
"Function 2",
"Function 3",
"Function 4",
"Function 5"
};
int ManageMenu( const char *szTitle, int bRadioButtons, int nDefaultChoice,
int nItems, const char* Items[] )
{
FILE *hDisplay;
int DisplayHeaderStatus;
// Menu.
StructList Menu;
int nY;
int nMaxX=0;
int nMaxY=0;
ENTRY_BUFFER Entry;
int i;
int nInput;
int nReturn;
hDisplay = fopen( "DISPLAY", "w" );
// Get Screen size.
GetScreenSize( &nMaxY, &nMaxX );
// For the menu height of the menu,
nY = 0;
DisplayHeaderStatus=StateHeader(0); // disable display header
if ((nDefaultChoice < 0) || (nDefaultChoice >= nItems))
{
nDefaultChoice = 0;
}
CreateGraphics(_MEDIUM_);
memset( &Menu, 0, sizeof(Menu) );
Menu.MyWindow.left = 0;
Menu.MyWindow.top = nY;
Menu.MyWindow.rigth = nMaxX - 1;
Menu.MyWindow.bottom = nMaxY - 1;
if( nMaxY == 128 )
{
Menu.MyWindow.nblines = 10;
}
else
{
Menu.MyWindow.nblines = 5;
}
Menu.MyWindow.fontsize = _MEDIUM_;
Menu.MyWindow.type = _PROPORTIONNEL_;
Menu.MyWindow.font = 0;
Menu.MyWindow.correct = _ON_;
Menu.MyWindow.offset = 0;
Menu.MyWindow.shortcommand = _ON_;
if( bRadioButtons )
{
Menu.MyWindow.selected = _ON_;
}
else
{
Menu.MyWindow.selected = _OFF_;
}
Menu.MyWindow.thickness = 2;
Menu.MyWindow.border = _ON_;
Menu.MyWindow.popup = _NOPOPUP_;
Menu.MyWindow.first = nDefaultChoice;
Menu.MyWindow.current = nDefaultChoice;
Menu.MyWindow.time_out = 60;
Menu.MyWindow.title = (unsigned char*)szTitle;
for( i = 0; i < nItems; i++ )
{
Menu.tab[i] = (unsigned char*)Items[i];
}
G_List_Entry((void*)&Menu);
ttestall(ENTRY, 0);
nInput = Get_Entry((void*)&Entry);
switch( nInput )
{
case CR_ENTRY_OK:
nReturn = Entry.d_entry[0];
break;
case CR_ENTRY_NOK:
nReturn = __EXIT_KEY;
break;
default:
nReturn = __BACK_KEY;
break;
}
StateHeader(DisplayHeaderStatus); // move display header in previous state
fclose( hDisplay );
return nReturn;
}
/**
* Ask application to define its working environment, Manager will select
* common parameters set and adapt its internal processing.
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - application_type: TYP_CARTE (French Bank)
* TYP_HEALTH(French Health)
* TYP_EXPORT (Export)
* - mask: Key "F" 031 -> Parameters initialization (0:absent, 1:present)
* - response_number: should be incremented
*
* \return FCT_OK
*
* \see sdk30.h
*/
int give_your_domain(NO_SEGMENT no, void *p1, S_INITPARAMOUT *param_out)
{
// Return application domain to Manager
// Setting parameters initialization
param_out->returned_state[param_out->response_number].mask = MSK_MDP|MSK_SWIPE|MSK_TYPE_PPAD|MSK_PINPAD|MSK_STANDARD|MSK_LANGUE|MSK_FRMT_DATE|MSK_DATE;
// International domain
param_out->returned_state[param_out->response_number].application_type = TYP_EXPORT;
param_out->response_number++;
return (FCT_OK);
}
/**
* Initialize data and create disks, eventually ends interrupted transaction
* by returning S_TRANS_OUT.
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O) Eventually ends interrupted transaction
*
* \return FCT_OK
*
* \see sdk30.h
*/
int after_reset(NO_SEGMENT no, void *p1, S_TRANSOUT *param_out)
{
FILE *hDisplay;
unsigned char chgt;
TYPE_CHGT type;
// Reset management
hDisplay = fopen( "DISPLAY", "w" ); // Open display driver
first_init(no, &chgt, (unsigned char *)&type); // New software loaded ?
if (chgt==0xFF) // Yes, just loaded with first execution
{
printf(coldReset);
printf(appName);
raz_init(no); // Reset downloading indicator
}
else // No, already loaded and executed
{
printf(warmReset);
printf(appName);
}
ttestall(0, 2*100); // Wait for 2s.
fclose( hDisplay ); // Close display driver
return FCT_OK;
}
/**
* Report application name to Manager.
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - appname: Application name
* - no: Application number
* - response_number: should be incremented
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_name(NO_SEGMENT no, void *p1, S_ETATOUT *param_out)
{
// Report application name to Manager cannot return the family name
// because the T_APPNAME type used in the "is_name" function is too short to store
// the FAMILY NAME (T_APPNAME length = 12+1 FAMILY NAME length =15+1)
// we use the binary name instead (without extension, and whose length is 11+1)
memset(param_out->returned_state[param_out->response_number].appname,0, sizeof(param_out->returned_state[param_out->response_number].appname));
strncpy(param_out->returned_state[param_out->response_number].appname, fileName, sizeof(param_out->returned_state[param_out->response_number].appname) - 1);
param_out->returned_state[param_out->response_number].no_appli = no;
param_out->response_number++;
return (FCT_OK);
}
/**
* Report application state initialize or not to Manager.
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - response: REP_OK (Initialized)
* REP_KO (Not initialized)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_state(NO_SEGMENT no, void *p1, S_ETATOUT *param_out)
{
int retour;
// Return application state
param_out->returned_state[param_out->response_number].state.response = REP_OK;
retour = is_name (no, PT_NULL, param_out);
return (retour);
}
/**
* Allows the application to display its idle message when Manager goes back
* to idle (the application should have the higher priority).
* \param no (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int idle_message (NO_SEGMENT no, void *p1, void *p2)
{
FILE *hDisplay;
int nFont;
char idleMessage[256];
// Idle message management
hDisplay = fopen("DISPLAY","w"); // Open display driver.
nFont = GetDefaultFont(); // Retrieve default font
CreateGraphics(_LARGE_); // Create graphic font
strcpy(idleMessage,appName);
strcat(idleMessage,idleMsg);
_DrawString((char*) idleMessage, 0, 20, _OFF_);
PaintGraphics(); // Display idle message
SetDefaultFont(nFont); // Restore default font
fclose(hDisplay); // Close display driver
return FCT_OK;
}
/**
* It's activated when pressing on "F" key to select the right application
* to go on menu.
* \param no (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*
* \note Other prototype variant 'int more_function_ext(NO_SEGMENT no, S_ETATOUT *in, void *out)'
* can be used with INCENDO.
* This other prototype is used if the application manages more than one application name.
* The 'S_ETATOUT' structure allows to know the name selected by the user after pressing the "F" key.
* This new prototype can be used with SDK version >= 6.5.
*/
int more_function( NO_SEGMENT no, void *p1, void *p2 )
{
FILE *hDisplay;
int bContinue=1;
// Menu management
hDisplay =fopen("DISPLAY", "w"); // Open display driver
do
{
switch(ManageMenu(appName, 0, 0, NUMBER_OF_ITEMS(MenuUser), MenuUser))
{
case 0: printf("Function1\nRunning..."); bContinue=0; break; // Function1 selected
case 1: printf("Function2\nRunning..."); bContinue=0; break; // Function2 selected
case 2: printf("Function3\nRunning..."); bContinue=0; break; // Function3 selected
case 3: printf("Function4\nRunning..."); bContinue=0; break; // Function4 selected
case 4: printf("Function5\nRunning..."); bContinue=0; break; // Function5 selected
default: bContinue=2; break; // Abort key pressed
}
} while(bContinue==1);
if (bContinue!=2)
{
ttestall(0, 2*100); // Wait for 2s.
}
fclose(hDisplay); // Close display driver
return FCT_OK;
}
/**
* It's activated when key is pressed and terminal is in idle mode.
* \param noappli
* \param key_in (I-)
* - keycode: Key pressed.
* \param key_out (-O)
* - keycode: Key pressed, new key, 0=disable.
*
* \return FCT_OK
*
* \see sdk30.h
*/
int keyboard_event(NO_SEGMENT noappli,S_KEY *key_in,S_KEY *key_out)
{
// Keyboard management
switch (key_in->keycode)
{
case N0: case N1: case N2: case N3: case N4:
case N5: case N6: case N7: case N8: case N9:
case T_VAL : case T_POINT :
key_out->keycode = 0; // Inhibit these keys to Manager for International domain
break;
case F1 : case F2 : case F3 : case F4 :
case T_CORR : case T_ANN : case NAVI_CLEAR : case NAVI_OK :
case UP : case DOWN :
case T_F : // do not filter F key and return the same key !
key_out->keycode=key_in->keycode; // Return the same key value for keys above !
break;
default :
key_out->keycode=key_in->keycode;
break;
}
return (FCT_OK);
}
/**
* It's activated on "F" key: Consultation->State.
* To print terminal content.
* \param no (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int state (NO_SEGMENT no, void *p1, void *p2)
{
DATE date;
object_info_t infos;
FILE *hPrinter;
// Print application info
ObjectGetInfo(OBJECT_TYPE_APPLI, no, &infos); // Retrieve application info
hPrinter=fopen( "PRINTER", "w-*" ); // Open printer driver
if (hPrinter!=NULL)
{
pprintf("\x1b""E%s\n""\x1b""F",appName); // Print application name
pprintf(" STATE \n"
"Application used as\n"
"IngeDev Template\n\n");
read_date(&date); // Print date and time
pprintf(szDate, date.day, date.month, date.year, date.hour, date.minute);
pprintf("File : %s\n",infos.file_name); // Print application file name
pprintf("CRC : %04x\n",infos.crc); // Print application CRC
ttestall(PRINTER, 0);
fclose(hPrinter); // Close printer driver
}
return FCT_OK;
}
/**
* It's activated on "F" key: Consultation->Transactions.
* To print transactions total receipt.
* \param no (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int consult (NO_SEGMENT no, void *p1, void *p2)
{
DATE date;
FILE *hPrinter;
// Print daily totals
hPrinter=fopen("PRINTER", "w-*"); // Open printer driver
if (hPrinter!=NULL)
{
pprintf("\x1b""E%s\n""\x1b""F", appName); // Print application name
pprintf(" CONSULT \n"
"Print daily totals here\n"
"Number of Debit/Credit \n"
"Totals of Debit/Credit \n"
"Number of Cancel\n\n");
read_date(&date); // Print date and time
pprintf(szDate, date.day, date.month, date.year, date.hour, date.minute);
ttestall(PRINTER, 3*100);
fclose(hPrinter); // Close printer driver
}
return FCT_OK;
}
/**
* It's activated on "F" key: Consultation->Call->Planning of Call.
* To print call schedule receipt.
* \param no (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int mcall (NO_SEGMENT no, void *p1, void *p2)
{
DATE date;
FILE *hPrinter;
// Print call schedule
hPrinter=fopen("PRINTER", "w-*"); // Open printer driver
if (hPrinter!=NULL)
{
pprintf("\x1b""E%s\n""\x1b""F", appName); // Print application name
pprintf(" MCALL \n"
"Planning of call here \n"
"Time release batch \n"
"Time loading parameters\n"
"Time loading hotlist\n\n");
read_date(&date); // Print date and time
pprintf(szDate, date.day, date.month, date.year, date.hour, date.minute);
ttestall(PRINTER, 3*100);
fclose(hPrinter); // Close printer driver
}
return FCT_OK;
}
/**
* Do you need the peripherals at the next call of time_function()?.
* It's call every minute.
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - response: REP_OK (Manager closes all peripherals)
* REP_KO (Manager keeps all peripherals opened)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_time_function(NO_SEGMENT no, void *p1, S_ETATOUT *param_out)
{
int retour;
// Peripherals needed?
param_out->returned_state[param_out->response_number].state.response=REP_OK;
retour = is_name (no, PT_NULL, param_out);
return(FCT_OK);
}
/**
* Allow application to execute its own periodical process.
* It's call every minute.
* \param no (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int time_function(NO_SEGMENT no, void *p1, void *p2)
{
// Periodical function in progress
fopen("DISPLAY","w"); // Open display driver
printf(timeToCall);
printf(appName);
ttestall(0, 1*100);
fclose(stdout()); // Close display driver
return (FCT_OK);
}
/**
* It's activated on "F" key: Initialization->Parameters->List.
* Each time Manager wants to change its own parameters.
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - mask: Key "F" 031 -> Parameters modification (0:accepting, 1:refusing)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_change_init(NO_SEGMENT no, void *p1, S_ETATOUT *param_out)
{
S_ETATOUT etatout;
int retour;
memcpy(&etatout, param_out, sizeof(etatout));
// accept all
etatout.returned_state[etatout.response_number].state.mask=0;
memcpy(param_out,&etatout,sizeof(etatout));
retour = is_name (no, PT_NULL, param_out);
return(FCT_OK);
}
/**
* Я прекрасно понимаю, что эту простыню кода
* никто читать не будет, но мало ли?
* Вдруг кому-то реально поможет?
*/
/**
* It's activated on "F" key: Initialization->Parameters->List.
* Each time Manager changed its own parameters.
* \param noappli (I-)
* \param param_in (I-)
* - mask: Key "F" 031 -> Parameters modification (0:not modified, 1:modified)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int modif_param(NO_SEGMENT noappli, S_MODIF_P *param_in, void *p2)
{
S_MODIF_P param_changed;
memcpy(¶m_changed, param_in,sizeof(param_changed));
fopen("DISPLAY","w");
printf("MODIF_PARAM\n%04x",(int)param_changed.etatout.returned_state[0].state.mask);
ttestall(0,200);
fclose(stdout());
return(FCT_OK);
}
/**
* It's activated each time Manager wants to run a downloading session (local or remote).
* "F" key: Evolution->Load->Local or Evolution->Remote Load
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - response: REP_OK (application authorizes donwloading process)
* REP_KO (application refuses any downloading process)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_evol_pg(NO_SEGMENT no, void *p1, S_ETATOUT *param_out)
{
int retour;
// Downloading process allowed?
param_out->returned_state[param_out->response_number].state.response=REP_OK;
retour = is_name (no, PT_NULL, param_out);
return(FCT_OK);
}
/**
* It's activated each time Manager wants to delete an application.
* "F" key: Deletion
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - response: DEL_YES (application authorizes deletion process)
* DEL_NO (application refuses any deletion process)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_delete(NO_SEGMENT no, void *p1, S_DELETE *param_out)
{
// Deletion process allowed?
param_out->deleting=DEL_YES;
return (FCT_OK);
}
/**
* Manager reports parameters file received from LLT.
* It's activated upon reception of a parameter file by the manager.
* \param no (-I)
* \param param_in (I-)
* - volume_name: SYSTEM (File loaded in CFS)
* HOST (File loaded in DFS).
* - file_name: Application file name
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int file_received(NO_SEGMENT no, S_FILE *param_in, void *p2)
{
FILE *prt;
S_FS_PARAM_CREATE ParamCreat;
int Ret;
char Dir_File[25];
char Dir_Label[25];
int len;
char rsp[256];
S_FS_FILE *pFile;
// Print parameter file received
prt=fopen("PRINTER","w-"); // Open printer driver
pprintf("\x1b""E%s\n""\x1b""F", appName);
pprintf("File Received :\n/%s/%s\n",param_in->volume_name,param_in->file_name);
ttestall(PRINTER,0); // Print volume+file_name
memclr(Dir_File,sizeof(Dir_File));
memclr(Dir_Label,sizeof(Dir_Label));
sprintf(Dir_Label,"/%s",param_in->volume_name);
ParamCreat.Mode = FS_WRITEONCE;
Ret = FS_mount (Dir_Label,&ParamCreat.Mode);
if (Ret == FS_OK)
{
sprintf(Dir_File,"/%s/%s",param_in->volume_name,param_in->file_name);
pFile = FS_open (Dir_File, "r"); // The file can be open at this stage
// Eventually read the file and get parameters
len = FS_length(pFile); // File length in bytes
if(len > sizeof(rsp)) {
len = sizeof(rsp);
}
FS_read(rsp, len, 1, pFile); // Read from file
FS_close(pFile); // Close the file
FS_unmount(Dir_Label); // Cannot be deleted as it is located in system disk
}
pprintf("%s\n", rsp);
fclose(prt); // Close printer driver
return (FCT_OK);
}
/**
* Inter application messaging.
* It's activated each time Manager received a message in its mailbox for this application.
* \param no (-I)
* \param param_in (I-)
* - sender: Sender ID
* - receiver: Receiver ID
* - type: IAM type
* - length: Message length
* - value: Message received
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int message_received(NO_SEGMENT no, S_MESSAGE_IAM *param_in, void *p2)
{
FILE *prt;
// Print message received from application 2
prt=fopen("PRINTER","w-"); // Open printer driver
pprintf("\x1b""E%s\n""\x1b""F", appName);
pprintf ("Message IAM :\n");
pprintf ("S:%04X R:%04X\n", param_in->sender, param_in->receiver); // USER2 to TEMPLATE
pprintf ("IAM Type : %04X \n\n", param_in->type);
pprintf("%s\n\n\n\n\n\n", param_in->value); // Print the message received
ttestall(PRINTER, 2*100);
fclose(prt); // Close printer driver
return (FCT_OK);
}
/**
* It's activated when a card is inserted, swiped or manually entry.
* Ask the application if the card need a specific processing.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - response: REP_OK (card processing)
* REP_KO (no card processing)
* Only one application wants to process the card, manager calls CARD_INSIDE entry.
* More application wants to process the card, manager asks for card removal.
* If no application wants to process the card, manager goes on with selection process.
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_card_specific(NO_SEGMENT no, S_TRANSIN *param_in, S_ETATOUT *param_out)
{
int ret;
// Return application state
param_out->returned_state[param_out->response_number].state.response = REP_KO;
ret = is_name (no, PT_NULL, param_out);
return (FCT_OK);
}
/**
* It's activated when an application has chosen to treat this card has specific.
* The transaction is done here.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - rc_payment: PAY_OK (Transaction done)
* PAY_KO (Transaction rejected)
* If an application returns STOP, polling is stopped and manager asks for card removal.
* The application is in charge to ask for amount and currency if needed.
*
* \return STOP: Card accepted and transaction process done, polling is stop.
* FCT_OK: Card refused and poll the next application.
*
* \see sdk30.h
*/
int card_inside(NO_SEGMENT no, S_TRANSIN *param_in, S_TRANSOUT *param_out)
{
bool card_accepted = TRUE;
if (card_accepted)
{
// Return transaction status
param_out->rc_payment = PAY_OK; // Transaction done, polling is stop
return (STOP);
}
else
{
return (FCT_OK); // Card refused, poll the next application
}
}
/**
* Ask application to recognize the magnetic, smart or manually card in order to be
* a candidate.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - cardappnumber: 1 = Card accepted
* 0 = Card rejected
* - cardapp: CARD_PROCESSED (low priority)
* CARD_RECOGNIZED (medium priority)
* CARD_PRIORITY (high priority)
* - appname: Application name
* - no: Application number
* - response_number: should be incremented
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_for_you_after(NO_SEGMENT no, S_TRANSIN *param_in, S_CARDOUT *param_out)
{
// case of chip card
if (param_in->support == CHIP_SUPPORT)
{
if(param_in->power_on_result == 0)
{
// accept this card
param_out->returned_state[param_out->response_number].cardappnumber = 1;
param_out->returned_state[param_out->response_number].cardapp [0].priority = CARD_PROCESSED;
}
else
{
// reject the card
param_out->returned_state[param_out->response_number].cardappnumber = 0;
}
}
// case of stripe 2 card
if (param_in->support == TRACK2_SUPPORT)
{
// accept this card
param_out->returned_state[param_out->response_number].cardappnumber = 1;
param_out->returned_state[param_out->response_number].cardapp [0].priority = CARD_PRIORITY;
}
// case of Card Number Manual entry
if (param_in->support == OPERATOR_SUPPORT)
{
// accept this card
param_out->returned_state[param_out->response_number].cardappnumber = 1;
param_out->returned_state[param_out->response_number].cardapp [0].priority = CARD_PRIORITY;
}
// give my application name
strcpy (param_out->returned_state[param_out->response_number].appname, appName) ;
// give my application number
param_out->returned_state[param_out->response_number].no_appli = no;
// give my card name
strcpy (param_out->returned_state[param_out->response_number].cardapp [0].cardappname, "Template") ;
// increment the response number
param_out->response_number++;
return (FCT_OK);
}
/**
* Process a non EMV chip card or a magnetic card or manual entry transaction.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - rc_payment: PAY_OK (Transaction done)
* PAY_KO (Transaction rejected)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int debit_non_emv (NO_SEGMENT no, S_TRANSIN * param_in, S_TRANSOUT * param_out)
{
FILE *prt;
int i;
prt = fopen("PRINTER", "w-");
// case of chip card
if ( param_in->support == CHIP_SUPPORT )
{
pprintf("\x1b""E%s\n""\x1b""F", appName);
if (param_in->historical_bytes.length != 0)
{
pprintf("Atr:\n");
for (i=0; ihistorical_bytes.length; i++)
{
pprintf("%02X ", param_in->historical_bytes.historic[i]);
}
}
else
{
pprintf("Synchronous card\n");
pprintf("or Chip mute\n");
}
pprintf("\n\n\n\n\n\n");
}
// case of stripe 2 card
if ( param_in->support == TRACK2_SUPPORT )
{
pprintf("\x1b""E%s\n""\x1b""F", appName);
pprintf("Track2:\n%s\n\n\n\n\n\n", param_in->track2);
}
// case of Card Number Manual entry
if ( param_in->support == OPERATOR_SUPPORT )
{
pprintf("\x1b""E%s\n""\x1b""F", appName);
pprintf("Manual Entry:\n%s\n\n\n\n\n\n",param_in->track2);
}
ttestall(PRINTER,2*100);
fclose(prt);
param_out->noappli = no; // Return application number
param_out->rc_payment = PAY_OK; // Transaction done
return (FCT_OK);
}
/**
* Services registration and priority.
* For all services except idle_message, priority => 0x00 highest and 0xFF lowest
* For idle_message, priority => 0x00 lowest 0xFF highest
* \param AppliNum (-I)
* \param p1 (-I)
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int give_interface(unsigned short AppliNum, void *p1, void *p2)
{
int i;
for(i = 0; i < (int)(sizeof(Services) / sizeof(Services[0])); i++)
Services[i].appli_id = AppliNum;
ServiceRegister((sizeof(Services) / sizeof(Services[0])), Services);
return FCT_OK;
}
#ifdef __cplusplus
extern "C" {
#endif
/**
* entry() is called by the OS for recording services and opening DLL(s).
* The RegisteryPowerFailure() can also be moved to entry().
*
* \see sdk30.h
*/
void entry(void)
{
object_info_t info;
char * indexExt;
// Recording services
ObjectGetInfo(OBJECT_TYPE_APPLI, ApplicationGetCurrent(), &info);
give_interface(info.application_type, NULL, NULL);
memcpy(appName, info.name, OBJECT_NAME_LEN);
memcpy(fileName, info.file_name, OBJECT_FILE_NAME_LEN);
fileName[OBJECT_FILE_NAME_LEN] = '\0';
appName[OBJECT_NAME_LEN] = '\0';
// In the string given to the "is_name" function
// FAMILY NAME cannot be used because the T_APPNAME type used in is_name function is too short to store FAMILY NAME (T_APPNAME length = 12+1 FAMILY NAME length =15+1)
// Binary name is used instead. "info.file_name" contains the binary name with the file extension
// (e.g. ABCDEFG.AGN) and must be removed to be returned in the 'is_name' function.
indexExt = strstr(fileName, ".");
if(indexExt != NULL) {
*indexExt = '\0';
}
}
#ifdef __cplusplus
}
#endif
По сути это минимальное рабочее приложение, которое можно загрузить в терминал.
После этого нажимаем «Project», выбираем «Rebuild» (при нажатии просто «Build» выдаёт ошибку).
Начнётся процесс сборки, и, если всё заработает без глюков, она закончится успешно, а в директории проекта появится папка «Bin\GNU_ARM_DEBUG», а в ней — несколько файлов (самые важные из них для нас — *.bin и *.txt).
На этом процесс установки ПО завершён. Тем не менее, запустить его на терминале просто так не выйдет. Отчего так и как с этим бороться, сейчас узнаем.
❯ Alert irruption!
И для начала давайте разберёмся, почему же Ingenico нельзя разбирать.
В своих терминалах Ingenico придумала поистине невероятную систему защиты: в памяти терминала находятся некие ключи IngeTrust, используемые для защиты производящихся внутри устройства операций, в том числе для подписывания приложений.
Их наличие отображается при запуске терминала: он должен показывать весёлый смайлик. Соответственно, грустный означает, что ключи отсутствуют, и устройство было заблокировано.
Самой частой ошибкой является Alert irruption, сигнализирующая о срабатывании тампера. При этом блокируются все криптографические операции, стираются все ключи, а загрузка другой прошивки не представляется возможным. Именно в ключах IngeTrust и кроется невозможность сбросить тампер на Ingenico: нужно не только сбросить флаг, но и залить новые низкоуровневые ключи.