Ansible — не форк и не сборка чужого сервера. Совместимость по семейству протокола нужна, чтобы работали существующие клиентские библиотеки, но сервер, транспорт, хранилище и вся продуктовая часть написаны заново. Ниже — что именно отличается от того, на что мы похожи снаружи.
Транспортный и криптографический слой живёт отдельным OTP-приложением
(bh-mvsy): TL-кодеки, хендшейк, сессии, шифрование. Сервер бизнес-логики
(bh-backend) подключает его как зависимость и о проводе ничего не знает.
Разделение не косметическое — оно позволяет менять транспорт, не трогая
~330 RPC-методов.
Шифрование — AES-IGE поверх сессионного ключа из хендшейка Диффи-Хеллмана.
Проверено вживую: маркер, вставленный в плейнтекст на уровне L0, на проводе не
находится ни в исходном виде, ни после снятия обфускации. Что всё равно
утекает — метаданные: auth_key_id, длины пакетов, тайминги. Это свойство
любой сети доставки, и мы предпочитаем сказать это прямо.
Клиент может подключиться четырьмя способами, и все четыре сходятся в один обработчик:
Обфускация не даёт приватности — её даёт шифрование. Она снимает признак, по которому соединение можно отсеять как «это мессенджер».
Один дата-центр, три master-узла Kubernetes за общим VIP. Мульти-DC заложен в
роутинге (bh_config_dcs, экспорт/импорт авторизации, маршрутизация
хранилища), но в проде пока один регион.
Дорогой урок: при разнесении PostgreSQL, Redis и MinIO по географии от бэкенда пол любой операции упирался в ~11 мс сетевого RTT — не в код. Никакая оптимизация запроса этого не отыгрывает. Лечится только совместным размещением, что и сделано.
Исходный маршрут /progressive/photo<id>?h=<access_hash> работает, но
поставить перед ним CDN нельзя, и по трём независимым причинам:
content-type сам.Поэтому добавлен /file/<token>.<ext>. Токен — 20 байт:
epoch, тип, file_id и HMAC-SHA256, усечённый до 80 бит. Обычный GET
получает 200 и целое тело; Range по-прежнему работает.
Токен детерминированный, без метки времени — и это не недосмотр. Кеш имеет
смысл только если у файла один URL навсегда; экспирация внутри токена минтила бы
новый адрес на каждый рендер и попаданий было бы ноль. Массовый отзыв — байт
epoch в конфиге.
my.ansible.su выдаёт api_id/api_hash.
Вход — код внутрь мессенджера, не по SMS: портал умеет выпускать credentials
и удалять аккаунт, поэтому владения номером недостаточно — аккаунт должен быть
достижим.
До этого api_id был значением, которое сервер только писал в
кодированные авторизации и никогда не проверял, то есть отрубить абузивный клиент
было нечем. Теперь проверка стоит в единственной точке входа RPC — все три
транспорта сходятся туда, поэтому отказ не требует правок в каждом.
Отдельно: неизвестный api_id разрешён намеренно. В реестре только
приложения, выпущенные порталом, а первые-партии клиенты носят id, появившиеся
раньше него; «неизвестный = отказ» выкинул бы всех из сети в момент выкатки.
Отказывает только явный бан.
Ссылка на группу, канал или юзернейм, открытая в обычном браузере, раньше попадала в SPA сайта, у которого не было такого роута, и URL молча подменялся главной — то есть ссылка теряла то, на что указывала. Теперь на неё отвечает отдельный сервис с серверным рендером: OG-теги в первом ответе, переход в приложение до загрузки фреймворка.
Резолвер — на бэкенде, и он отвечает одинаково на «не существует», «отозвано» и «истекло». Различать их означало бы дать оракул для перебора хешей приглашений.
Кошелёк — отдельный сервис со своей базой; мессенджер владеет коллекционными объектами (подарки, реальные номера и юзернеймы), кошелёк — балансом. Общение S2S. Атомарность покупки на P2P-маркете обеспечивается одним CTE, а не последовательностью запросов: иначе между списанием и передачей есть окно.
End-to-end, ключ выводится на устройствах. Сервер — слепой релей: он передаёт байты, не имея ключа и не участвуя в обмене Диффи-Хеллмана. Из этого следует и ограничение: историю секретного чата нельзя восстановить на новом устройстве, потому что восстанавливать её сервер не из чего.
Схема TL покрыта на 98,2 % от опубликованной. Оставшееся — методы, которым не соответствует ни одна наша подсистема (часть паспортных, часть рекламных). Каждый реализованный метод проверяется парой e2e-тестов «наш клиент / референсный клиент» — совпадение байт в байт, а не «похоже работает».
Раздел, без которого остальное читалось бы как реклама.
WHERE username = $1,
а DNS-имя приводится к нижнему регистру, из-за чего @Support и
@support могут существовать одновременно. Лечится уникальным
индексом по lower(username), нужна миграция.