Development of applications for Ingenico payment terminals

Tomcat

Professional
Messages
2,656
Reputation
10
Reaction score
647
Points
113
Greetings to all!

Tell me: have you ever been interested in how bank payment terminals are designed and work, to which you apply your card almost every day? Would you like to know how to write something of your own for any of these devices?

If your answer is “Yes,” then this post is definitely for you.

v6h7bdokqytawnwcdgxfaxza5i8.jpeg


Usually the topic of programming POS terminals is shrouded in secrecy, but now we will try to dispel it. In this article, we will understand the basics of development for such devices. We will find out where to download the necessary software, how to install it, and also, in fact, how to compile and run our first program. As usual, there will be a lot of interesting things.

❯ Let's go!​


In today's article we will talk about Ingenico terminals on the Telium platform. Why about them? The fact is that to obtain software for development, you usually need to contact a vendor in your region and buy it there. It is not generally available.

The only thing we managed to dig up was the one for Ingenico. That is why we will write specifically for them. What also adds to the interest is that these are not some ancient junk, but devices still used in banks.

I warn you right away that the barrier to entry into development for terminals is extremely high, and the minimal amount of publicly available documentation increases it even more. So what is presented below is based on my own experiments and their successful results. That is why this is “abnormal programming”.

Let me also remind you that this article was written, as they say, just for fun and is intended for enthusiasts like me. The author is not responsible for damaged terminals, demolished software of national importance and other production issues. If your goal is to write a commercial application, then in any case you will need to contact the Ingenico distributor in your country (in Russia this is Arcom).

❯ Equipment overview​


As I already mentioned, we will write software for Ingenico terminals, and specifically for the Telium-2 platform . And first, a little history. A long time ago in France there was such an association as SAGEM. The joint-stock company had a large number of divisions and produced a wide range of equipment: telecommunications equipment, military equipment, mobile phones, satellite communications, encryption devices. One of its areas was payment terminals, produced under the SAGEM Monetel line and operating on the Telium-1 platform. At the end of the 2000s, SAGEM was abolished, and the division for the production of payment terminals went to Ingenico. At that time, they already had their own platform, Unicapt32, but, having acquired Telium-1, they began work on the development of this much more secure and advanced platform. This is how Telium-2 appeared, around which our article will focus. Currently, the Telium TETRA platform has already been released, but tens of thousands of Telium-2-based devices are still being successfully operated around the world, fulfilling their main task - accepting bank cards and carrying out electronic transactions. Telium-based terminals are reliable, compact, easy to use, well protected and have a modern and ergonomic design. There are also disadvantages, but we will talk about them separately.

So, let's look at various representatives of POS-terminals of this company.

j0gzzfygmcholrqxqb5fvq7b0hu.jpeg


nazsayhewqhdyhpzaufo9fbk6r0.jpeg


Old terminals on the Unicapt32 platform: Ingenico 5100 and Ingenico 7910. The models are quite common; software was even written for 7910 to accept payments from OSMP (aka Qiwi).

ulyx40muea9rjqnc8scdb1jykkq.jpeg


Ingenico 3050 pin pad. Used in conjunction with Unicapt32 terminals.

7sjd7h8qptmmtvfmp3i6q5etozi.png


POS-terminal SAGEM Monetel EFTsmart on the Telium-1 platform. The same predecessor of the current Ingenico. Nearby is a PPC30 pin pad.

qouxsktchyvspn6mgdxd_z5vz2q.jpeg


And here is another pin pad, SAGEM Monetel PP 30.

_ohjfs7v76iqepcsy68r9au6q1s.jpeg


POS-terminal Ingenico ICT220 A98. ICT220 is the most popular model; I think each of you has touched a card to such a device at least once in your life. There is also a version of ICT250 with a color display, as well as IPP320/350 - essentially the same ICT220/250, but without a printer and controlled externally (they are the ones at supermarket checkouts).

hx4yooitwley58ugduvfupgmyse.jpeg


ICT220 C98. This is a newer version of the previous device, equipped with new firmware that meets newer security standards. By the way, pin pads also have such a separation, so the IPP220 A98 will not work on the C98 POS terminal, and vice versa.

wdevzul5svmmxtbn7bbxwyej5eu.jpeg


IPP220. Pin pad for the ICT220/250 terminal, connected to it via USB.

w6fajvin_4rlqktq_azk7jl3qf8.jpeg


IWL250. A portable terminal with a color display, used in cafes, restaurants, bars and other similar establishments. Alas, my copy is not working.

8vcgycheftgeuz5zn9bo3gguocy.jpeg


ryqxsv4ipsjpga5b57nr2g7y41o.jpeg


IPA280. Data collection terminal on Windows CE with printer and pin-pad. The piece of hardware is extremely interesting, most likely I will even write a separate post about it.

fvyjeqys9clyudhz7aichrwtmgo.jpeg


sgcurq8nvhlz7zj9bojcxuiuaeo.jpeg


ISMP. Mobile POS-terminal, which is a case for iPhone 4 with a built-in pin pad on the Telium platform and a barcode scanner.

So, as you can see, the Telium-2 platform offers a wide selection of devices for any application. Despite the release of newer devices, you will probably find the mentioned copies at some of the retail outlets in your city.

❯ Internals​


Let's take a closer look at ICT220. It is for him that we will write software.

The characteristics of the terminal are as follows: an ARM9 processor (plus a separate ARM7 for encryption), sixteen megabytes of RAM and Flash (but this is what is available to the user, in reality it is much more). Interfaces include USB, RS-232, Ethernet, modem, USB host.

Attention! Opening the terminal will lead to its 100% damage. After this, they can only restore it at the vendor’s representative office for a lot of money (in most cases, it’s easier to throw out a blocked terminal than to sort it out). Do not disassemble devices that are in use. The copy shown below was given to me already dead. I wrote a separate post about what security systems POS terminals have .

hprt27malsq11-bpfazjuw2kdzo.jpeg


And here is the experimental specimen. It is inoperative, so the only thing left to do with it is to disassemble it for parts.

_ozwhv8ztqkm9cwbex7zig6tlkw.jpeg


Back side. USB-B for connecting to a computer and USB-A for external devices. The COM port is connected to the miniUSB connector (but the signal there is not USB!). Telephone line port. To save space and force you to buy proprietary cords instead of the standard 8P8C it costs 4P4C, so the cable uses only two middle twisted pairs. At the bottom there are slots for SAM cards and connectors for SIM and microSD, invisible from this angle.

otmccdneufhb2db_tlbqoeoibvq.jpeg


grrxcqd9lp_otonedjljyxqmnqy.jpeg


6qyasrsfbkyaaugmebuqa-nuimo.jpeg


fjtxdjt3_ougneiwbiwjht6n6cg.jpeg


Let's sort it out. There are a lot of proprietary chips on the motherboard. A GSM modem from Sagemcom is located on a separate board. The display is labeled M0412 SHON58 , Google has no idea what model it is and what kind of controller it is. On the modem board there is a certain element similar to a lithium battery cell - this is a supercapacitor. There is a CR2450 battery in the half-cylinder shaped protrusion on the case next to the ports.

e7wicdzo-f2gaic3juaycr2nihg.jpeg


GSM modem antenna.

ayilp2yc713rh1yduxm1pwyeays.jpeg


Modem removed from the board.

g5rwjcrumhyqymqp14conf4vgww.jpeg


vqkztkotckdpo8omlnfgtmmgnyo.jpeg


Motherboard. A long-dead battery, components covered with a metal shield. The smart card reader is protected by a security loop.

The chips on the board are:
  • SI3018-FS, SI3056-FS - modem chipset
  • SMsC LAN8700-AEZG - Fast Ethernet controller
  • ZT3223E – RS-232 level converter
  • MONEFT3X SPIIIM 1215 1T6239-1 - proprietary encryption processor
  • WE-MIDCOM 7090-37 GV1217LF1 – Ethernet transformer
  • LB1838 - low-voltage bipolar stepper motor driver

oaywp-fziropfqueuts9eqwirdo.jpeg


Unscrew the protective screen. It fits very tightly, it was impossible to remove it without bending it. Underneath it is a processor (also custom-made), memory chips and ROM. All terminals on the Telium-2 platform are built on the MONEFT3X chipset.

zccgh1e2ry9spkzaqql8ognmy2i.jpeg


msvpfhxrar9sxxyw2aa8s5aituk.jpeg


vcfzq8qoid4to8rmiwhznoipaok.jpeg


A printer. The standard parameters for the terminal are fifty-seven millimeter tape, three hundred and eighty-four dots. Manufactured by Alps Electric, I couldn’t find the datasheet right away.

ywyomaobdmwukzxksx0vgl4idbm.jpeg


Magnetic head.

❯ Install the software​


Well, let's move directly to the software.

In order to start developing for Telium-2, we will need the following set of software:
  • IngeDev (development environment and compiler)
  • Telium SDK (libraries and components)
  • SAT (Software authentication tool, software signature tool)
  • LLT (Local loading tool, a utility for loading an application into the terminal)

A special “developer” terminal will also be needed. We'll talk about where to get it a little later.

The software itself can be found here. Once upon a time he was here, but the links have long since died. Of all this heap, we are interested in the Installed SDKs archive, IngeDev.rar . You also need the latest version of the LLT nearby. You can use an older one, but using it is a pain.

We put LLT. Along the way, drivers will also be rolled out. This stage should not cause any special problems.

Now it’s the turn of the big archive. Unpack it to the root of your system disk. We are interested in the folders SDK 9.10.2 , TELIUM Tools and Ingenico . Go to Ingenico and run IngeDev.exe . Next, you need to create a workspace, similar to how it is done in Eclipse (IngeDev is based on this IDE).

ukexhaysy3bcz718e6f9wnfjiog.png


Next, you need to connect the Telium SDK, for which click on the “Window” menu bar, then “Preferences”, open “Installed Telium packages”. Click “Add” and select SDKDescriptor.xml in the folder with the SDK version you need.

zq1vvwpvbx2nw73y9-ugvul_4su.png


Now click “File”, create a new “Telium project”. Leave “Telium application”, enter the desired name and select “Finish”. We will create a new project, where there will be two files in the Src folder: main.c and entry.c . We will look at them in more detail later.

main.c
Code:
/** 
* \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
Code:
/**
* 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

In essence, this is a minimal working application that can be loaded into the terminal.

k3uhnt4yhpp0e0c6nf_cre4xqcy.jpeg


After that, click “Project”, select “Rebuild” (when you simply click “Build” it gives an error).

a9rcw9jpct--albkaussri-ndec.jpeg


The build process will begin, and if everything works without glitches, it will end successfully, and the folder “Bin\GNU_ARM_DEBUG” will appear in the project directory, and in it there will be several files (the most important of them for us are *.bin and *.txt) .

This completes the software installation process. However, you can’t just run it on a terminal. Why this is so and how to deal with it, we will now find out.

❯ Alert irruption!​


And first, let's figure out why Ingenico cannot be disassembled.
In its terminals, Ingenico has come up with a truly incredible security system: the terminal memory contains certain IngeTrust keys, which are used to protect operations carried out inside the device, including signing applications.

hzmn_g4mojvca6rlbta85hipvai.jpeg


Their presence is displayed when the terminal starts: it should show a cheerful emoticon. Accordingly, sad means that the keys are missing and the device has been locked.

rmaghgt4hng0bk6e2mepciuy87w.jpeg


The most common error is Alert irruption, which signals that the tamper has been triggered. In this case, all cryptographic operations are blocked, all keys are erased, and loading other firmware is not possible. It is in the IngeTrust keys that it is impossible to reset the tamper on Ingenico: you not only need to reset the flag, but also upload new low-level keys.

zzl6zmn3tsk1i8rhul0jcx_9iww.jpeg


Another (no less fatal) option is the Unauthorized error. It means that the tamper did not work, but there are still no keys. Most often, the terminal dies with such symptoms due to the discharge of the internal battery. That is, the terminal will probably be dead after lying for a long time. For approximately the same reason, there are no longer any working terminals on the Telium-1 platform.

Only a representative of the Ingenico terminal vendor in your region can reset this error. During this procedure, a certain KIT 43C is used , which is a regular terminal on Telium-2 OS with special software. Such devices are strictly in limited access to Ingenico representatives. The terminal has a dedicated Internet connection, each operation to obtain keys is registered with Ingenico.



Well, this is what the terminal reactivation process looks like. Alas, the video is not mine, so I can’t say anything about the details of the process. As I understand it, first the tamper flag is reset using special software, then fresh IngeTrust keys are loaded.

This procedure is not cheap; they will charge you around five thousand rubles to clear this error. Sometimes it turns out that it’s easier to throw away a dead terminal and buy a new one in return.

❯ Signing applications​


Now let's look at the software itself. To sign it, two utilities are used - Software Signature Tool (SST) and Software Authentication Tool (SAT). The differences between them are primarily that the first is designed for Telium-1, and the second is for Telium-2. Of course, we are primarily interested in the second option.

ofr5yw4z97k1gupeeqtlmjyuhx0.png


Here is the data exchange diagram for signing an application, located in the SAT manual. To release your software, you need to obtain a terminal with special firmware from the vendor. Next, you order a smart card from Ingenico with your personal developer certificate. The terminal connects to the network and to a computer running SAT software. To sign the application, you need to insert the card into the terminal, enter its PIN, then select a package in SAT and, in fact, send the request. The terminal will connect to the Ingenico server, where it will receive the keys. Thus, it is impossible to sign applications “on the spot”.

cstyvrpfiwsp_dhpa_ixvjg6gq4.png


With SST, in this regard, everything is somewhat simpler; you only need a key in a container on a smart card and a certificate.

vec3jovdm5rgyuw2dikj8gry0a8.jpeg


zgf7zombhoogl5dmsxyhrmmreo0.jpeg


I even found a reader like the one in this illustration, and it is Gemplus GemPC410. At one time I bought it brand new for three hundred rubles to play around with reading smart cards.
Such a system provides a fairly high level of protection, but it is inconvenient to request keys every time during application development, so the terminals provide a special “developer” mode - Mock-up mode (or simply MOCKUP). It disables the tamper, signature verification and some other restrictions inherent in standard (Production, PROD) firmware.

❯ Mockup​


In order to activate this mode, you need to reflash the OS.

Attention! The MOCKUP activation procedure cannot be canceled! An attempt to return back to PROD mode will lead to the terminal becoming bricked!

d0hwmvvj5mi7ritcysulm5yqnme.jpeg


So, we will assume that you have made up your mind. We connect the terminal with a cable to the computer, hold down the F1 key and apply power. After the “star” appears, quickly press F2-F3-F4. If you managed to do this, “LLT” will light up on the screen.

i2msf-otegyz6hi4kfjjc5azjfm.jpeg


We launch this very LLT on the computer.

vvibk8s3n-vuvoeqsxlvbanr8py.jpeg


Double-click on the line with the terminal model in the lower right part of the window. The connection should be established.

6ccgjc3fso_qc3ncyrzpayedrss.jpeg


Next, download the required package from the OS, in my case - ICT2XX_MOCKUP.m40 . It lives in the Components folder of the Telium SDK. Right-click on the line with the name of the package and select “Download”. That's it, the process has begun.

x_wsdo9i_kni1ckott7hylpm9nm.jpeg


We complete the connection with the terminal (click twice in the same place where we connected it). The terminal will freeze for a while, flash the backlight, beep through the speaker, and then reboot several times. If everything was done correctly, “LLT” will light up on the screen again. In this case, the “Special Mock-up” message will flash once per second, similar in style to the “Unauthorized” message. But in this case, this is not an error, but just a warning that protects against installing a “developer” terminal at a point of sale. But do not forget that activating the mockup leads to the deletion of IngeTrust keys, which is why when you install a regular OS, the terminal will display the “Unauthorized” error. However, it will not be possible to activate the mockup on an already locked terminal, since the distribution kit of the MOCKUP version itself is signed with PROD keys, which is why the terminal will display a signature error when trying to download something. So if you want to repeat what is shown in this article, you need to find a working terminal.

5_9lfdb7muae7tpm3r-diilxhny.jpeg


ksavtz0il_5jgtkonnlmcjy4uzc.jpeg


f0xir2iayr6jhh9n13n-rd6gq6s.jpeg


x9pe1szxfyti-eanec3w4j5hfcg.jpeg


Similarly, we establish a connection and roll from the adjacent Telium Manager folder. After a simple setup process, the terminal is ready to accept applications.

ezzwyhep8_hjndff--qppaw1jrq.jpeg


The result of all this should be approximately what is on the screen.

❯ SAT​


As we remember, a regular project can be launched right away on the terminal. Why don't we try loading a freshly compiled program? Although Mock-up eliminates the need for developers to have a smart card, the application must still be signed. So we open SAT, which is archived in the TELIUM tools folder.

In the folder with it there is a file SAT.ini, which must be opened in a text editor and activated Mock-up there. Like that:

SAT.ini

6t_rkzvriniudeexu_kxe0trkqa.png


Now we launch SAT and make sure that mocap mode is enabled. The default login and password is admin .

f36stxgygbp-hnckdufifdiabuw.png


And here is the main SAT window.

40epiuqnmploz5oblkjwjjwfsfo.png


Click “File”, select Sign and authenticate an application . In the window that opens, select the binary and text file from the Bin folder of our project. Next, click “OK”.

_mn8liactmcy4_mboh4_qduhs98.png


We are not changing anything here.

ccuku7sw5-ypmx2mhci7cj7odk0.png


That's all. A *.M40 file appears in the Destination folder (next to the SAT executable). We copy it to the GNU_ARM_DEBUG folder of our project. That's it, we received a ready-made package suitable for loading into a mock-up terminal.

Launch​


g3unte8imzo9cgmza-ur6iwwmwy.jpeg


So, the project is assembled. We take our terminal, press “F” on it and get into the menu where we select “Telium manager”. There we select “Evolution”, then “Load”, “Local”. “LLT” will light up on the screen. Next, in the manner described earlier, load the *.M40 file into the terminal (along with it, all other dependencies will be pulled up). By the way, if you accidentally downloaded something that causes the terminal to freeze immediately after a reboot, repeat the manipulation with F1-F2-F3-F4 and reload Telium Manager (the OS is no longer needed) and your application.

We reboot the device, and if everything was done correctly, something like the following should appear on the display:

edn_-jbjcip-tefgsiuadeh6s58.jpeg


It works!

❯ We write the first program​


Well, time to try to figure out how it works. Of greatest interest to us is the entry.c file , which specifies the entry points for different terminal states. To begin with, this will be enough for us.

So, we find the idleMsg array there and write our line there. Next, we look for the idle_message function and bring it to this form:

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

We collect, sign, upload.

0nr1nbquff0supjt1j79zvlveqm.jpeg


It's alive, it works!

❯ Keyboard​


To work with the keyboard, there is a function keyboard_event :

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

As you might guess, it allows you to process button presses on the main screen (idle state). This is exactly what the unoccupied switch at the beginning is for. In this case, the processing of pressing some keys can be skipped, for example, “F” here is transferred directly from key_in to key_out and is processed by Telium Manager itself.

❯ A printer​


Let's try to print something.

As it turns out, it's simple:

Code:
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);

nipdoyygmetobdhs377x14wptio.jpeg


Loading. It works, however!

Similarly, you can work with files that can be created directly on the terminal or downloaded via LLT.

In addition to text, the standard library allows you to print barcodes (example from the documentation):

Code:
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);

❯ Menu​


Let's try to create some kind of menu with a selection of elements.

This is done like this:

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

First, an array of strings is created, which will be the menu items. The ManageMenu function is responsible for its rendering . For example, it is called here when you press the “F” key and select our application.

lmle0yjc-99ig-m8990lpa7kf8k.jpeg


b2gnieq8ttp1q_azwk97jhs0n38.jpeg


And this is what it looks like on the terminal.

❯ What's next?​


Of course, Telium-2 is much more interesting than antique terminals on the Z80. You can do a lot more interesting things on devices from Ingenico. What’s more, the topic of amateur development for payment equipment has never come across to me anywhere before. Despite the lack of sane documentation (compared to the same VeriFone, where the help about the Verix EVO platform (the same age as Telium-2) in terms of the volume of what is written resembles an entire novel and allows you to get involved in the development of POS-terminals even in a state of complete (more precisely, empty ) teapot), on Github you can find example projects for Telium-2, from which you can learn something. It would be interesting to find an SDK for other terminals (in particular, Ingenico Unicapt32, Lipman Nurit, NewPOS NEW8110 are of interest), but so far research in this direction has ended in nothing.

Telium-2 has many more built-in tools like windowing applications, screen graphics, HTML viewing, cryptography and much more. And, of course, it will be extremely interesting to understand all this.

So it goes.

❯ Links​

 
Top