// Сессия Ayla LAN (сторона «приложения»). docs/PROTOCOL.md §4-6, §4.4. // Потоки: httpd (входящие от модуля: key exchange/commands/datapoint) и // session (исходящие local_reg, таймеры, backoff). Крипто-цепочки и выдача // команд — только в httpd-потоке; session-поток читает очередь под mutex. #pragma once #include #include #include #include "ayla/crypto.hpp" #include "ayla/httpd.hpp" namespace fgl::ayla { enum class SessionState : uint8_t { kIdle = 0, // создан, не запущен kRegistering, // local_reg отправлен, ждём key exchange kOnline, // сессия активна kRecovering, // ожидание самолечения (re-key по keep-alive / активация) kOffline, // модуль недоступен (backoff) или нет слотов kKeyError, // lanip_key_id не совпал — требуется смена конфига }; enum class SessionError : int { kNone = 0, kNoSlot = 1, // 503: оба слота модуля заняты kUnreachable = 2, // transport/backoff kKeyMismatch = 3, // key_id != lanip_key_id (state = kKeyError) kBadKeyExchange = 4, // ver/proto/sec не поддержаны kActivationTimeout = 5,// KE прошёл, опроса commands.json нет (>5 c) kDecryptFailed = 6, // подпись/расшифровка push не сошлись (ждём re-key) }; // Событие обновления свойства (push модуля). Значение — какой-то один тип. struct PropertyEvent { char name[40]; bool is_int = false; int64_t int_value = 0; bool is_bool = false; bool bool_value = false; char str_value[64]; // используется, если !is_int && !is_bool int cmd_id = -1; // из ?cmd_id=N (ответ на GET), иначе -1 int status = 0; // из ?status=200 int64_t seq_no = 0; }; struct SessionConfig { const char* host = nullptr; // DNS-имя или IP модуля uint16_t device_port = 80; // порт local_reg модуля const char* dsn = nullptr; // "AC000W00XXXXXXX" const char* lanip_key = nullptr; // base64-строка как есть uint32_t lanip_key_id = 0; uint16_t listen_port = 10275; // 0 — любой свободный uint32_t keepalive_ms = 15000; uint8_t max_queue = 16; // лимит очереди команд }; struct SessionCallbacks { // КОНТРАКТ: колбэки приходят из потоков ядра (httpd и/или session), // возможно перекрытие во времени; обязаны быть быстрыми и реентерабельными. // Вызывать stop() из колбэка запрещено (deadlock на join). void (*on_state)(void* ctx, SessionState st, SessionError err); void (*on_property)(void* ctx, const PropertyEvent& ev); void* ctx = nullptr; }; // Тайминги поведения (PROTOCOL §4.3-4.4; проверено на приборе). struct SessionTimings { uint32_t keepalive_ms = 15000; // период local_reg uint32_t activation_timeout_ms = 5000; // нет poll после KE uint32_t recovering_quiet_ms = 50000; // пауза при десинке/«KE без poll» // (> порога возврата модуля ~44-50с) uint32_t no_slot_retry_ms = 60000; // повтор после 503 uint32_t backoff_base_ms = 1000; // transport backoff, шаг x1.6 uint32_t backoff_max_ms = 60000; uint8_t backoff_attempts = 6; uint32_t delete_wait_ms = 2000; }; class Session { public: static Session* create(const SessionConfig& cfg, const SessionCallbacks& cbs); ~Session(); Session(const Session&) = delete; Session& operator=(const Session&) = delete; bool start(); // Штатное завершение: DELETE-команда + local_reg notify, ожидание выдачи, // остановка потоков. state -> kIdle. void stop(); SessionState state() const; SessionError last_error() const; uint16_t listen_port() const; // Телеметрия (диагностика). uint32_t rekey_count() const; uint32_t pushes_ok() const; uint32_t pushes_bad() const; uint32_t commands_served() const; bool had_activity() const; // ---- Команды (потокобезопасны; кладутся в очередь с coalescing) ---- // GET-команда: свойство придёт on_property (cmd_id совпадает). bool get_property(const char* name); // SET-команда (integer/boolean как int64). bool set_property(const char* name, int64_t value, const char* base_type = "integer"); // Пакет: собрать несколько команд, один notify на commit. bool begin_batch(); bool commit_batch(); bool abort_batch(); // DELETE local_reg.json/delete_session (для stop() и ручного завершения). bool delete_session(); // Тест-хук: тайминги. ТОЛЬКО до start() (после — читаются потоками ядра). void set_timings_for_test(const SessionTimings& t); private: Session(const SessionConfig& cfg, const SessionCallbacks& cbs); struct Impl; Impl* impl_; }; } // namespace fgl::ayla