Разработка приложений для платёжных терминалов Ingenico

Приветствую всех!

Скажите: интересовались ли вы хоть раз тем, как устроены и работают банковские платёжные терминалы, к которым вы прикладываете свою карту едва ли не ежедневно? Хотели ли вы узнать, как написать что-то своё под какое-нибудь из данных устройств?
Если ваш ответ — «Да», то этот пост определённо для вас.

v6h7bdokqytawnwcdgxfaxza5i8.jpeg

Обычно тема программирования 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’ов данной фирмы.

j0gzzfygmcholrqxqb5fvq7b0hu.jpeg

nazsayhewqhdyhpzaufo9fbk6r0.jpeg

Старые терминалы на платформе Unicapt32: Ingenico 5100 и Ingenico 7910. Модели достаточно распространённые, под 7910 даже был написан софт для приёма платежей от ОСМП (она же Qiwi).

ulyx40muea9rjqnc8scdb1jykkq.jpeg

Пин-пад Ingenico 3050. Использовался совместно с терминалами Unicapt32.

7sjd7h8qptmmtvfmp3i6q5etozi.png

POS-terminal SAGEM Monetel EFTsmart на платформе Telium-1. Тот самый предшественник нынешних Ingenico. Рядом пин-пад PPC30.

qouxsktchyvspn6mgdxd_z5vz2q.jpeg

А вот ещё один пин-пад, SAGEM Monetel PP 30.

_ohjfs7v76iqepcsy68r9au6q1s.jpeg

POS-terminal Ingenico ICT220 A98. ICT220 — самая популярная модель, думаю, каждый из вас по-любому хоть раз в жизни прикладывал карту к такому аппарату. Есть также версия ICT250 с цветным дисплеем, а также IPP320/350 — по сути тот же ICT220/250, но без принтера и управляемый извне (именно они стоят на кассах супермаркетов).

hx4yooitwley58ugduvfupgmyse.jpeg

ICT220 C98. Это более новая версия предыдущего девайса, оснащённая новой прошивкой, соответствующей более новым стандартам безопасности. К слову говоря, пин-пады тоже имеют такое разделение, поэтому IPP220 A98 не будет работать на POS-terminal’е C98, ну и наоборот.

wdevzul5svmmxtbn7bbxwyej5eu.jpeg

IPP220. Пин-пад для терминала ICT220/250, подключаемый к нему по USB.

w6fajvin_4rlqktq_azk7jl3qf8.jpeg

IWL250. Портативный терминал с цветным дисплеем, используется в кафе, ресторанах, барах и других аналогичных заведениях. Увы, мой экземпляр в нерабочем состоянии.

8vcgycheftgeuz5zn9bo3gguocy.jpeg

ryqxsv4ipsjpga5b57nr2g7y41o.jpeg

IPA280. Терминал сбора данных на Windows CE с принтером и пин-падом. Железка крайне интересная, скорее всего, я даже напишу про неё отдельный пост.

fvyjeqys9clyudhz7aichrwtmgo.jpeg

sgcurq8nvhlz7zj9bojcxuiuaeo.jpeg

ISMP. Мобильный POS-terminal, представляющий собой чехол для iPhone 4 со встроенным пин-падом на платформе Telium и сканером штрих-кодов.
Итак, как можно заметить, платформа Telium-2 представляет широкий выбор устройств для любого применения. Несмотря на выход более новых устройств, вы наверняка встретите упомянутые экземпляры на какой-нибудь из торговых точек вашего города.

Внутренности


Остановимся поподробнее на ICT220. Именно для него мы и будем писать софт.

Характеристики терминала такие: процессор ARM9 (плюс отдельно ARM7 для шифрования), по шестнадцать мегабайт ОЗУ и Flash (но это из доступных пользователю, в реальности куда больше). Из интерфейсов — USB, RS-232, Ethernet, модем, USB-хост.

Внимание! Вскрытие терминала приведёт к стопроцентной его порче. Восстановить его после этого смогут только в представительстве вендора за немаленькие деньги (в большинстве случаев заблокированный терминал проще выкинуть, чем разбираться). Не разбирайте устройства, находящиеся в эксплуатации. Показанный далее экземпляр достался мне уже помершим. Про то, какими системами безопасности обладают POS-terminal’ы, я писал отдельный пост.

hprt27malsq11-bpfazjuw2kdzo.jpeg

А вот и подопытный экземпляр. Он в нерабочем состоянии, так что единственное, что остаётся с ним сделать, так это разобрать его на запчасти.

_ozwhv8ztqkm9cwbex7zig6tlkw.jpeg

Обратная сторона. USB-B для подключения к компьютеру и USB-A для внешних устройств. COM-порт выведен на разъём miniUSB (но сигнал там никакой не USB!). Порт телефонной линии. Для экономии места и принуждения к покупке проприетарных шнуров вместо стандартного 8P8C стоит 4P4C, соответственно, в кабеле используются только две средние витые пары. Снизу слоты под SAM-карты и незаметные с такого ракурса разъёмы под SIM и microSD.

otmccdneufhb2db_tlbqoeoibvq.jpeg

grrxcqd9lp_otonedjljyxqmnqy.jpeg

6qyasrsfbkyaaugmebuqa-nuimo.jpeg

fjtxdjt3_ougneiwbiwjht6n6cg.jpeg

Разбираем. На материнке куча проприетарных чипов. На отдельной плате размещён GSM-модем от Sagemcom. Дисплей имеет маркировку M0412 SHON58, гугл без понятия, что за модель и что за контроллер там стоит. На плате модема некий элемент, похожий на ячейку литиевого аккумулятора — это суперконденсатор. В выступе в виде половинки цилиндра на корпусе рядом с портами находится батарейка CR2450.

e7wicdzo-f2gaic3juaycr2nihg.jpeg

Антенна GSM-модема.

ayilp2yc713rh1yduxm1pwyeays.jpeg

Модем, снятый с платы.

g5rwjcrumhyqymqp14conf4vgww.jpeg

vqkztkotckdpo8omlnfgtmmgnyo.jpeg

Материнская плата. Давно севшая батарейка, закрытые металлическим экраном компоненты. Считыватель смарт-карт закрыт контуром безопасности.

Из чипов на плате находятся:

  • SI3018-FS, SI3056-FS — модемный чипсет
  • SMsC LAN8700-AEZG — контроллер Fast Ethernet
  • ZT3223E — преобразователь уровней RS-232
  • MONEFT3X SPIIIM 1215 1T6239–1 — проприетарный шифропроцессор
  • WE-MIDCOM 7090–37 GV1217LF1 — трансформатор Ethernet
  • LB1838 — низковольтный драйвер биполярного ШД


oaywp-fziropfqueuts9eqwirdo.jpeg

Отковыряем защитный экран. Сидит он очень плотно, снять и не погнуть не вышло. Под ним процессор (тоже заказной), чипы памяти и ПЗУ. На базе чипсета MONEFT3X построены все терминалы на платформе Telium-2.

zccgh1e2ry9spkzaqql8ognmy2i.jpeg

msvpfhxrar9sxxyw2aa8s5aituk.jpeg

vcfzq8qoid4to8rmiwhznoipaok.jpeg

Принтер. Стандартные для терминала параметры — лента на пятьдесят семь миллиметров, триста восемьдесят четыре точки. Произведён фирмой Alps Electric, даташит сходу найти не смог.

ywyomaobdmwukzxksx0vgl4idbm.jpeg

Магнитная головка.

Ставим софт


Ну что же, перейдём непосредственно к софту.

Для того, чтобы начать разработку под 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).

ukexhaysy3bcz718e6f9wnfjiog.png

Далее нужно подключить Telium SDK, для чего жмём на панели меню «Window», далее «Preferences», открываем «Installed Telium packages». Жмякаем «Add», выбираем SDKDescriptor.xml в папке с нужной вам версией SDK.

zq1vvwpvbx2nw73y9-ugvul_4su.png

Теперь жмём «File», создаём новый «Telium project». Оставляем «Telium application», вводим желаемое имя и выбираем «Finish». У нас создастся новый проект, где в папке Src будут два файла: main.c и entry.c. В дальнейшем мы разберём их поподробнее.

main.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
/**
 * 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


По сути это минимальное рабочее приложение, которое можно загрузить в терминал.

k3uhnt4yhpp0e0c6nf_cre4xqcy.jpeg

После этого нажимаем «Project», выбираем «Rebuild» (при нажатии просто «Build» выдаёт ошибку).

a9rcw9jpct--albkaussri-ndec.jpeg

Начнётся процесс сборки, и, если всё заработает без глюков, она закончится успешно, а в директории проекта появится папка «Bin\GNU_ARM_DEBUG», а в ней — несколько файлов (самые важные из них для нас — *.bin и *.txt).

На этом процесс установки ПО завершён. Тем не менее, запустить его на терминале просто так не выйдет. Отчего так и как с этим бороться, сейчас узнаем.

Alert irruption!


И для начала давайте разберёмся, почему же Ingenico нельзя разбирать.
В своих терминалах Ingenico придумала поистине невероятную систему защиты: в памяти терминала находятся некие ключи IngeTrust, используемые для защиты производящихся внутри устройства операций, в том числе для подписывания приложений.

hzmn_g4mojvca6rlbta85hipvai.jpeg

Их наличие отображается при запуске терминала: он должен показывать весёлый смайлик. Соответственно, грустный означает, что ключи отсутствуют, и устройство было заблокировано.

rmaghgt4hng0bk6e2mepciuy87w.jpeg

Самой частой ошибкой является Alert irruption, сигнализирующая о срабатывании тампера. При этом блокируются все криптографические операции, стираются все ключи, а загрузка другой прошивки не представляется возможным. Именно в ключах IngeTrust и кроется невозможность сбросить тампер на Ingenico: нужно не только сбросить флаг, но и залить новые низкоуровневые ключи.

© Habrahabr.ru