Да, мы написали с нуля новое EMV ядро.
Нет, мы не сошли с ума или нам некуда девать рабочее время. Всё достаточно просто – старое ядро просто перестало удовлетворять нашим потребностям, а если быть более откровенным, то сложилось достаточное большое количество факторов, которые препятствовали работе со старым EMV ядром.
Но давайте разбираться в конкретных причинах и в том, как мы их решали.
Архитектура
Начнём с самой главной части – архитектура ядра.
Первую реализацию ядра можно назвать монолитной, так как это один проект на C++ стандарта C++98. Вы спросите почему C++98, а не более свежие версии? Ответ очень прост – NewPOS 8210, у которого SDK не поддерживает ничего новее C++0x, да и тот слишком кривой.
Возвращаясь к архитектуре ядра стоит упомянуть что в проекте не было реализовано нормального разделения на отдельные ядра платёжных систем (Mastercard, Visa, Mir), что сильно затрудняло исправления ошибок, да и добавление нового тоже давалось с трудом. То есть, по сути, это монолитный кусок легаси кода, который никто не хочет поддерживать, да и не все смогут.
Когда же нам понадобилось использовать ядро на Android терминалах , то пришлось сбоку пристраивать обвязку на Java что было тем ещё «приключением» .
К счастью новая же архитектура лишена данных проблем. Мы за всё время разработки и поддержки поняли, что нам нужно и в какую сторону нужно развивать ядро. Поэтому сразу подумали, что нам обязательно нужна модульная архитектурой, где каждый из компонентов всегда можно довольно легко заменить.
Также мы уже довольно давно думали, а не использовать ли Lua в качестве ещё одного языка программирования, который очень хорош как встраиваемый язык с минимальным оверхедом .
Если представлять архитектуру графически, то она соответствует следующему рисунку.
Схематичное представление архитектуры EMV ядра.
Тут стоит сразу обратить внимание на самый нижний блок «Платёжные ядра», так как он является полностью заменяемым, так как полностью реализуется на языке Lua. Для поддержки данной фичи пришлось встроить в ядро интерпретатор языка Lua.
Встроенный интерпретатор помог заметно сократил этапы реализация-тестирование, так как теперь отсутствует этап пересборки ядра. Необходимо только заменить lua файл платёжного ядра на новый и можно продолжать тестирование.
Конечно и без минусов не обошлось – пришлось платить временем выполнения приложения, так как появились накладные расходы на интерпретацию кода, но это довольно незначительные потери, которые не будут заметны клиенту.
Идём дальше и рассмотрим верхний блок «Клиентское приложение», который должен реализовывать работу с картридером, а также несколько криптографических функций, используемые в ядре. Это сделано для того, чтобы ядро было максимально переносимым между платформами.
Переходим к главному блоку – средний слой, который реализует самые базовые части ядра:
- Entrypoint – основная точка входа для ядра
- Lib – набор вспомогательных библиотеки (log, tlv, emv ...)
- Lua – интерпретатор языка lua
Данный уровень строит мост между клиентским приложением и ядрами написанными на lua. Я бы назвал этот слой ядро ядра, но это звучит как-то слишком тавтологично и поэтому это «База ядра».
Теперь можно перейти и к технологической составляющей проекта.
Технологический стек
Вот мы и перешли к самой гиковской части. Тут мы немного приоткроем занавесу над используемым технологическим стеком, да и расскажем немного про наш пайплайн.
В качестве основного языка у нас всё также C++, но уже используем стандарт C++11 . Вторым языком в проекте, как уже можно было понять, является Lua версии 5.4.4 (последнюю версию на данный момент).
Для сборки проекта используем CMake с ограничением на минимальную версию 3.12. Можно было, конечно, взять meson с ninja , но нам нужно беспроблемная сборка ядра в Android Studio, а там CMake работает без нареканий.
И последняя, но не по значению, библиотека для логирования Plog .
На этом наш программный стек заканчивается и начинается интеграция с Gitlab CI . Сейчас в CI уходят многие рутинные задачи. Для данного проекта были сделаны всего три простых стейджа.
Этапы Gitlab CI/CD.
На первом этапе производим сборку проекта. Тут всё просто – ставим в контейнере нужные инструменты и собираем cmake'ом.
Далее идёт этап тестирования, где автоматом прогоняются все тесты и полученные артефакты отдаются Gitlab CI, чтобы в web интерфейсе можно было посмотреть на результаты.
И последний этап – генерация документации, которая автоматически заливается в вики данного проекта. На этом автоматизация проекта в Gitlab заканчивается.
Ещё хочется немного упомянуть про поддержку сборки ядра под Android. Данная возможность реализована в отдельном репозитории, в который сабмодулем подключен данный проект, а в остальном это обычный Java биндинг над C++ кодом со всеми удобствами
Под самый конец статьи хочется ещё похвалиться ещё одно возможностью, которую очень легко реализовать в проекте, а именно загрузка и горячая смена платёжного ядра. Так как платёжные ядра реализованы на Lua, то их всегда можно легко подгрузить по сети и на ходу перенастроить ядро на работу с ними. Вроде фича и простая, но если подумать, то можно без обновления приложения быстро вносить правки и быстро доставлять эти изменения в клиентские приложения. Круто же!