Skip to content

Releases: EuZireael/SCADA_rust

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 19 Oct 09:48
9a6347d

Настройки тегов из channel.tag-settings, быстрый возврат связи по OPC UA, защита от лавины полной переотправки в Kafka,
опрос контроллера сразу после записи, сравнение старой SCADA и шлюза на одном эмуляторе. Формат сообщений Kafka, ключи и
топики телеметрии и команд не менялись; новый входной топик channel.tag-settings необязателен (нет топика — работа по YAML).

Что может задеть при обновлении

  • GATEWAY_SUPERVISE_INTERVAL_MS по умолчанию 200 вместо 10000. Пауза между попытками подключения к контроллеру: первые 300
    неудачных попыток (≈ минута) — 0,2 с, дальше не реже раза в 2 с; после отказа сервера (пароль, защита, лимит сессий) — не короче 2 с.
    Если в окружении значение задано явно, оно сохраняется (и никогда не сокращается). Чтобы вернуть прежнее поведение —
    GATEWAY_SUPERVISE_INTERVAL_MS=10000.

Добавлено

  • Настройки тегов из топика channel.tag-settings (контракт бэкенда от 19.10.2026). channel публикует в сжатый топик период опроса и зону
    нечувствительности канала (ключ — имя тега, null — настройки сняты); шлюз читает топик целиком с начала (без группы, каждому экземпляру нужна полная
    копия) и применяет записи на лету, без перезапуска. Период: тег с poll.mode = "period" читается не чаще periodMs: у OPC UA читаются только
    «созревшие» узлы, у PAC и Modbus снимок/блоки читаются целиком, а в обработку и Kafka идут созревшие теги; цикл контроллера по-прежнему определяют
    pollingRate из YAML, чаще цикла тег не читается. Зона: deadband подменяет history.deadband тега в локальной истории; фильтр публикации в Kafka
    не меняется. Тег без записи и tombstone — значения из YAML. Нет топика — шлюз пишет предупреждение, повторяет раз в 10 с и работает по YAML.
    Настройки: CHANNEL_TAG_SETTINGS_TOPIC (channel.tag-settings), GATEWAY_TAG_SETTINGS_ENABLED (true); метрика scada_tag_settings_tags.
    Тесты: разбор записи, расписание (каждый N-й цикл, дрожание тикера, пропущенное не догоняется), зона истории, сквозной tests/tag_settings.rs.

Исправлено

  • Возврат связи по OPC UA после обрыва контроллера занимал 5–10 с и больше. Две причины. (1) Закрытие уже оборвавшейся сессии
    ждало ответа от мёртвого соединения до конца таймаута операции (5 с по умолчанию), и только потом шлюз пробовал подключиться заново;
    теперь, если цикл событий сессии завершён, закрывать нечего. (2) Между неудачными попытками подключения стояла фиксированная пауза
    GATEWAY_SUPERVISE_INTERVAL_MS (10 с), а после обрыва ещё 1 с: контроллер, вернувшийся через пару секунд, ждал шлюз до 10 с.
    Теперь по умолчанию пауза 0,2 с (первые 300 неудачных попыток, ≈ минута; дальше не реже раза в 2 с, чтобы журнал не рос при
    многодневной недоступности контроллера). Быстро повторяются только попытки после потери связи (порт закрыт, обрыв, таймаут); если
    сервер ответил отказом (неверный пароль, защита, лимит сессий), пауза не короче 2 с: иначе каждая попытка оставляла бы на сервере
    незавершённую сессию (на симуляторе тест неверного пароля за секунды исчерпал лимит сессий и уронил следующие тесты).
    Изменение поведения: значение по умолчанию GATEWAY_SUPERVISE_INTERVAL_MS 10000 → 200.
    На симуляторе возврат после обрыва в 5 с / 30 с: было 5,4 с и до 10 с, стало ≈ 0,15 с (по PAC — 0,65 с: ждёт цикла опроса). Цена на недоступный контроллер:
    0,017 ядра и 1,7 КБ/с журнала, нагрузка на Kafka не меняется. Тесты poller::tests::reconnect_is_fast_at_first_and_slows_down_for_a_long_outage,
    a_longer_configured_pause_is_never_shortened.
  • PLC-симулятор не принимался драйвером старой SCADA (EasyDriver). Ответы PAC шли без завершающего нуля (драйвер исполняет их как
    C-строки и читал хвост прошлого ответа), на GET_DEVICES симулятор отвечал другим форматом, чем прошивка (снимок t={…}), а
    рецептурный менеджер был набором приборов RECMAN[n], а не массивом t.RECMAN[n]. Теперь реальный драйвер находит все 2 741
    тегов станции «Танки нормализации» (раньше 222–253 не находил). Симулятор также понимает формат записи драйвера EasyDriver
    (__OBJECT2:set_cmd( "S_PAR_F", 1, 10 ) — двойные кавычки, пробелы, __RECMAN[n]) и раз в 5 с пишет в журнал число запросов состояния. Нужно для
    замеров «старая SCADA против шлюза» (docs/OLD_VS_NEW.md).
  • Полная переотправка всех тегов при «мигающем» брокере Kafka. Каждое восстановление доставки после сбоя запускало отправку всех тегов всех
    контроллеров заново (при 54 800 тегах — залп из 54 800 сообщений), и при частых сбоях и восстановлениях шлюз почти не публиковал «по
    исключению». Теперь флаг сбоя снимается как раньше, но новая полная отправка идёт не чаще раза в 30 с; недоставленное в паузе уходит
    с обычной плановой полной отправкой (GATEWAY_PUBLISH_FULL_RESEND_MS). Тест kafka::tests::flapping_broker_causes_one_resync_per_interval.
    (Основная причина шторма — переполнение очереди продюсера принималось за сбой доставки — исправлена раньше, см. ниже.)
  • OPC UA-фасад ptusa-opcua зависал целиком после нескольких записей. Обработчик записи (синхронный, под блокировкой адресного
    пространства библиотеки OPC UA) ждал ответа прошивки через драйвер ввода-вывода основного рантайма, а этот драйвер в тот же момент
    держал рабочий поток, остановленный параллельным запросом чтения на той же блокировке. Ответ прошивки некому было принять, сервер
    переставал отвечать на всё, в том числе на новые подключения (у шлюза — таймауты записи и чтения, «handshake завис», потеря связи).
    Чаще всего проявлялось при быстром опросе фасада (период 100 мс и меньше). Теперь команды идут по отдельному соединению в отдельном
    рантайме со своим драйвером; после обрыва соединение открывается заново, команда повторяется один раз. Тест
    writes_during_fast_polling_do_not_freeze_the_server (300 записей при опросе 5 мс и параллельном чтении) на прежнем коде виснет.
  • Недоступная Kafka останавливала опрос ПЛК: ложная «потеря связи» и кадры BAD. Приём команд пересоздаёт консьюмера в цикле, пока
    брокера нет; создание (fetch_metadata, чтение позиций — до 10 с каждое) шло синхронно прямо в асинхронной задаче и держало
    рабочий поток рантайма, а вместе с ним задачи, лежащие в его очереди, в том числе ответ на чтение из ПЛК. Чтение упиралось в таймаут
    5 с, шлюз писал «Потеряна связь» и слал монитору BAD, хотя ПЛК был жив. Нашёл дымовой тест стенда (сбой Kafka на 45 с): шлюз
    теперь создаёт консьюмера в отдельном блокирующем потоке (spawn_blocking), опрос от Kafka не зависит.
  • Переполнение очереди продюсера Kafka запускало лавину повторной отправки. Сообщение, которое очередь librdkafka
    (по умолчанию 100 000) не приняла, считалось сбоем доставки; следующая удачная отправка запускала повторную отправку
    всех тегов, она снова переполняла очередь, и так по кругу. На стенде в 750 тысяч тегов шлюз публиковал каждое
    снятое значение (785 тысяч сообщений в секунду вместо ожидаемых 250 тысяч) и накопил 14,8 млн ошибок за минуту.
    Теперь отказ очереди — не сбой доставки: тег забывает, что он опубликован, и уходит следующим же циклом опроса,
    остальные теги это не затрагивает. Новая метрика scada_kafka_queue_full_total; предупреждение в журнале — не чаще
    раза в 30 с. На реальной станции (2–3 тысячи тегов) очередь не переполняется, поведение не меняется.

Изменено

  • Симулятор по умолчанию отдаёт ВСЕ каналы базы каналов станции только по OPC UA. Новая конфигурация
    simulator/config/station_opcua.yaml — 2647 каналов config/stations/BN1_MCA1.yaml (включено в базе 1834) с теми же
    адресами узлов ns=2;s=<прибор>.<поле>, что у OPC UA-фасада эмулятора мойки: шлюзу нужен один и тот же файл станции.
    Прежний симулятор описывал 2517 тегов, из них 489 не относились к базе каналов, а 619 каналов базы в нём
    отсутствовали. Modbus и PAC симулятор умеет по-прежнему, но поднимает их, только если в конфигурации есть их теги
    (config/replay_config.yaml — три протокола, для интеграционных тестов); healthcheck проверяет порт OPC UA и порты
    настроенных протоколов. Подкоманда simulator station-config пересобирает конфигурацию из базы каналов; тест следит, что
    файл в репозитории не устарел.
  • docker-compose.yml и scripts/stack_smoke.sh: стенд — симулятор станции по OPC UA и шлюз с
    config/stations/BN1_MCA1.yaml (PLC_HOST вместо SIM_HOST); порты 5020 и 10000 на хост не пробрасываются.

Добавлено

  • Два готовых режима чтения одной станции: «EasyDriver» (PAC, driver-master) и OPC UA. config/stations/BN1_MCA1_easydriver.yaml
    (получается из станции для OPC UA скриптом tools/station_to_easydriver.sh; тест следит, что каналы, имена, типы и доступ не
    разошлись), simulator/config/station_pac.yaml (simulator station-config pac: все 2647 каналов только по PAC, серверы OPC UA и
    Modbus не поднимаются), scripts/stand.sh opcua|easydriver, up-moika.sh opcua|easydriver (профиль opcua — фасад). У PAC-режима
    каналы с пробелами в индексе (ST_CH[ 1 ]) читаются как ST_CH[1].
  • Проверка эффекта записи теперь и для PAC (GATEWAY_COMMANDS_VERIFY_MS): код 0 PAC означает только «команду приняли», а прошивка
    принимает почти любую запись. Шлюз читает значение до записи, отпускает соединение (опрос не ждёт), через заданное время запрашивает
    свежий снимок и при неизменном значении отвечает FAILED_NOT_APPLIED. Проверено на настоящей прошивке и тестом
    write_effect::pac_write_to_a_program_owned_valve_is_reported_as_not_applied_until_manual_mode.
  • scripts/compare_protocols.py, loadsim --protocol pac, PROTOCOL=pac scripts/stress_test.sh, scripts/pac_probe_noauth.py и
    docs/PROTOCOL_COMPARISON.md: сравнение EasyDriver и OPC UA на настоящей прошивке и симуляторе (охват, нагрузка, нагрузка на ПЛК при
    нескольких шлюзах, запись, обрыв связи, масштаб до миллиона значений в секунду, безопасность), выводы и список того, чего нет в PAC-режиме по
    сравнению с драйвером savushkin-r-d/driver.
  • scripts/stack_smoke.sh с MODE=easydriver и вторая стр...
Read more

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 07:47
298f6b2

Аудит безопасности и надёжности всего модуля, исправление записи PAC в поля-массивы, полная документация. Формат
сообщений Kafka, ключи и топики не менялись.

Что может задеть при обновлении

  • Конфигурация проверяется строже. Станция, в которой тег молча не работал бы, теперь останавливает запуск с понятным
    текстом (список ниже, раздел «Исправлено»). Конфигурации из репозитория проходят проверку; свою прогоните на тестовом
    запуске до выкладки.
  • Порты БД и Kafka в compose-стендах привязаны к 127.0.0.1: с других хостов они недоступны, пока привязку не заменят.
  • Подключение к БД по умолчанию — sslmode=prefer: TLS, если сервер его предлагает (раньше TLS не было вовсе). Чтобы
    требовать шифрование — sslmode=require или строже.

Безопасность

  • TLS к PostgreSQL. Раньше драйвер БД был собран без TLS, а sslmode в адресе молча отбрасывался: трафик шёл
    открытым текстом, даже если оператор написал sslmode=require. Теперь поддерживаются sslmode, sslrootcert,
    ssl=true|false и DB_SSLMODE/DB_SSLROOTCERT; неверное значение останавливает запуск. Проверено на настоящем
    PostgreSQL с УЦ (tests/db_tls.rs, scripts/pg_tls.sh, шаг в CI).
  • Команда со значением "NaN"/"inf" больше не доходит до ПЛК (REJECTED_TYPE_MISMATCH); значение для set_cmd PAC —
    только конечное число или bool, строки и NaN/inf отклоняются на месте подстановки в Lua-текст.
  • Адреса PAC (deviceName, fieldName) проверяются при запуске: кавычки, пробелы и управляющие символы запрещены.
  • Пароль БД и токен REST — тип Secret: в Debug и журнал не попадают.
  • Ошибки БД в REST не раскрывают адрес и SQL (подробности — в журнале шлюза); токен короче 16 символов — предупреждение.
  • Образ и CI: права задач по умолчанию — только чтение; cargo audit проверяет и симулятор, и ptusa-opcua; обоснование
    исключения RUSTSEC-2023-0071 (rsa) исправлено: оно ссылалось на «политику None», хотя защищённый канал поддерживается
    с v0.3.0. Порты БД и Kafka в compose-стендах привязаны к 127.0.0.1.
  • Чек-лист безопасности перед пуском на объекте — docs/OPERATIONS.md.

Исправлено

  • Запись PAC в поле-массив. Шлюз слал set_cmd('RT_PAR_F[62]', 1, v): настоящий ptusa принимает такую команду с кодом 0 и
    ничего не меняет, то есть запись выглядела выполненной (APPLIED). Теперь индекс идёт отдельным аргументом:
    set_cmd('RT_PAR_F', 62, v) (как у фасада ptusa-opcua).
  • Чтение RECMAN[n]. Менеджер рецептов лежит в снимке PAC массивом t.RECMAN[n]; тег с deviceName: "RECMAN[1]" читался
    как t["RECMAN[1]"] и всегда был BAD.
  • POST /api/events/{id}/acknowledge отвечал «ACKNOWLEDGED» и для несуществующего аларма — теперь 404; пустой или
    слишком длинный userId — 400.
  • Конфигурация, при которой тег молча не работал, останавливает запуск с понятным текстом: протокол тега не совпадает с
    контроллером (тег никто не опрашивал), два тега с одним nodeId (общий id в БД и общая история), Modbus-адрес вне
    40001…105536 (обрезался бы до чужого регистра), PAC-тег без deviceName/fieldName, разные modbusUnitId у тегов
    одного контроллера.
  • Тип контроллера определяется по схеме адреса, а не по вхождению: pac://modbus-gw:502 больше не принимается за Modbus.
  • Неверный пароль БД и непроверяемый сертификат показываются сразу, а не после 60 с повторов.
  • Зависший Lua-скрипт на большом числе каналов останавливал обработку контроллера (по timeout на канал за цикл): после
    3 превышений времени подряд скрипт отключается на 30 с, его каналы идут как BAD, остальные работают.
  • Мусор в топике команд усекается в журнале (300 символов) и экранируется.

Добавлено

  • Запись в менеджер рецептов RECMAN[n] в формате, который разбирает прошивка: __RECMAN[1]:set_cmd( "CMD", 1, 1001 )
    (двойные кавычки, пробелы, строка в кавычках, число без знака). Значение, которое прошивка молча проигнорировала бы
    (отрицательное число, кавычка в строке), отклоняется как REJECTED_TYPE_MISMATCH. Формат взят из описания прошивки
    (регулярное выражение ptusa_main), на настоящем контроллере с рецептами пока не проверен.
  • SIM_PAC_PORT: порт PAC в controllers.yaml и на хосте в docker-compose.yml настраивается, если 10000 занят настоящим
    ptusa_main.

Документация

  • README переписан: быстрый старт, схемы (mermaid), возможности, контракт с монитором, таблица документов.
  • Новые документы: docs/ARCHITECTURE.md (потоки данных, жизненный цикл, надзор, резервирование, песочницы Lua, принципы),
    docs/CONFIGURATION.md (все переменные окружения, controllers.yaml, scripts.yaml, проверки запуска),
    docs/DEVELOPMENT.md (сборка, тесты, CI, соглашения, как расширять, релиз), docs/TROUBLESHOOTING.md
    (симптом → причина → что делать), simulator/README.md.
  • Комментарии: каждый публичный элемент шлюза описан (#![warn(missing_docs)] проверяется в CI), описаны приватные
    функции, типы и поля во всех пакетах, у интеграционных тестов — сценарий, в неочевидных местах — пояснения «почему».
  • Примеры controllers.yaml в документации проверяются тестом, чтобы не устаревать.

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.4.0

v0.3.2

Choose a tag to compare

@github-actions github-actions released this 08 Oct 11:37
6493487

Внутренние изменения; формат Kafka, переменные окружения и поведение шлюза не менялись.

Изменено

  • Симулятор и фасад ptusa-opcua: длинные функции main и build разделены на шаги (поведение то же). В CI для всех крейтов добавлен cargo doc -D warnings, исправлены битые ссылки в документации.

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.2

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 08 Oct 10:35

Изменено

  • Тело телеметрии формируется без промежуточных выделений (в 2 раза быстрее: 198 → 94 нс на сообщение), байты на проводе те же (тест на совпадение с serde). Код разделён на модули: startup, config/, script/; поведение не менялось.

Добавлено

  • GATEWAY_COMMANDS_VERIFY_MS (по умолчанию 0 — выключено): проверка эффекта записи OPC UA. Если ПЛК принял
    команду, но значение осталось прежним (программа не разрешает действие, например клапан не в ручном режиме),
    результат — FAILED_NOT_APPLIED, success=false. Тест tests/write_effect.rs.
  • docs/FIRMWARE_UNMAPPED_FIELDS.md: 138 полей настоящей прошивки без канала в базе станции (для разработчика монитора).

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.1

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 13:42
5c830f3

Шлюз под сбои и под масштаб, безопасность, симулятор и фасад на Rust (Python в репозитории больше нет), условная
логика прошивки в симуляторе. Формат сообщений Kafka, ключи и топики не менялись; новое для монитора —
docs/MONITOR_INTEGRATION.md («Что нового для монитора в шлюзе v0.3»), эксплуатация — docs/OPERATIONS.md.

Исправлено

  • Недоставленная телеметрия не теряется молча. После обрыва брокера значения, которые фильтр
    «по исключению» уже считал отправленными, не доходили до монитора до полной отправки. Теперь, когда
    доставка восстановилась, все теги отправляются заново (scada_kafka_resyncs_total); предупреждения о
    недоставленных пишутся раз в 30 с (раньше — по строке на сообщение, тысячи при обрыве).
  • Полная отправка без «залпа». Первая полная отправка каждого тега смещена на свою долю периода
    (filter::phase): на 54 800 тегах вместо одной секунды с десятками тысяч сообщений раз в 30 с —
    ровный поток.
  • Падение задачи не остаётся незамеченным. Опрос контроллеров, приём команд и фоновые задачи
    перезапускаются надзирателем (scada_task_restarts_total, событие SYSTEM/ERROR); отравленный
    мьютекс больше не каскадирует паники.
  • События в Kafka не зависят от БД. Запись в БД вынесена в отдельную задачу: при недоступной БД
    смена роли пары и обрывы связи уходят в Kafka сразу, а не после таймаута пула. Потерянные из-за БД
    строки считаются (scada_db_write_errors_total, scada_db_rows_lost_total).
  • /actuator/health показывает компонент kafka.
  • OPC UA-клиент шлюза пробует все адреса имени. Клиент async-opcua берёт первый адрес из DNS и
    не пробует остальные: localhost → ::1 первым, а сервер на 0.0.0.0 слушает только IPv4 —
    шлюз на хосте не подключался (Could not connect to host [::1]:4840), хотя 127.0.0.1 отвечал.
    Теперь имя с несколькими адресами проверяется TCP-подключением, и если первый недоступен,
    используется тот, что ответил (в журнале — строка «недоступен — подключаюсь к …»).

Проверка конфигурации

  • Конфигурация контроллеров проверяется целиком при старте: повторяющееся имя тега (ключ Kafka и адрес команды),
    повторяющееся имя контроллера, пустое имя тега и неизвестный протокол в endpoint останавливают запуск с
    перечнем проблем (раньше контроллер с неизвестным протоколом молча не опрашивался, а два тега с одним именем
    делали телеметрию и команды неоднозначными). Выключенные контроллеры и теги в расчёт не идут.

Безопасность OPC UA

  • Защищённое подключение к ПЛК. security: (Basic256Sha256, Aes128_Sha256_RsaOaep, Aes256_Sha256_RsaPss,
    Basic256, Basic128Rsa15; режим _Sign / _SignAndEncrypt) и username:/password: в controllers.yaml
    теперь работают — раньше разбирались и молча игнорировались (канал оставался открытым и анонимным). Сертификат сервера
    проверяется по trusted/ (GATEWAY_OPCUA_PKI_DIR, том /app/pki в образе);
    GATEWAY_OPCUA_TRUST_SERVER_CERTS — только для стенда. Ошибка в security, логин без пароля и защита у
    Modbus/PAC останавливают запуск с понятным сообщением; пароль не попадает в журнал и Debug.
  • Симулятор: SIM_OPCUA_USER / SIM_OPCUA_PASSWORD включают защищённые конечные точки (Basic256Sha256) — на них
    проверяются политика, режимы, неверный пароль, анонимный вход, цепочка доверия к сертификату сервера.

Эксплуатация

  • docs/OPERATIONS.md — настройки, здоровье, метрики и оповещения, что шлюз делает при сбоях.
  • scripts/stack_smoke.sh — дымовой тест стенда целиком с отказами Kafka, БД и ПЛК.
  • scripts/load_check.sh (job load в CI) — нагрузочная проверка на 27 400 тегах: опрос не отстаёт, нет ошибок доставки и перезапусков задач, память не растёт.

Симулятор повторяет условную логику прошивки

  • Снято с прошивки мойки (все 1816 записываемых каналов станции) и воспроизведено в симуляторе: часть полей прошивка
    принимает (код 0), но не меняет (обратная связь, датчики, регистры команд, состояния техобъектов — 107), часть
    действует только в ручном режиме (LINE#V0 — главные клапаны линий, виртуальные выходы, сторожевые таймеры — 18; при
    возврате в автоматику значение возвращается к программе), часть хранится целой (16). В replay_config.yaml — ключи
    write: ignore, write_requires: {field: M, equals: 1}, write_int, write_state. Протокол PAC отвечает как
    прошивка: неизвестное поле известного прибора — код 0, не число — код 1.
  • simulator conformance [host] [port] [config] проверяет соответствие по driver-master у любого PAC (CI — на симуляторе;
    на прошивке мойки: 81 тег соответствует, 45 пропущено, расхождений 0). Описание — docs/FIRMWARE_WRITE_BEHAVIOR.md.

Изменено

  • PLC-симулятор переписан с Python на Rust (simulator/): OPC UA, Modbus TCP и PAC
    driver-master в одном процессе, тот же replay_config.yaml, тот же реплей архива BN1_MCA1
    (формат архива — data/archive_replay.bin.gz вместо pickle). Образ — бинарник без интерпретатора,
    узлы OPC UA получают только изменившиеся значения, запись оператора применяется сразу.
    Структура снимка PAC и раскладка регистров Modbus совпадают с прежним симулятором
    (сверено на 63 приборах PAC и 500 регистрах). Новое: поле-массив PAC пишется командой
    set_cmd('RT_PAR_F', 12, v) как у настоящего ptusa; simulator probe-pac вместо pac_probe.py.
    Убраны: синтетические генераторы сигнала (в конфигурации только replay), режим «сырых записей
    приборов» (sim_records.py, нужен был Java-шлюзу), tools/*.py (build_archive.py
    строил архив из SQL-дампа, которого в репозитории нет).

  • OPC UA-фасад ptusa-opcua/ переписан с Python на Rust (async-opcua + mlua): те же узлы
    ns=2;s=<прибор>.<поле>, те же настройки окружения и порт, запись — set_cmd с исходом прошивки
    в статусе записи. Проверен на настоящей прошивке ptusa: 1834 канала GOOD, команда APPLIED за
    27 мс, переключение резерва шлюза. Песочница Lua — как в шлюзе (без os/io/load/байткода,
    память 64 МБ, 1 с на снимок), потолок распаковки zlib 8 МБ; вместе с этим закрыт эффект алерта
    GHSA-69v7-xpr6-6gjm (lupa в репозитории больше нет). Python в репозитории не осталось.
    Ограничение: конечные точки GetEndpoints строятся из OPCUA_BIND (async-opcua не разделяет
    адрес привязки и анонса); сторонним клиентам OPC UA, которые не пробуют второй адрес localhost, вне Docker нужен OPCUA_BIND=[::]:4840 (шлюз с этим справляется сам, см. «Исправлено»).

Добавлено

  • Безопасность Kafka: TLS и SASL (PLAIN, SCRAM) — KAFKA_SECURITY_PROTOCOL,
    KAFKA_SASL_*, KAFKA_SSL_*, любое свойство librdkafka через KAFKA_CLIENT_*, Spring-имена
    SPRING_KAFKA_PROPERTIES_* (включая JAAS); OpenSSL вшит в бинарник. Секреты не попадают в журнал.
    Проверено на брокере с SASL_PLAINTEXT: неверный пароль — отказ в аутентификации, верный — данные идут.
  • Безопасность REST: GATEWAY_API_TOKEN (или GATEWAY_API_TOKEN_FILE) закрывает /api/*
    токеном Bearer; /actuator/health и /actuator/prometheus открыты. GATEWAY_HTTP_BIND.
  • Резервирование по здоровью: активный экземпляр без связи со всеми контроллерами
    (GATEWAY_HA_YIELD_AFTER_MS, 30 с) передаёт лидерство партнёру, который в группе, и не отбирает
    его обратно; без партнёра остаётся активным.

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.3.0

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 02 Oct 14:48
29383e5

Перенос наработок Java-шлюза: телеметрия «по исключению» (контракт монитора), горячее
резервирование, пользовательские Lua-скрипты и станция мойки целиком по OPC UA.

Добавлено

  • Телеметрия «по исключению» (GATEWAY_PUBLISH_*): тег уходит в Kafka при первом значении,
    смене качества, изменении за зону нечувствительности и раз в full-resend-ms (полная
    отправка). Фильтр локальной истории (GATEWAY_HISTORY_*, блок history: тега, колонки
    tags.history_*). Контракт — docs/TELEMETRY_BY_EXCEPTION.md. На стенде 54 800 тегов
    (20 × 2740, 18 280 изменений/с): ≈25 % одного ядра, 125 МБ, без потерь.
  • Горячее резервирование (GATEWAY_HA_*): выборы через группу потребителей Kafka, резерв
    опрашивает, но молчит; переключение при отказе ≈1,9 с (штатная остановка ≈0,6 с), новый
    активный сразу шлёт все теги; GET /api/ha, метрика scada_ha_active, события HotStandby.
    Команды: ручное назначение партиций, коммит позиции до исполнения, возраст команды
    (GATEWAY_COMMANDS_MAX_AGE_MS, REJECTED_EXPIRED).
  • Пользовательские скрипты Lua 5.1 (config/scripts/, GATEWAY_SCRIPTS_*): process для
    входящих значений, write для команд, состояние на канал, цепочки, горячая перезагрузка;
    песочница с лимитами памяти и времени; GET /api/scripts.
  • Станция мойки по OPC UA: config/stations/BN1_MCA1.yaml (1834 канала), фасад
    ptusa-opcua/ (прошивка ptusa → OPC UA, запись через set_cmd), docker-compose.moika.yml,
    up-moika.sh, tools/station-config/, docs/MONITOR_INTEGRATION.md.
  • Нагрузочный стенд loadtest/loadsim; метрики scada_telemetry_suppressed_total,
    scada_process_seconds, scada_poll_seconds, scada_poll_overruns_total,
    scada_script_errors_total, scada_scripts_bound_tags.
  • CONTROLLERS_CONFIG (форма file:/путь), KAFKA_TOPICS_REPLICATION.

Изменено

  • Все узлы OPC UA вернули BAD ⇒ связь с контроллером считается потерянной.
  • ${NAME} в controllers.yaml без значения и без умолчания — ошибка старта (${NAME:} — пусто).
  • Миграция 0002: колонки history_* в tags (совместима с Flyway V2 Java-шлюза).

Безопасность

  • Песочница Lua: убраны coroutine (в сопрограмме не работает хук времени), getfenv/setfenv,
    newproxy, collectgarbage; лимит распаковки ответа PAC — 8 МБ (zlib-бомба).

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.2.0

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 13:29

Первый релиз: шлюз АСУ ТП на Rust — замена Java-шлюза savushkin-dev/scada-gateway с теми же
внешними контрактами (controllers.yaml, топики и формат Kafka, статусы команд, env,
/actuator/health, метрики, схема БД), поэтому встаёт на его место без правок у монитора.

Добавлено

  • Опрос контроллеров, по задаче на контроллер с периодом по pollingRate:
    • OPC UA — подключение прямо к адресу из конфига (без discovery), чтение пачками по 500 узлов,
      пересоздание сессии при зависании;
    • Modbus TCP — FC03 блоками до 120 регистров; при исключении IllegalDataAddress блок делится
      пополам, пока не прочитается (план подстраивается под карту регистров ПЛК);
    • PAC driver-master в формате ptusa (баннер PAC accept, zlib, снимок t исполняется
      в Lua 5.1), запись через set_cmd с кодом результата.
  • Kafka: телеметрия scada.tags (ключ — путь канала, тело {value, quality, timestamp},
    timestamp — epoch-секунды с наносекундами), события, алармы, команды записи и результаты со
    статусами Java-шлюза; идемпотентный продюсер, который не блокирует опрос; команды читаются
    с конца топика, повторы отбрасываются.
  • PostgreSQL: схема, совместимая с Flyway-схемой Java-шлюза, и индексы для выборок;
    синхронизация тегов с controllers.yaml; журнал событий и (по флагу) история телеметрии
    пачками через очереди — база не на горячем пути.
  • HTTP: /actuator/health, /actuator/prometheus, REST журнала /api/* (всегда с LIMIT).
  • Кадры BAD при обрыве, события связи (одно сводное событие смены качества на цикл),
    heartbeat, остановка по SIGTERM, подкоманда healthcheck для контейнера.
  • Стенд в репозитории: PLC-симулятор (simulator/: OPC UA, Modbus, PAC, реплей архива
    BN1_MCA1), конфигурация станции (2517 каналов на трёх контроллерах), docker-compose.yml,
    эмулятор настоящего PAC (scripts/ptusa_emulator.sh).
  • Тесты: юнит (протоколы на фейковых серверах PAC и Modbus), сверка конфигов шлюза и
    симулятора, интеграционные против симулятора (каждый тег своим протоколом, запись),
    сквозной через Kafka и БД, обрыв и восстановление связи через TCP-прокси.
  • CI/CD: fmt, clippy, тесты, pytest симулятора, интеграционные (Kafka и Postgres —
    service-контейнеры), образ в GHCR, GitHub Release по тегу; cargo audit на каждый push и
    раз в неделю; Dependabot.

Безопасность

  • Lua-скрипт в ответе PAC исполняется в песочнице: без io/os/package и файлов, без загрузки
    байткода (load, loadstring, string.dump) и pcall; не больше 64 МБ памяти и 1 с на
    скрипт.
  • RUSTSEC-2023-0071 (rsa из async-opcua, исправленной версии нет) — в исключениях
    cargo audit: при политике OPC UA None RSA-расшифровки у клиента нет.

Отличия от Java-шлюза

  • Учитывается pollingRate (Java опрашивала раз в секунду при 2000 мс).
  • История telemetry по умолчанию выключена (было ~15 ГБ/сут), кадры BAD — включены.
  • Обрыв OPC UA обнаруживается за ~1 с (было 30 с); память 14–27 МБ (было 823 МБ),
    образ 137 МБ (было 646 МБ), старт до связи с контроллерами < 1 с (было 17,5 с).
  • Не перенесено: режим recordDevice/fields, JVM-метрики /actuator/metrics/*,
    политики безопасности OPC UA кроме None.

Образ: docker pull ghcr.io/euzireael/scada_rust:v0.1.0