Perfpowerservice mac os ��� ���

Сбербанк или туда и обратно

ГЛАВА 1. Нежданные гости

Все началось в то злополучное утро, когда Project Manager сообщил, что сроки реализации проекта должны быть быстро и решительно сокращены на месяц. Точнее говоря проект должен быть готов через 4 дня. Нет, наш PO не зверь, и ничуть не похож на сову (разве что чуть-чуть на ворона), просто так сложилось. Ну раз надо, так надо, тем более что команде (а я являюсь ведущим разработчиком команды «С») было обещано что-то вкусное. На часах и календаре был четверг, 11:00, к понедельнику проект должен быть готов.

Для начала, чем мы вообще занимаемся. Мы занимаемся автоматизацией кинотеатров — автоматическим и дистанционным управлением оборудования, автоматизацией кинопоказа, мониторингом, видеопанелями, а теперь еще и терминалами продажи билетов и бара. Конкретно последнему пункту и посвящена данная статья.

Сам проект, который нужно было завершить до понедельника представляет из себя некую прослойку между основным сервером на Scala и железным терминалом оплаты VeriFone VX 820 (на самом деле терминалов больше, но для примера возьмем только его). Понятно, что просто так проводить через него транзакции нам никто не даст, поэтому используются утилиты и библиотеки Сбербанка/Arcus и UCS. Таким образом схема работы в итоге должна быть следующей:

Внешне он выглядит вот так:

Также данная подсистема должна использоваться на стандартных кассовых машинах которые все видели в любом кинотеатре у кассиров.

Согласно внутренней традиции каждый проект нашей команды мы называем именем из древнескандинавской мифологии, для данной подсистемы было выбрано имя Gefjon — Имя богини плодородия и изобилия (неплохое название для сервера оплаты, разве нет? Ну и легенда о быках отрезающих остров идеально ложится на текущую архитектуру, отрезая работу с оборудованием от высокоуровневого языка).

Формат входящих и выходящих сообщений — HTTP сервер с JSON нагрузкой. Это оптимальный компромисс между Scala, которой сложно опуститься до вычленения бинарных данных из socket-потоков и C, которому трудно подняться до передачи объектов через сеть. Возможных операций, которыми необходимо оперировать не так много: оплата, отмена, возврат, разные типы отчетов, открытие сервисного меню и ping. С виду ничего сложного. Так как банковских систем целых три (а в последствии ожидается пополнение семейства), то было решено разделить проект на компоненты:

Зеленым покрашены блоки, которые нам нужно было сделать, синим — те, которые нельзя поменять и которые предоставляет банк.

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

ГЛАВА 2. Баранье жаркое


(фото: heaclub.ru)

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

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

Каждый банк, который предоставляет библиотеки для работы с терминалом обычно предоставляет два варианта подключения: через функции библиотеки (.so/.dll) или посредством готовой утилиты, которой всего-то нужно передать два значения — тип операции и сумму (когда нужно). В теории ничего сложного, всего-то

Результат операции при этом будет помещен в файл «e», а слип-чек — в файл «p». Просто отправим эти файлы на stdout с преобразованием в JSON, чтобы HTTP-сервер просто отправил их наверх как payload без размышлений о том, что там.

Читайте также:  Аналог exe для линукс

Но эта статья не вышла бы, если бы все было так просто.

ГЛАВА 4. Через гору и под горой

Первоначальный вариант реализации представлял из себя простой вызов приложения — HTTP-сервер вызывал нужную обертку с унифицированными параметрами (например X-отчет это 4), а утилита например gfj_pilot запускала sb_pilot с параметром, который требовался для это операции (например X-отчет это 9). Затем утилита-обертка читала из е-файла результат операции (например 2000 — «отказ оплаты, повторите операцию») и преобразовывала в универсальную ошибку (например 3 — «Ошибка чтения или процессинга карты/счета, повторите операцию»). После этого файл «p» преобразовывался в base64 для избежания ломания форматирования и отсылался вместе с результатом в stdout в виде JSON.

Все это прекрасно работало, пока в один прекрасный момент нам не сообщили, что…

… это не работает под Windows.

Ну точнее у самого Windows проблем нет (кроме того, что слип генерируется в кодировке Cp-1251, а консоль работает в CP866). Просто не генерировался «е» файл. Запустили банковскую утилиту напрямую:

Действительно, «e»-файла нет. Камень в сторону Сбербанка #1. Пишем письмо в сбербанк (впоследствии получили ответ, что так и должно быть), а так как времени на переписку нет и надо запускаться вот прям уже, ищем обходные пути получения результата.

Ага, результат можно получить из лога sbkernelГГММ.log. Неудобно, плюс нет хеша карты чтобы впоследствии прикрутить «Спасибо» от сбербанка. Не годится.

Придется подключаться к библиотеке pilot_nt.dll и импортировать из нее функции. Все бы ничего, но… Камень в сторону Сбербанка #2: под Linux такой библиотеки нет, придется создавать два разных приложения под разные платформы — для linux вызывать утилиту sb_pilot (аналог loadparm.exe, кстати камень #3 за разное название утилиты под разными платформами), под windows подключаться к библиотеке pilot_nt.dll.

ГЛАВА 5. Загадки в темноте

Сбербанк — компания крупная, большинство программных решений производятся по ГОСТам и формальным документам. Залезаем в каталог, который поставляет Сбербанк вместе с библиотеками:

Куча добра, однако нас интересует только каталог для разработчиков:

Много макулатуры, на всякий случай еще раз перечитаем pilot_nt, из которой узнаем следующее:

Таблица 1. Поддерживаемые sb_pilot ОС.

ОС Разрядность Имя модуля
Windows 32 sb_pilot.exe
Linux 32 sb_pilot
DOS 16 sb_pilot.exe

Передача результатов работы программы.

По окончании работы программы формируются два текстовых файла — файл обмена и файл чека.

Первый имеет имя e и предназначен для передачи вызывающей программе параметров совершенной операции. Первая строка в этом файле содержит код результата операции, и через запятую – поясняющее текстовой сообщение. Код 0 означает успешное проведение платежа, любое другое значение – отказ или невозможность проведения платежа.

Лениво кидаем еще один камень и начинаем изучать документацию на подключение библиотеки напрямую.

При оплате (возврате) покупки по банковской карте кассовая программа должна вызвать из библиотеки Сбербанка функцию card_authorize(), заполнив поля TType и Amount и указав нулевые значения в остальных полях. По окончании работы функции необходимо проанализировать поле RCode. Если в нем содержится значение «0» или «00», авторизация считается успешно выполненной, в противном случае отклоненной. Кроме этого, необходимо проверить значение поля Check.

Если оно не равно NULL, его необходимо отправить на печать (в нефискальном режиме) и затем
удалить вызовом функции GlobalFree(). При закрытии смены кассовая программа должна вызвать из библиотеки Сбербанка функцию close_day(), заполнив поле TType = 7 и указав нулевые значения в остальных полях. По окончании работы функции необходимо проверить значение поля Check.

Если поле Check не равно NULL, его необходимо отправить на печать (в нефискальном режиме) и после этого удалить вызовом функции GlobaFree().

Звучит несложно, даже хэдер файл предоставлен. Что ж, подключаем его, компилируем и…

Эммм… Что? Открываем pilot_nt.h:

Сразу, не глядя камень за комментарии на русском в кодировке CP1251.

Читайте также:  Сброс счетчиков производительности windows

Ну и самый серьезный камень: дорогие разработчики на С++. Если вы пишете extern «C» — это означает, что код внутри блока должен компилироваться С-компилятором. Если вы НЕ сделали `typedef` структуры, то при каждом ее упоминании в качестве указания типа необходимо писать ключевое слово `struct`.

Патчим файл для разработчиков, подставляя везде, где нужно слово `struct`. Линкуемся с библиотекой `pilot_nt.dll`. Победа, не? Запускаем наше приложение.

ГЛАВА 6. Из огня да в полымя

Ну вы поняли, да? Приложение просто падает. Сразу, до main. Медитируем, добавляем NIH-аналог функции errno для windows: GetLastError (камень #3 в сторону Microsoft, первые два за кодировки).

0xc0000096? А разве GetLastError не должна возвращать адекватный код ошибки?

For a complete list of error codes provided by the operating system, see System Error Codes.

The following topics provide lists of system error codes. These values are defined in the WinError.h header file.

  • System Error Codes (0-499) (0x0-0x1f3)
  • System Error Codes (500-999) (0x1f4-0x3e7)
  • System Error Codes (1000-1299) (0x3e8-0x513)
  • System Error Codes (1300-1699) (0x514-0x6a3)
  • System Error Codes (1700-3999) (0x6a4-0xf9f)
  • System Error Codes (4000-5999) (0xfa0-0x176f)
  • System Error Codes (6000-8199) (0x1770-0x2007)
  • System Error Codes (8200-8999) (0x2008-0x2327)
  • System Error Codes (9000-11999) (0x2328-0x2edf)
  • System Error Codes (12000-15999) (0x2ee0-0x3e7f)

Отлично, мы получили незадокументированную ошибку, кидаем камень и открываем всезнающий google:

  • _inp()
  • _inpw()
  • _inpd()
  • _outp()
  • _outpw()
  • _outpd()

Использование которых запрещено под NT-ядрами, так как они пытаются работать с параллельным портом напрямую. Судя по всему этот код вызывается в инициализаторе библиотеки, т.е. библиотека при старте хочет опросить порты на наличие устройств, но NT-ядро требует работы через драйвер.

ГЛАВА 8. Пауки и мухи

22:00. На всякий случай возникает идея проверить, что это не из-за того, что мы используем кросскомпиляцию с Linux с помощью mingw. Параллельно понимаем, что Сбербанк поставляет только 32хбитное приложение, поэтому слинковаться с 64хбитным приложением не выйдет, ну ладно, но все равно запустим камень в сторону Сбербанка за 32-only версию в 2019м году.

Дано: установленная в virtualbox windows 7;
Необходимо: установить Visual Studio и скопилировать MVP.

Заходим на сайт Microsoft, качаем Visual Studio 2017. Берем лицензию сообщества, так как мы берем ее для проверки, для бизнеса лицензия будет куплена, если взлетит.
Скачиваем несколько сотен мегабайт и…

Видим, что наша версия ОС (Windows 7) не поддерживается.

Ок, идем на всякие непотребные сайты, ищем Visual Studio 2008, скачиваем несколько сотен мегабайт заново и…

Получаем iso файл.

Ладно, попытаемся установить Daemon Tools 10 (так как это та версия, которую предлагает сайт), чтобы вставить этот виртуальный диск.

Запускаем скачанный бинарь. Осечка, требуется .NET Framework 4.5, скачиваем, ставим.
Запускаем скачанный бинарь, установка началась, загрузчик говорит что ему нужна 4.5.2, скачиваем, ставим.
Запускаем скачанный бинарь, установка началась, загрузчик говорит что никуда не поедет, пока мы не поставим обновление безопасности KB3033929, скачиваем, ставим.

И получаем оплеуху от Microsoft в виде сообщения:

Яростно кидаем очень острый камень в сторону Microsoft, качаем с торрентов старый Daemon Tools, успешно распаковываем Visual Studio, устанавливаем, наконец-таки (00:00) компилируем MVP, получаем такую же ошибку. Что ж, хорошая была версия, но не срослось.

ГЛАВА 11. На пороге

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

Подозрительно глядя на репозиторий скачиваем его, компилируем и запускаем. Работает.

Еще более подозрительно смотрим на код. Код идентичен, разве что написан на C++ а не С.
Понимаем, что язык тут не причем. Смотрим библиотеки сбербанка, которые тянет за собой код.
Видим последний коммит.

Читайте также:  Flash player для астра линукс

И вот тут нас поджидает очередной сюрприз.

Оказывается, что версии библиотеки Сбербанка могут быть разными. Последний коммит увеличивает версию с 23 до 27й. Копируем себе на тестовый компьютер версию из гита — РАБОТАЕТ!

Проверяем все архивы, которые присылал Сбербанк, сравниваем версии и строим табличку:

Версия Работает
26.0.15 — Основная нет
27.4.12 — Из репозитория да
23.0.13 — Из репозитория да
29.0.9 — Самая свежая от СБ да
23.0.13 — С патчем для системы «Криптера» да

Отлично, вот теперь заживем. На тех системах где стоит 26 обновим до 29 или 27 и все взлетит.
Кидаем камень #9 в сторону Сбербанка за то, что сломали поведение на NT системах.

ГЛАВА 12. Что ждало их внутри

Не хватает «е» файла? Не беда, берем патченные заголовочники, динамически линкуемся с библиотекой чтобы корректно вернуть ошибку, пишем код, который просто запишет код возврата из функции в файл «е», назовем бинарь sb_pilot.exe и…

Работать-то оно работает.

Вот только на версии для системы «Криптера» не создается «р» файл.

Грустно смотрим на капающую по костяшкам кровь и на вмятину в стене.

Для начала, что такое система «Криптера».

Cryptera — это датская компания, выпускающая шифрующее оборудование/оборудование безопасности/ключи и пр. Думаю, что вы все видели один из экземпляров их продукции:

Так вот Сбербанк использует их криптомодуль для пинпадов и выпускает специальную «патченную» библиотеку, в которой, как мы уже поняли, не создается файл «р». Пишем по этому поводу в Сбербанк и через несколько дней получим ответ, что «под оригинальной системой файл „р“ будет создаваться, а под патченной на Криптеру — нет». Выдадим им камень # 10 через несколько дней, ведь работать нужно уже сейчас.

К счастью, или к несчастью, но функции, которые мы используем для проведения операций возвращают уже упомянутую структуру:

О, отлично, чек уже есть, можем сами сохранить его в файл или сразу вывести в JSON…

И получаем падение приложение из-за обращения по невалидному указателю.

ГЛАВА 14. Огонь и вода

[out] образ чека, должен освобождаться GlobalFree в вызывающей программе

Что это нам дает? Очень многое. Во-первых то, что раз указатель требует очистки с помощью GlobalFree то его саллоцировали с помощью GlobalAlloc. Следовательно она выдает не указатель на память, как было в 16битной версии, а номер объекта с семантически объявленым типом HGLOBAL, который можно скормить в функции GlobalSize чтобы получить размер выделенного блока и GlobalLock чтобы заблокировать кусок памяти, но получить оригинальный указатель. Кстати, камень #6 в сторону Microsoft за NIH malloc и free, которые есть в стандартной библиотеке.

И все равно получаем падение. Окей, а что показывае GlobalSize? Ноль? Как-то странно.

Проверяем другие функции, которые тоже должны отдавать слип — видим ту же картину.

В голову приходит разве что самостоятельно сгенерировать слип по тем данным, которые может выдать самая крутая функция оплаты (да, у Сбербанка функции именуются card_authorize2..14, камень кидать за это не буду):

Пробуем подбирать поля… Выясняем, что от счастья нас отделяло всего одно — Фамилия и Имя носителя карты. Без них слип не считается законным:

Реквизиты: идентификатор банкомата, электронного терминала или другого технического средства, предназначенного для совершения операций с использованием платежных карт; вид операции; дата совершения операции; сумма операции; валюта операции; сумма комиссионного вознаграждения код авторизации; реквизиты платежной карты.

Жаль, но сформировать законный слип с теми данными, что у нас есть не получится.

Покопаемся в документации еще раз.

Находим пример, который Сбербанк поставляет в каталоге «examples»

Просто выводится текст, находящийся по указателю. Но ведь мы уже убедились, что так оно не работает… На всякий случай скомпилируем их пример и запустим. Вылет на строчке `file

Источник

Оцените статью