Приветствую всех!
Скажите: интересовались ли вы хоть раз тем, как устроены и работают банковские платёжные терминалы, к которым вы прикладываете свою карту едва ли не ежедневно? Хотели ли вы узнать, как написать что-то своё под какое-нибудь из данных устройств?
Если ваш ответ — «Да», то этот пост определённо для вас.
Обычно тема программирования 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-модема.
Модем, снятый с платы.
Материнская плата. Давно севшая батарейка, закрытые металлическим экраном компоненты. Считыватель смарт-карт закрыт контуром безопасности.
Из чипов на плате находятся:
Отковыряем защитный экран. Сидит он очень плотно, снять и не погнуть не вышло. Под ним процессор (тоже заказной), чипы памяти и ПЗУ. На базе чипсета MONEFT3X построены все терминалы на платформе Telium-2.
Принтер. Стандартные для терминала параметры — лента на пятьдесят семь миллиметров, триста восемьдесят четыре точки. Произведён фирмой Alps Electric, даташит сходу найти не смог.
Магнитная головка.
Ну что же, перейдём непосредственно к софту.
Для того, чтобы начать разработку под Telium-2, нам понадобится следующий набор софта:
Также будет нужен специальный «девелоперский» терминал. О том, где его взять, поговорим чуть позже.
Сам софт можно взять тут. Когда-то давно он был и тут, но ссылки давно померли. Из всей этой кучи нас интересует архив 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. В дальнейшем мы разберём их поподробнее.
По сути это минимальное рабочее приложение, которое можно загрузить в терминал.
После этого нажимаем «Project», выбираем «Rebuild» (при нажатии просто «Build» выдаёт ошибку).
Начнётся процесс сборки, и, если всё заработает без глюков, она закончится успешно, а в директории проекта появится папка «Bin\GNU_ARM_DEBUG», а в ней — несколько файлов (самые важные из них для нас — *.bin и *.txt).
На этом процесс установки ПО завершён. Тем не менее, запустить его на терминале просто так не выйдет. Отчего так и как с этим бороться, сейчас узнаем.
И для начала давайте разберёмся, почему же 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. После несложного процесса настройки терминал готов принимать в себя приложения.
Итогом всего этого должно стать примерно то, что на экране.
Как мы помним, штатный проект уже сходу можно запустить на терминале. Отчего бы нам не попробовать загрузить свежескомпилированную прогу? Хотя Mock-up освобождает от необходимости иметь смарт-карту разработчики, приложение всё равно должно быть подписано. Так что открываем SAT, который в архиве лежит в папке TELIUM tools.
В папке с ним лежит файл SAT.ini, который необходимо открыть в текстовом редакторе и активировать там Mock-up. Примерно так:
Теперь запускаем 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 и приводим её к вот такому виду:
Собираем, подписываем, заливаем.
Оно живое, оно работает!
Для работы с клавиатурой существует функция keyboard_event:
Как нетрудно догадаться, она позволяет обрабатывать нажатия кнопок на главном экране (idle state). Именно для этого служит ничем не занятый switch в начале. При этом обработку нажатия некоторых клавиш можно пропустить, так, например, «F» здесь передаётся напрямую из key_in в key_out и обрабатывается самим Telium Manager.
Попробуем что-то напечатать.
Как оказалось, это просто:
Загружаем. Работает, однако!
Аналогичным образом можно работать с файлами, которые можно создавать непосредственно на терминале или загружать через LLT.
Помимо текста стандартная библиотека позволяет печатать и штрих-коды (пример из документации):
Попробуем создать какое-нибудь меню с выбором элементов.
Делается это так:
Для начала создаётся массив из строк, которые и будут пунктами меню. За его отрисовку отвечает функция 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, криптографии и много чего ещё. И, разумеется, со всем этим будет крайне интересно разобраться.
Такие дела.
Скажите: интересовались ли вы хоть раз тем, как устроены и работают банковские платёжные терминалы, к которым вы прикладываете свою карту едва ли не ежедневно? Хотели ли вы узнать, как написать что-то своё под какое-нибудь из данных устройств?
Если ваш ответ — «Да», то этот пост определённо для вас.
Обычно тема программирования 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!). Порт телефонной линии. Для экономии места
Разбираем. На материнке куча проприетарных чипов. На отдельной плате размещён 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(¶m_changed, param_in,sizeof(param_changed));
fopen("DISPLAY","w");
printf("MODIF_PARAM\n%04x",(int)param_changed.etatout.returned_state[0].state.mask);
ttestall(0,200);
fclose(stdout());
return(FCT_OK);
}
/**
* It's activated each time Manager wants to run a downloading session (local or remote).
* "F" key: Evolution->Load->Local or Evolution->Remote Load
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - response: REP_OK (application authorizes donwloading process)
* REP_KO (application refuses any downloading process)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_evol_pg(NO_SEGMENT no, void *p1, S_ETATOUT *param_out)
{
int retour;
// Downloading process allowed?
param_out->returned_state[param_out->response_number].state.response=REP_OK;
retour = is_name (no, PT_NULL, param_out);
return(FCT_OK);
}
/**
* It's activated each time Manager wants to delete an application.
* "F" key: Deletion
* \param no (-I)
* \param p1 (-I)
* \param param_out (-O)
* - response: DEL_YES (application authorizes deletion process)
* DEL_NO (application refuses any deletion process)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_delete(NO_SEGMENT no, void *p1, S_DELETE *param_out)
{
// Deletion process allowed?
param_out->deleting=DEL_YES;
return (FCT_OK);
}
/**
* Manager reports parameters file received from LLT.
* It's activated upon reception of a parameter file by the manager.
* \param no (-I)
* \param param_in (I-)
* - volume_name: SYSTEM (File loaded in CFS)
* HOST (File loaded in DFS).
* - file_name: Application file name
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int file_received(NO_SEGMENT no, S_FILE *param_in, void *p2)
{
FILE *prt;
S_FS_PARAM_CREATE ParamCreat;
int Ret;
char Dir_File[25];
char Dir_Label[25];
int len;
char rsp[256];
S_FS_FILE *pFile;
// Print parameter file received
prt=fopen("PRINTER","w-"); // Open printer driver
pprintf("\x1b""E%s\n""\x1b""F", appName);
pprintf("File Received :\n/%s/%s\n",param_in->volume_name,param_in->file_name);
ttestall(PRINTER,0); // Print volume+file_name
memclr(Dir_File,sizeof(Dir_File));
memclr(Dir_Label,sizeof(Dir_Label));
sprintf(Dir_Label,"/%s",param_in->volume_name);
ParamCreat.Mode = FS_WRITEONCE;
Ret = FS_mount (Dir_Label,&ParamCreat.Mode);
if (Ret == FS_OK)
{
sprintf(Dir_File,"/%s/%s",param_in->volume_name,param_in->file_name);
pFile = FS_open (Dir_File, "r"); // The file can be open at this stage
// Eventually read the file and get parameters
len = FS_length(pFile); // File length in bytes
if(len > sizeof(rsp)) {
len = sizeof(rsp);
}
FS_read(rsp, len, 1, pFile); // Read from file
FS_close(pFile); // Close the file
FS_unmount(Dir_Label); // Cannot be deleted as it is located in system disk
}
pprintf("%s\n", rsp);
fclose(prt); // Close printer driver
return (FCT_OK);
}
/**
* Inter application messaging.
* It's activated each time Manager received a message in its mailbox for this application.
* \param no (-I)
* \param param_in (I-)
* - sender: Sender ID
* - receiver: Receiver ID
* - type: IAM type
* - length: Message length
* - value: Message received
* \param p2 (-I)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int message_received(NO_SEGMENT no, S_MESSAGE_IAM *param_in, void *p2)
{
FILE *prt;
// Print message received from application 2
prt=fopen("PRINTER","w-"); // Open printer driver
pprintf("\x1b""E%s\n""\x1b""F", appName);
pprintf ("Message IAM :\n");
pprintf ("S:%04X R:%04X\n", param_in->sender, param_in->receiver); // USER2 to TEMPLATE
pprintf ("IAM Type : %04X \n\n", param_in->type);
pprintf("%s\n\n\n\n\n\n", param_in->value); // Print the message received
ttestall(PRINTER, 2*100);
fclose(prt); // Close printer driver
return (FCT_OK);
}
/**
* It's activated when a card is inserted, swiped or manually entry.
* Ask the application if the card need a specific processing.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - response: REP_OK (card processing)
* REP_KO (no card processing)
* Only one application wants to process the card, manager calls CARD_INSIDE entry.
* More application wants to process the card, manager asks for card removal.
* If no application wants to process the card, manager goes on with selection process.
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_card_specific(NO_SEGMENT no, S_TRANSIN *param_in, S_ETATOUT *param_out)
{
int ret;
// Return application state
param_out->returned_state[param_out->response_number].state.response = REP_KO;
ret = is_name (no, PT_NULL, param_out);
return (FCT_OK);
}
/**
* It's activated when an application has chosen to treat this card has specific.
* The transaction is done here.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - rc_payment: PAY_OK (Transaction done)
* PAY_KO (Transaction rejected)
* If an application returns STOP, polling is stopped and manager asks for card removal.
* The application is in charge to ask for amount and currency if needed.
*
* \return STOP: Card accepted and transaction process done, polling is stop.
* FCT_OK: Card refused and poll the next application.
*
* \see sdk30.h
*/
int card_inside(NO_SEGMENT no, S_TRANSIN *param_in, S_TRANSOUT *param_out)
{
bool card_accepted = TRUE;
if (card_accepted)
{
// Return transaction status
param_out->rc_payment = PAY_OK; // Transaction done, polling is stop
return (STOP);
}
else
{
return (FCT_OK); // Card refused, poll the next application
}
}
/**
* Ask application to recognize the magnetic, smart or manually card in order to be
* a candidate.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - cardappnumber: 1 = Card accepted
* 0 = Card rejected
* - cardapp: CARD_PROCESSED (low priority)
* CARD_RECOGNIZED (medium priority)
* CARD_PRIORITY (high priority)
* - appname: Application name
* - no: Application number
* - response_number: should be incremented
*
* \return FCT_OK
*
* \see sdk30.h
*/
int is_for_you_after(NO_SEGMENT no, S_TRANSIN *param_in, S_CARDOUT *param_out)
{
// case of chip card
if (param_in->support == CHIP_SUPPORT)
{
if(param_in->power_on_result == 0)
{
// accept this card
param_out->returned_state[param_out->response_number].cardappnumber = 1;
param_out->returned_state[param_out->response_number].cardapp [0].priority = CARD_PROCESSED;
}
else
{
// reject the card
param_out->returned_state[param_out->response_number].cardappnumber = 0;
}
}
// case of stripe 2 card
if (param_in->support == TRACK2_SUPPORT)
{
// accept this card
param_out->returned_state[param_out->response_number].cardappnumber = 1;
param_out->returned_state[param_out->response_number].cardapp [0].priority = CARD_PRIORITY;
}
// case of Card Number Manual entry
if (param_in->support == OPERATOR_SUPPORT)
{
// accept this card
param_out->returned_state[param_out->response_number].cardappnumber = 1;
param_out->returned_state[param_out->response_number].cardapp [0].priority = CARD_PRIORITY;
}
// give my application name
strcpy (param_out->returned_state[param_out->response_number].appname, appName) ;
// give my application number
param_out->returned_state[param_out->response_number].no_appli = no;
// give my card name
strcpy (param_out->returned_state[param_out->response_number].cardapp [0].cardappname, "Template") ;
// increment the response number
param_out->response_number++;
return (FCT_OK);
}
/**
* Process a non EMV chip card or a magnetic card or manual entry transaction.
* \param no (-I)
* \param param_in (-I)
* \param param_out (-O)
* - rc_payment: PAY_OK (Transaction done)
* PAY_KO (Transaction rejected)
*
* \return FCT_OK
*
* \see sdk30.h
*/
int debit_non_emv (NO_SEGMENT no, S_TRANSIN * param_in, S_TRANSOUT * param_out)
{
FILE *prt;
int i;
prt = fopen("PRINTER", "w-");
// case of chip card
if ( param_in->support == CHIP_SUPPORT )
{
pprintf("\x1b""E%s\n""\x1b""F", appName);
if (param_in->historical_bytes.length != 0)
{
pprintf("Atr:\n");
for (i=0; 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, криптографии и много чего ещё. И, разумеется, со всем этим будет крайне интересно разобраться.
Такие дела.
❯ Ссылки
- Софт
- Ещё немного про программирование этих устройств
- Порты арканоида и змейки на ICT220. Увы, сходу запустить не удалось, а полноценного проекта у автора нет.
- Проект для ICT220
- Ещё один проект
- Пост про программирование антикварного терминала на Z80
- Пост про системы защиты в пин-падах