# SIRMENT/core SIRMENT — сервер системы обмена данными Artex. Узлами могут быть роботы, операторские приложения и сервисы. Сервер управляет соединениями и сессиями и передаёт данные по заданным маршрутам. ## Реализовано - **Transport/TCP** принимает соединения и читает JSON-пакеты, разделённые LF. `TCP.run()` запускает настоящий asyncio-сервер; `src/main.py` собирает Router и SessionManager и запускает TCP на `0.0.0.0:9000`. - **Session** хранит состояние подключения, версию протокола, метаданные и последние данные по командам. Handshake проходит последовательность `connection → token_ask → token_answer → handshake_ask → handshake_answer`. После успешного ответа сессия переходит в `air`. - **Heartbeat**: клиент отправляет `heartbeat_ask` с `payload`, сервер отвечает `heartbeat_answer` и обновляет дедлайн. - **Watcher**: у каждой сессии есть собственная asyncio-задача, проверяющая дедлайн раз в секунду и передающая менеджеру событие `disconnect`. - **Отключение** закрывает транспорт и переводит сессию в `stop`. Менеджер сохраняет запись на время восстановления, затем удаляет её и маршруты. - **Реконект** создаёт новый UID и собственный handshake. В `token_answer` можно передать старые `token` и `upf`. Сессия в `stop` с совпадающим `upf` отдаёт динамические метаданные и последние данные; Router переносит исходящие маршруты и все входящие ссылки на новый UID. Старая сессия получает состояние `migrated`, поэтому перенос нельзя повторить. - **Router** передаёт полезную нагрузку отправителя каждому получателю. `Session.send()` запускает отправку фоновой задачей; маршрутизатор не ждёт завершения сетевой отправки. Текущие сроки: | Условие | Срок | | --- | --- | | От создания сессии до первого heartbeat, включая handshake | 60 секунд | | После каждого heartbeat | 5 секунд | | Сохранение отключённой сессии для восстановления | 60 секунд | Проверка watcher выполняется раз в секунду: фактическое обнаружение таймаута может произойти позже дедлайна на период проверки. Сам event loop продолжает обслуживать остальные соединения во время `asyncio.sleep()`. `in`/`out` описывают типы портов будущего редактора узлов. `active_in` и `active_out` хранят динамическое состояние. Эти поля сами по себе не создают маршруты. Значение `com` и общее правило объединения метаданных при реконекте ещё не зафиксированы. `connection.payload.token` — токен робота, сохраняемый в метаданных `auth`; его будущая проверка по whitelist роутера пока не реализована. UID/upf в `token_answer` служат отдельно для восстановления сессии. По согласованному протоколу отказ handshake означает только буквальный JSON `false`; остальные значения `status` означают успех. Текущий `if not status` ещё использует ложность Python и расходится с этим правилом. Payload данных сохраняется и пересылается как JSON-значение без автокаста в словарь. Ошибка разбора входного пакета может завершить обработчик клиента; TCP-finally закрывает его сессию, остальные соединения продолжают обслуживаться. ## Незавершённое и ограничения - GUI/API редактирования маршрутов; автоматическое создание маршрутов и правила подписок. - Авторизация. Совпадение UID/upf сейчас используется для восстановления и не заменяет полноценную авторизацию. - Полноценное переключение диалектов: используется `v1`; `v2` и `short` содержат только черновые имена полей и не имеют полного набора команд. - WebSocket/Socket.IO (`sio`). - Сохранение сессий, данных и маршрутов после перезапуска процесса. - Отдельный ответ клиенту с результатом восстановления. Heartbeat подтверждает новый handshake, но не доказывает успешный перенос старой сессии. - Настройка адреса и порта через CLI/окружение: сейчас они заданы в `main.py`. Актуальные результаты, ошибки реализации и ограничения проверки приведены в [отчёте тестов](tests/REPORT.md). Изменения исходников сервера выполняет автор проекта; доработка тестов и документации не редактирует `src`. ## Запуск Версия Python указана в `.py-version` — Python 3.11. Из корня репозитория: ```sh python -m pip install -r req.txt python src/main.py ``` Общий клиент находится в отслеживаемом `examples/client.py`: ```sh python examples/client.py rover --count 10 --interval 1 python examples/client.py rover --old-token 123 --old-upf 456 ``` На Windows можно использовать `src/start_server.bat` и `examples/start_client.bat`; они берут Python из `D:\_Programs\anaconda\envs\sites`. ## Тесты ```sh python -m pip install -r req-dev.txt python -m pytest -q --cov=src --cov-branch --cov-report=term-missing --cov-report=xml ``` Тесты используют общий клиент из `examples/`, поэтому не зависят от локальной игнорируемой папки `src/.test`. Интеграционные сценарии работают с настоящим TCP-сервером. Отдельно выполняется настоящий `src/main.py` в дочернем процессе. Подробности и команды запуска — в [tests/README.md](tests/README.md). Новые регрессии остаются обычными падающими тестами до исправления реализации. ## Docker и CI Тесты выполняются для Pull Requests в `main` и при ручном запуске workflow. Docker-образы собираются при push в `deploy` и создании тегов `v*.*.*`. Образ запускает `python src/main.py`. ## Структура ```text src/ TCP, сессии, маршрутизатор и точка входа examples/ Общий клиент и батник ручного запуска tests/ Модульные, TCP-интеграционные и процессные тесты ```