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

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

Если ваш ответ — «Да», то этот пост определённо для вас.



Обычно тема программирования 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. В дальнейшем мы разберём их поподробнее.

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(&param_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; i<param_in->historical_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: нужно не только сбросить флаг, но и залить новые низкоуровневые ключи.



Другим (не менее фатальным) вариантом является ошибка Unauthorized. Она означает то, что тампер не сработал, но ключей всё равно нет. Чаще всего терминал дохнет с такими симптомами из-за разряда внутренней батарейки. То есть терминал после долгого лежания наверняка окажется помершим. Примерно по этой же причине уже не осталось рабочих терминалов на платформе Telium-1.

Сбросить эту ошибку может только представитель вендора терминалов Ingenico в вашем регионе. В ходе данной процедуры используется некий KIT 43C, представляющий собой обычный терминал на ОС Telium-2 со специальным софтом. Такие устройства находятся строго в ограниченном доступе у представителей Ingenico. Терминал имеет выделенное подключение к интернету, каждая операция получения ключей регистрируется у Ingenico.

Ну а вот так выглядит процесс реактивации терминала. Увы, видео это не моё, так что о подробностях процесса ничего сказать не могу. Как я понимаю, вначале специальным софтом сбрасывают флаг тампера, потом загружают свежие IngeTrust keys.

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

Подписывание приложений


Теперь разберёмся с самим софтом. Для его подписи используются две утилиты — Software Signature Tool (SST) и Software Authentication Tool (SAT). Отличия между ними в первую очередь в том, что первая предназначена для Telium-1, а вторая — для Telium-2. Разумеется, в первую очередь нас интересует именно второй вариант.



Вот схема обмена данными при подписи приложения, размещённая в руководстве к SAT. Для выпуска своего софта необходимо получить у вендора терминал со специальной прошивкой. Далее в Ingenico заказывается смарт-карта с вашим личным сертификатом разработчика. Терминал подключается к сети и к компьютеру с ПО SAT. Для подписи приложения необходимо вставить карту в терминал, ввести её ПИН, далее выбрать пакет в SAT и, собственно, отправить запрос. Терминал соединится с сервером компании Ingenico, где получит ключи. Таким образом, производить подписывание приложений «на месте» невозможно.



У SST в этом плане всё несколько проще, нужны только ключ в контейнере на смарт-карте и сертификат.





У меня даже отыскался считыватель как на этой иллюстрации, и это Gemplus GemPC410. В своё время купил его за триста рублей совершенно новым поиграться с чтением смарт-карт.
Такая система обеспечивает достаточно высокий уровень защиты, но каждый раз запрашивать ключи во время разработки приложения неудобно, поэтому в терминалах предусмотрен специальный «девелоперский» режим — Mock-up mode (он же просто MOCKUP). В нём отключены тампер, проверка подписи и некоторые другие ограничения, присущие стандартной (Production, PROD) прошивке.

Мокап


Для того, чтобы активировать данный режим, необходимо перепрошить ОС.

Внимание! Процедуру активации MOCKUP невозможно будет отменить! Попытка возвращения обратно в режим PROD приведёт к окирпичиванию терминала!



Итак, будем считать, что вы решились. Подключаем терминал кабелем к компьютеру, зажимаем клавишу F1 и подаём питание. После появления «звёздочки» быстро прожимаем F2-F3-F4. Если вы успели это сделать, на экране загорится «LLT».



На компьютере запускаем эту самую LLT.



Делаем двойной клик на строчке с моделью терминала в правой нижней части окна. Должно установиться соединение.



Далее загружаем нужный пакет с ОС, в моём случае — ICT2XX_MOCKUP.m40. Живёт он в папке Components у Telium SDK. Жмём правой кнопкой на строчку с названием пакета и выбираем «Download». Всё, процесс пошёл.



Завершаем соединение с терминалом (жмём два раза туда же, где мы его подключили). Терминал ненадолго зависнет, помигает подсветкой, попищит динамиком, после чего несколько раз перезагрузится. Если всё было сделано верно, на экране вновь загорится «LLT». При этом раз в секунду будет мигать надпись «Special Mock-up», похожая по стилю на сообщение «Unauthorized». Но в этом случае это не ошибка, а всего лишь предупреждение, защищающее от установки «девелоперского» терминала на торговую точку. Но не забывайте, что активация мокапа приводит к удалению ключей IngeTrust, отчего при накатывании обычной ОС терминал выпадет в ошибку «Unauthorized». Тем не менее, активировать мокап на уже заблокированном терминале не выйдет, так как сам дистрибутив MOCKUP-версии подписан PROD-ключами, отчего при попытке загрузить что-либо терминал выдаст ошибку подписи. Так что при желании повторить показанное в данной статье необходимо найти рабочий терминал.









Аналогичным образом устанавливаем соединение и накатываем из соседней папки Telium Manager. После несложного процесса настройки терминал готов принимать в себя приложения.



Итогом всего этого должно стать примерно то, что на экране.

SAT


Как мы помним, штатный проект уже сходу можно запустить на терминале. Отчего бы нам не попробовать загрузить свежескомпилированную прогу? Хотя Mock-up освобождает от необходимости иметь смарт-карту разработчики, приложение всё равно должно быть подписано. Так что открываем SAT, который в архиве лежит в папке TELIUM tools.

В папке с ним лежит файл SAT.ini, который необходимо открыть в текстовом редакторе и активировать там Mock-up. Примерно так:

SAT.ini
; SAGEM Monetel 
;---------------

[Repertoire]
BaseDeDonnees=SAT
FichierLangue=SAT.lng

[X]
1=79D0149CF2E4FFEFBCDA8EF0
2=13467D24C742D627E8F0F960AA2ECD4E65F755E1793C

[Certif]
Commun=

[Options]
portNumber=1
Mockup=yes
ErrMsgLng=yes
DefaultExt=40




Теперь запускаем SAT и убеждаемся, что мокап-режим включён. Логин и пароль для входа по умолчанию — admin.



А вот и главное окно SAT.



Нажимаем «File», выбираем Sign and authenticate an application. В открывшемся окне выбираем бинарник и текстовик из папки Bin нашего проекта. Далее жмякаем «OK».



Тут ничего не меняем.



Вот и всё. В папке Destination (по соседству с экзешником SAT) появляется файл *.M40. Копируем его в папку GNU_ARM_DEBUG нашего проекта. Всё, мы получили готовый пакет, пригодный для загрузки в мокап-терминал.

Запуск




Итак, проект собран. Берём наш терминал, жмякаем на нём «F» и попадаем в меню, где выбираем «Telium manager». Там выбираем «Evolution», далее «Load», «Local». На экране загорится «LLT». Далее описанным ранее образом загружаем файл *.M40 в терминал (вместе с ним подтянутся и все остальные зависимости). К слову говоря, если вы случайно загрузили нечто, что заставляет терминал зависнуть сразу после перезагрузки, повторите манипуляцию с F1-F2-F3-F4 и перезалейте Telium Manager (ОС уже не надо) и ваше приложение.

Перезагружаем аппарат, и, если всё было сделано правильно, на дисплее должно появиться примерно следующее:



Работает!

Пишем первую программу


Ну что же, время попробовать разобраться, как оно работает. Наибольший интерес для нас представляет файл entry.c, где указаны точки входа для разных состояний терминала. Для начала этого нам будет достаточно.

Итак, находим там массив idleMsg и записываем там свою строку. Далее ищем функцию idle_message и приводим её к вот такому виду:

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
    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;
}

Собираем, подписываем, заливаем.



Оно живое, оно работает!

Клавиатура


Для работы с клавиатурой существует функция keyboard_event:

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);
}

Как нетрудно догадаться, она позволяет обрабатывать нажатия кнопок на главном экране (idle state). Именно для этого служит ничем не занятый switch в начале. При этом обработку нажатия некоторых клавиш можно пропустить, так, например, «F» здесь передаётся напрямую из key_in в key_out и обрабатывается самим Telium Manager.

Принтер


Попробуем что-то напечатать.

Как оказалось, это просто:

    FILE * hPrinter;
    char * toPrinter = "Hello, Habrahabr!\nfrom MaFrance351";
    hPrinter = fopen("PRINTER","w-");     
    pprintf("\x1b""E%s\n""\x1b""F", toPrinter);
    ttestall(PRINTER,3*100);
    fclose(hPrinter);



Загружаем. Работает, однако!

Аналогичным образом можно работать с файлами, которые можно создавать непосредственно на терминале или загружать через LLT.

Помимо текста стандартная библиотека позволяет печатать и штрих-коды (пример из документации):

        unsigned char *String = "Test";
        int X = 2;  // width:  2 pixels
        int Y = 50; // height: 50 pixels
        FILE *hPrinter;
        hPrinter = fopen( "PRINTER", "w" );
        //Horizontal, centered and value printed 
        PrintBarCode128(String, X, Y, 0, 1, 1);
        fclose(hPrinter);


Меню


Попробуем создать какое-нибудь меню с выбором элементов.

Делается это так:


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;
}

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;
}

Для начала создаётся массив из строк, которые и будут пунктами меню. За его отрисовку отвечает функция ManageMenu. Для примера она вызывается тут при нажатии клавиши «F» и выборе нашего приложения.





И вот так оно выглядит на терминале.

Что же дальше?


Безусловно, Telium-2 представляет много больший интерес, нежели антикварные терминалы на Z80. На устройствах от Ingenico можно сделать ещё много всего интересного. Да что уж там — тема любительской разработки под платёжное оборудование до этого не встречалась мне решительно нигде. Несмотря на отсутствие вменяемой документации (по сравнению с тем же VeriFone, где справка про платформу Verix EVO (ровесник Telium-2) по объёму написанного напоминает целый роман и позволяет втянуться в разработку для POS-terminal'ов даже в состоянии полного (точнее, пустого) чайника), на Github можно найти примеры проектов для Telium-2, откуда можно что-то почерпнуть. Было бы интересно найти SDK для других терминалов (в частности, интересуют Ingenico Unicapt32, Lipman Nurit, NewPOS NEW8110), но пока что изыскания в данном направлении закончились ничем.

Telium-2 имеет ещё много встроенных инструментов вроде оконных приложений, экранной графики, просмотра HTML, криптографии и много чего ещё. И, разумеется, со всем этим будет крайне интересно разобраться.

Такие дела.

Ссылки




Комментарии (51)


  1. Breathe_the_pressure
    00.00.0000 00:00
    +1

    Было бы интересно найти SDK для других терминалов (в частности, интересуют Ingenico Unicapt32, Lipman Nurit, NewPOS NEW8110), но пока что изыскания в данном направлении закончились ничем.

    Нуриты это уже вообще древнее зло, их уж на рынке как лет 15-20 нет новых. Зачем они вам?

    По поводу любых китайцев, типа New POS, рекомендую напрямую им написать, типа я разработчик, дайте СДК. Шансы есть, иногда и дают под NDA.


    1. MaFrance351 Автор
      00.00.0000 00:00
      +2

      Просто у меня коллекция таких аппаратов, хочется попробовать запустить.

      Насчёт NewPOS — у них некогда был публичный FTP. Но сейчас ссылки все померли. Я пробовал им писать, сказали, что всё можно взять у представителей компании в регионе (которые по первому требованию такого не присылают). Такие дела.


      1. vladkorotnev
        00.00.0000 00:00
        +1

        В своё время писал под 8320 нурит вот этой штукой, подойдёт? :-)


        1. MaFrance351 Автор
          00.00.0000 00:00

          Хыхых. Archive.org, серьёзно? Столько искал, но на самом видном месте не нашёл…
          Неплохо, неплохо! Надо скачать, попробовать собрать что-нибудь…

          Ещё бы для 3020 найти, ну да ладно.


          1. vladkorotnev
            00.00.0000 00:00
            +2

            Архиворг вообще супер хреново индексируется, так-то там два шага от главной и попадаешь в огромный варезник в лучших традициях начала нулевых.


            Но положил я его туда не очень давно, а заполучил ещё лет 9 назад с какого-то китайского сайта, где для скачивания нужно было залить три своих файла с кодом. Ну я и залил, очистку экрана спектрума на ассемблере тремя разными способами… Вот была бы тогда ChatGPT..! :-)


            1. MaFrance351 Автор
              00.00.0000 00:00

              Не CodeForge, случаем, был?
              Я там VeriFone eVo SDK свистнул, правда, без ключа подписи…


  1. dlinyj
    00.00.0000 00:00
    +3

    Потрясающий проект.

    Когда-то пытался ковырять терминалы, но дальше сброса прошивки при вскрытии (глупый смех) у меня не пошло :).


    1. MaFrance351 Автор
      00.00.0000 00:00

      Если не секрет, что за терминалы были?


      1. dlinyj
        00.00.0000 00:00
        +1

        Секретов нет, я просто не помню… Если фотографии тех времён попадутся на глаза, скину.


  1. avbochagov
    00.00.0000 00:00
    +3

    Замеченные неточности:

    • в MockUp режиме ничего не отключается. Этот режим отличается только тем, что криптографические ключи, используемые для подписи ПО, заранее известны.

    • Перевести в MockUp можно только "чистый" терминал, в который не был инсталлирован профиль разработчика ПО (ну или VAR, как он раньше назывался). Кроме заливки MockUp ОС надо еще правильно инициализировать криптопроцессор.

    • Ключи IngeTrust никогда не удаляются из терминала (ну кроме случая срабатывания защиты от вскрытия аппарата).

    Для улучшения "обзорности" хорошо бы добавить описание криптопроцессора (что это и с чем его кушают). Это самая мякотка терминала - а про неё ни слова. Как и про крипто-схемы.


    1. MaFrance351 Автор
      00.00.0000 00:00
      +1

      Увы, документации на это всё у меня исчезающе мало. Так бы был рад написать поподробнее. Но спасибо за комментарии.

      Кстати, а почему тогда выпадет Unauthorized, если накатить ОС категории PROD на Mockup-терминал?


      1. avbochagov
        00.00.0000 00:00
        +2

        В поставке SDK есть несколько PDF, которые надо вдумчиво читать. Согласен, что изложенное в них можно понять только после трех... а то и четырех прочтений - такой там стиль изложения.

        Unauthorized...
        Дело в том, что терминалы Telium 1/2 персонализируются в два этапа: сначала прошивается профиль VAR (в нем есть криптоключ, которым потом проверяется подпись приложения), а потом инициализируется криптопроцессор (с помощью прошитого ранее ключа из профиля VAR). Получается, что в терминале есть два компонента, у которых должны быть одинаковые ключи для проверки подписи.

        Когда на готовый PROD терминал накатывается MOCKUP прошивка, то получается конфликт - операционная система использует одну крипто-подпись, а криптографический процессор требует крипто-подпись от PROD. И при проверке этого у терминала сносит башню - вроде все должно быть одиннаковое, а оно разное.
        При обратном движение - получаем такую же ситуацию: ОС стала PROD, а криптопроцессор остался MOCKUP. Результат тот же.

        Сменить инициализацию криптопроцессора можно ТОЛЬКО с помощью KIT-а (номер не помню).

        На самом деле MOCKUP - это профиль специального VAR, для разработчиков.

        Правила очень простые:

        • PROD не трогать никогда. Если потрогали - то приложить к иконе (то есть к KIT-у)

        • MockUp терминал можно делать только из чистого терминала (без загруженного VAR профиля)


        1. MaFrance351 Автор
          00.00.0000 00:00

          Интересно, однако.

          MockUp терминал можно делать только из чистого терминала (без загруженного VAR профиля)

          Получается, на уже инициализированном PROD-терминале запустить криптопроцессор в мокап-режиме не выйдет? А сменить профиль можно только при помощи KIT 43C?

          В поставке SDK есть несколько PDF, которые надо вдумчиво читать.

          А что за версия SDK, интересно? Просто у меня такого нет. Только *.CHM-файл справки с описанием некоторых функций, про VARы там ничего нет. Нарыть удалось только вот этот документ, но он по программированию, а не по персонализации. Или эти PDFки поставлялись просто вместе с SDK как часть пакета документации?

          в MockUp режиме ничего не отключается. Этот режим отличается только тем, что криптографические ключи, используемые для подписи ПО, заранее известны.

          А где хранятся эти ключи? В файле *.M40, генерируемом SAT в мокап-режиме, содержится всего лишь
          ; 
          LCD.AGN

          А в аналогичном пакете реальной прошивки вот что (фрагмент):
          ; SHA1 Checksum: 3c0f3c030554b42c7fed988060060dcaf9772805
          ; SDK 9.32.2.PatchH\Component\Barcode\Legacy\QR\8440960104.M40
          ; Component 8440960104.M40
          ; E07AB000
          8440960104.LGN
          ; SDK 9.32.2.PatchH\Component\CLess\CLESS.M40
          ; Component CLESS.M40
          ; Contactless DLL
          36550623.LGN
          ; SDK 9.32.2.PatchH\Component\DLL_ExtraGPRS\8445640119.M40
          ; Component 8445640119.M40
          ;
          8445640119.LGN
          ; SDK 9.32.2.PatchH\Component\DLL_SSL\8443650405.M40
          ; Component 8443650405.M40
          ; SSL_NO_EC
          8443650405.LGN
          ; SDK 9.32.2.PatchH\Component\Fonts\Standard\ISO5.m40
          ; Component ISO5.m40
          ; 00000000
          8442190111.PGN
          ; SDK 9.32.2.PatchH\Component\Manager\iCT_LIBGR_EXPORT_PROD.m40
          ; Component iCT_LIBGR_EXPORT_PROD.m40
          ;TELIUM MANAGER EXPORT ICT PROD
          ;INGESTATE COMPATIBILITY T2


          1. avbochagov
            00.00.0000 00:00
            +1

            Получается, на уже инициализированном PROD-терминале запустить криптопроцессор в мокап-режиме не выйдет? А сменить профиль можно только при помощи KIT 43C?

            Да, именно так.

            А что за версия SDK, интересно?

            Уже точно не помню. В комплекте было несколько PDF, причем копаться в них надо было самому. Потом французы начали перенос информации из этих PDF в CHM файлы. Сейчас, может и нет никаких PDF.

            VAR - это сокращение от Value Added Reseller. То есть компания, которая будет продавать терминал с добавленной стоимостью (как правило ПО). В документации на SDK этого никогда и не было.

            А где хранятся эти ключи?

            Этот ключ выдается отдельным файлом :) и у каждого VAR он свой. Он загружается в терминал во время инициализации. Где уж там он храниться - не знаю.
            И конечно, ключей VAR никогда не было в составе SDK.


            1. MaFrance351 Автор
              00.00.0000 00:00

              То есть компания, которая будет продавать терминал с добавленной стоимостью (как правило ПО).

              Как, к примеру, в этой стране это «Арком», который разрабатывает свой софт
              Arcus и NewWay и попутно толкает терминалы?

              Вот такой материал удалось нарыть. Это как раз про это всё?


              1. avbochagov
                00.00.0000 00:00
                +1

                Да, это именно оно. Номер в квадратных скобках - это и есть номер VAR профиля.


        1. Frappy_21
          00.00.0000 00:00

          Купил ради этой статьи терминал ict220, который ранее использовался (последние платежи датировались 18 годом) он спокойно перешел в mockup режим. Так что видимо еще зависит от банка, чье по было залито туда.


          1. MaFrance351 Автор
            00.00.0000 00:00

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


            1. Nicks_TechSupport
              00.00.0000 00:00

              поправка, уже не Арком, а Ingenico Group.
              Арком уже давно поглощена.


              1. MaFrance351 Автор
                00.00.0000 00:00

                А когда это произошло?


                1. avbochagov
                  00.00.0000 00:00
                  +1

                  в 2012


          1. avbochagov
            00.00.0000 00:00
            +1

            Теперь надо проверить функционирование крипто-процессора.
            У меня очень большие сомнения, что хотя бы одна операция на нём сработает.


  1. ruomserg
    00.00.0000 00:00

    OK, есть глупый вопрос — если уж мы вскрыли терминал, то не дешевле ли посмотреть на то, какие ноги процессора там куда распаяны, и впихнуть ему свою прошивку, которая позволит работать с экраном и клавиатурой? Понятно что он уже не будет пригоден для финансовых операций, но как выносной пульт для поделок — вполне… Если камень внутри заблокирован насмерть — ну напаять вместо него пустой…


    1. MaFrance351 Автор
      00.00.0000 00:00

      Можно, конечно. Процессор снять феном и контакты позвонить.


  1. MiraclePtr
    00.00.0000 00:00
    +2

    Круто! Хабре торт.

    Я почему-то думал что внутри таких железок крутится софт на какой-нибудь специальной версии Java (ее вроде как раз для такого изначально и делали), а там обычный Си, оказывается.

    Интересно, а есть демки под такие терминалы? Например, видео Bad Apple проиграть со цифровым звуком используя PWM через его однобитную пищалку... :)


    1. MaFrance351 Автор
      00.00.0000 00:00
      +1

      Увы, демок нет. Всё же архитектура там крайне закрытая.

      на какой-нибудь специальной версии Java (ее вроде как раз для такого изначально и делали)

      Для смарт-карт делали. Но не для терминалов.


      1. MiraclePtr
        00.00.0000 00:00

        Для смарт-карт делали. Но не для терминалов.

        Да, точно. Но с другой стороны, ещё в 90-х годах уже существовала такая штука как Java ME - вариант Java для мобильных устройств со скромными ресурсами. И сюда бы оно подошло идеально, как по мне: во-первых переносимость (работает на любом устройстве где есть реализация машины), во-вторых меньше вероятность для разработчиков выстрелить в ногу и наделать непредсказуемых багов и дыр в безопасности (чем и славятся C и C++), что в финансовой сфере должно быть важно.

        Интересно, почему не выбрали такой путь.


        1. MaFrance351 Автор
          00.00.0000 00:00

          Может, из-за необходимости работать с криптографией и кучей всякой проприетарной периферии?
          Да и куча исходников под терминалы живут ещё с тех годов, когда эти аппараты работали на Z80 или MC68000. И, конечно, написано оно всё на C. У той же VeriFone хорошая совместимость по этой части, программы для более старых терминалов сравнительно легко переписывались на новые.


        1. Greesha
          00.00.0000 00:00
          +1

          Для терминалов тоже делали. :) Были такие терминалы Jade корейской компании CyberNet. В них была своя java-подобная машина. Причём она крутилась на каких-то копеечных микроконтроллерах. Жутко глючная, долго не прожила, но позволила выйти на некоторые рынки. Следующие поколения уже использовали обычный голый Си.


          1. MaFrance351 Автор
            00.00.0000 00:00

            Интересно. Но таких никогда не держал.


        1. votez
          00.00.0000 00:00
          +1

          Для смарт-карт была вообще отдельная java card. А java-me была слишком ограничена, от собственно жавы она далека, как и жаваскрипт. В общем-то, хотели сделать унификацию, а получили еще один стандарт, который почти никто поддерживать не захотел. Уже в начале десятых годов полноценные java машины работали на платежных терминалах, одной из распространенных была ibm J9. Проблем с перифирией не было практически никаких - жава хорошо работала с библиотеками на С, которые за бутерброд в обед могли написать прожженные программисты прошлых поколений или студенты за ночь. Главное было не давать им работать творчески с памятью... Память терялась на скучной бизнес-логике куда и влезала жава, а драйвера были довольно стабильные.


          1. MaFrance351 Автор
            00.00.0000 00:00

            А теперь есть и аппараты на полноценном ведроиде, где на Java реализована и работа с шифрованием, пин-падом и этим всем.


  1. incogn1too
    00.00.0000 00:00
    +6

    Если кому-то интересно, могу написать про разработку под Verifone ????


    1. MaFrance351 Автор
      00.00.0000 00:00

      Можно, конечно. Я определённо такое оценю.
      С ними тоже доводилось дело иметь. Но софт не могу дать.
      Кстати, а про какую их платформу хотите написать? Tranz/Omni/Verix-3/Verix V/Verix eVo/Engage?


      1. incogn1too
        00.00.0000 00:00
        +2

        Я участвовал в разработке ADK и EOS слоев под VerixV, ADK и платформы под V/OS1 (Trident), ADK и платформы под V/OS2 (Raptor, он же Engage в коммерческих материалах) и платформы VAOS (Android based).


        1. MaFrance351 Автор
          00.00.0000 00:00

          Интересно будет про это почитать.


  1. Ludafr
    00.00.0000 00:00
    +1

    Очень интересные материалы в статье, с удовольствием прочла . Сейчас работаю во французской компании Preludd . Платёжные системы . И наш основной клиент - Ingenico монополист-производитель платёжных терминалов во Франции. Переведу статью коллегам на французский , если автор не возражает ( авторство укажу ).


    1. MaFrance351 Автор
      00.00.0000 00:00

      С учётом того, что я сам хорошо знаю французский, буду только рад потом прочитать, что у вас получится.


      1. Ludafr
        00.00.0000 00:00
        +1

        Здорово , постараюсь вас не разочаровать


    1. Nicks_TechSupport
      00.00.0000 00:00
      +1

      Не слышал про Preludd, а что за компания и чем занимается?


      1. Ludafr
        00.00.0000 00:00

        Это платформа сервис ( PasS) по обработке платежей в облаках Амазона , Preludd Payement System. Офис в Тулузе.


  1. push_banker
    00.00.0000 00:00
    +1

    Круто. Прям ностальгия нахлынула, как софт под эти ingenico будучи студентом писал.

    Тогда новое поколение 32битных Ingenico типа 5100 был прорыв. В предыдущем нужно было память по 32k страницы вручную переключать, а тут пиши как хочешь, не экономя каждый байт.

    И быстрее они были, помню операция rsa подписи почти без задержки впечатлила.


    1. MaFrance351 Автор
      00.00.0000 00:00

      Интересно.
      Насколько я помню, старые терминалы Ingenico (ещё до Unicapt32) работали на процессоре 186.

      Помню, begoon в своём посте про OMNI-395 упоминал, что на тот момент Ingenico в удобстве разработки сильно отставали.


      1. push_banker
        00.00.0000 00:00
        +1

        Про процессор я не помню, но может и 186, или 8088, явно младше 286. Про OMNI не скажу, но на Ingenico основным средством отладки у меня была печать на чеке. В теории была возможность запускать эмулятор и делать полноценную отладку на компьютере, но после пары случаев с различным поведением в эмуляторе и железе, все эти попытки прекращались.

        И с документацией был швах. Мне вместо нее дали контакты индуса из Парижа, которого я сильно доставал своим ужасным английским первое время.

        Еще одна фишка, которая появилась в 32битных igenico - это удобная закладка кассовой ленты. Катушку можно было просто положить и закрыть, а в предыдуших нужно было в щель бумагу просовывать и прокручивать валик.


  1. MaFrance351 Автор
    00.00.0000 00:00

    а в предыдуших нужно было в щель бумагу просовывать и прокручивать валик.

    На Nurit'ах точно так же было.


  1. votez
    00.00.0000 00:00
    +2

    больше десяти лет назад писал софт (с нуля и до прода) для терминалов в финке (белые квадратики). Писалось все на чистой Java 5 с вменяемым SDK. И стандартная жавовая многопоточность была, и ввод-вывод с файлами и сетью, только GUI был не стандартной библиотеки ну и всякие подписи транзакций с взаимодействием с contactless и проверкой чипов в SDK вендора. Писали в IntelliJ Idea и с junit тестами на локальных машинах. Вроде даже дебажить можно было на терминале, но не уверен. Рядом сидели С-шники с VeriFone и плакали. Мы выкатывали фичи с такой скоростью, что эти терминалы поставили везде и старые выкинули, оставив только, наверное, в метро да в полиции. Ну, пару лет спустя верифон компанию и купил - меня там уже не было... Так что все эти ужасы разработки инжеников и верифонов - мазохизм и жадность владельцев бизнеса.


    1. MaFrance351 Автор
      00.00.0000 00:00

      А что за терминалы были? Какой модели?


      1. votez
        00.00.0000 00:00
        +1

        Терминалы YOMANI, сейчас они YOMANI VeriFone, а тогда делали их, вроде, датчане. Конкретную модель не помню, у нас были первые модели без contactless, а модели с contactless прикручивали уже после релиза на рынок. До этого была у них (производителей) линейка Xenta, где был свой форк Java 1.3, если помню правильно - там SDK был поуродливее, но и было это лет на пять-семь раньше. Их и сейчас можно встретить иногда (широкое распространиение получили в Нидерландах) - тоже кубик, только более икея-дизайн и число-цифровой дисплей.


        1. MaFrance351 Автор
          00.00.0000 00:00
          +1

          Что-то типа такого?


          1. votez
            00.00.0000 00:00

            ага, оно.


            1. MaFrance351 Автор
              00.00.0000 00:00

              О как.
              Вообще, VeriFone много компаний поглотила. Hypercom, Lipman, Dione сходу вспоминаются…