diff --git a/AGENTS.md b/AGENTS.md index 7d697e7f..9d427756 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,10 +34,10 @@ This file contains essential information for AI coding agents working in the uTu ## Quick Reference -**Repository:** uTun - Secure VPN tunnel with ETCP protocol +**Repository:** uTun — общее сетевое ядро ETCP, независимо запускаемые UTUN и P2P-чат **Language:** C (C99) **Build System:** GNU Autotools (autoconf/automake) -**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256) +**Cryptography:** OpenSSL (AES-CCM, X25519, Ed25519, SHA256) ## Build Commands @@ -68,9 +68,10 @@ make distclean # Clean everything including configure files ### Partial Builds ```bash -cd lib && make # Build only the library (libuasync.a) -cd src && make # Build only the main program -cd tests && make # Build only tests +make -C lib # базовые библиотеки +make -C lib/libopus # встроенный Opus +make -C src # основной бинарник src/utun (библиотеки уже должны быть собраны) +make -C tests # тесты (используют объекты src/) ``` ### Direct Build (Windows, без autotools) @@ -86,7 +87,7 @@ python3 tools/chatcli members # мемберы с полн python3 tools/chatcli messages [count] # последние сообщения python3 tools/chatcli send "text" # отправить сообщение python3 tools/chatcli invite # создать invite-ссылку -python3 tools/chatcli connect # join по ссылке +python3 tools/chatcli connect 'utun://...' # join по ссылке python3 tools/chatcli create "Name" # создать канал python3 tools/chatcli listen # слушать события (Ctrl+C выход) ``` @@ -94,13 +95,14 @@ python3 tools/chatcli listen # слушать событи ### ASAN Build (AddressSanitizer, Linux) ```bash -./build.sh --asan -j4 # lib/ + src/ only (tests not ASAN-compatible) +./build.sh --asan -j4 # собирает lib/ + src/; тесты этот режим скрипта пропускает ``` Запуск с ASAN (detect_leaks отключены чтобы не спамить при shutdown): ```bash -ASAN_OPTIONS=detect_leaks=0:halt_on_error=0 ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log +ASAN_OPTIONS=detect_leaks=0:halt_on_error=0:log_path=/tmp/utun_asan ./src/utun -f -p /tmp/utun.pid -l /tmp/utun.log ``` -Краш-логи ASAN пишутся в `/tmp/utun_asan.`. +В этом примере отчёты ASAN пишутся в `/tmp/utun_asan.` благодаря `log_path`. +Для отдельного теста нужно собирать его зависимости с теми же sanitizer-флагами; обычные и ASAN-объекты не смешивать. ## Test Commands @@ -118,13 +120,13 @@ cd tests/ ./test_etcp_two_instances ``` -### Run Single Test with Debug Info +### Build and Run a Single Test ```bash -cd tests/ -gcc -I../src -I../lib -I../tinycrypt/lib/include \ - -o my_test test_file.c ../src/*.c ../lib/*.c ../tinycrypt/lib/source/*.c -./my_test +make -C tests test_services +cd tests && ./test_services ``` +Использовать цель из `tests/Makefile.am`: ручная компиляция `src/*.c` пропускает подкаталоги и добавляет лишний `main`. +Уровни и категории диагностики настраиваются в самом тесте через `debug_set_level` / `debug_set_category_level`. ## Code Style Guidelines @@ -137,7 +139,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \ ### Formatting - **Indentation:** 4 spaces, no tabs -- **Braces:** Same line for functions, new line for control structures: +- **Braces:** Открывающая скобка на строке функции или условия: ```c int function(void) { if (condition) { @@ -145,7 +147,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \ } } ``` -- **Comments:** Primary language is Russian for business logic, English for API docs +- **Comments:** Краткие описания на русском; сохранять принятые имена API и протокольных полей. - **Line Length:** Aim for 80-100 characters, but can go up to 150 if logically coherent ### Include Order @@ -201,7 +203,7 @@ sc_context_t ctx; sc_init_ctx(&ctx, &my_keys); // 2. Set peer public key (for key exchange) -sc_set_peer_public_key(&ctx, peer_public_key, SC_PEER_PUBKEY_HEX); // 0=bin, 1=hex +sc_set_peer_public_key(&ctx, peer_public_key_bin, SC_PEER_PUBKEY_BIN); // 32 бинарных байта; HEX — для hex-строки // 3. Ready for encrypt/decrypt sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ciphertext_len); @@ -227,40 +229,61 @@ debug - отладка: всё что нужно для понимание су info - сообщения для пользователя о работе. мы должны видеть в читаемом виде понятные и осмысленные сообщения о каких-либо значимых действиях с точки зрения логики работы модуля или приложения warn / error - ошибки и предупреждения (аномалии). должны быть во всех ошибочных ветках. Молча вываливаться с ошибкой нельзя, все ошибки и предупреждения должны логироваться. -### Debug Categories (28 категорий) -``` -NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6, -TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13, -SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20, -KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24, CONNECTIVITY=25, DB_SYNC=26, -MEMBER_SYNC=27 -``` +### Debug Categories +Актуальные имена и константы — в `lib/debug_config.h`, текстовые имена — в `lib/debug_config.c`. +Основные: `sys`, `connection`, `etcp`, `crypto`, `config`, `tun`, `routing`, `timers`, `bgp`, +`socket`, `control`, `dump`, `traffic`, `debug`, `general`, `nat`, `keepalive`, `etcp_route`, +`media`, `etcp_dump`, `chat`, `chat_sync`, `member_sync`, `proxy`, `video`, `reality`, `dm`, `call`, `radio`. +UASYNC/LL_QUEUE/MEMORY/TIMING объединены в SYS, NORMALIZER/BBR — в ETCP; старые числовые списки не использовать. ### Настройка отладки -- В конфиге: `debug = etcp=trace,config=info` (формат: `категория=уровень,...`) +- В INI-конфиге: секция `[debug]`, каждая категория отдельной строкой (`etcp=trace`, `config=info`). +- Строку `etcp=trace,config=info` принимает `debug_parse_config()`; GUI хранит её в `[gui] debug_categories`. - В коде: глобальный уровень и per-category уровни из `debug_config_t g_debug_config` - Макросы: `DEBUG_ERROR(cat,fmt,...)` `DEBUG_WARN` `DEBUG_INFO` `DEBUG_DEBUG` `DEBUG_TRACE` -- `log_dump(prefix, data, len)` — hex dump в лог +- `log_dump(level, category, prefix, data, len)` — hex dump в лог ### Dual Output -- Консоль и файл настраиваются раздельно (`debug_set_console_level`, `debug_set_file_level`) +- Фильтр уровней общий для выходов; `debug_enable_console()` включает/выключает консоль. - `debug_enable_file_output(path, truncate)` / `debug_disable_file_output()` ## Architecture +### Ядро, сервисы и владение ресурсами + +- `utun_instance_create*()` создаёт ядро; `utun_core_start()` запускает общие сетевые службы. +- `utun_service_start/stop()` управляет UTUN-группой, TUN/NAT и запросами к пирам из конфига. +- `chat_service_start/stop()` управляет чатом и CHAT-группами. Чат не требует UTUN; desktop и Android запускают ядро и чат явно. +- Ядро владеет общей SQLite (`db_path/chats.db`, без пути — `:memory:`), идентичностью, транспортом и маршрутизатором. + Остановка сервиса сохраняет БД и ресурсы другого сервиса. `utun_instance_destroy()` освобождает ядро, но не переданный `UASYNC`. +- Группа владеет NCD handle с начала CONNECTING. Инициатор хранит отменяемый `TOPO_PEER_REQUEST`; + закрытие своего запроса не разрывает транспорт других владельцев. Готовая сессия сохраняется у группы. +- UP означает работоспособный транспорт. READY означает завершённое согласование и обмен таблицей конкретной группы. + Recovery ждёт нужных маршрутов через READY-пиров; наличие `ETCP_CONN*` само по себе не гарантирует UP. +- JOIN добавляет подписанного мембера, Merkle распространяет запись. Доступ к CHAT-группе каждый узел даёт по своей таблице участников; + invite-ключ не даёт временного членства. Протокол описан в `src/chat/chat_join.h`, групповое согласование — в `src/routing_layer/topo_group.h`. +- `TOPO_NODE` — общая подписанная запись узла. Более свежий timestamp заменяет запись целиком, с полями и подписью; + адреса не версионируются отдельно. BGP обновляет запись и передаёт изменения NCD (см. `doc/node_snapshot.md`). + +Все операции с ядром выполняются в его uasync-потоке; stop/destroy — вне callbacks останавливаемых сервисов. +Порядок освобождения ресурсов и нюансы media workers: `doc/service_lifecycle.md`. + ### Directory Structure ``` -├── lib/ # Core libraries (18 .c + 20 .h + libopus/liblmdb) +├── lib/ # Core libraries, SQLite, libopus/liblmdb ├── src/ # Main source code -│ ├── transport_layer/ # ETCP stack, crypto, STCP, normalizer, BBR (~30 файлов) -│ ├── routing_layer/ # Routing, BGP, conn_mgr, etcp_router, NAT detection (~20 файлов) -│ ├── chat/ # P2P чат, db_sync, merkle_sync, member_sync (17 файлов) -│ ├── proxy/ # SOCKS5, TCP/UDP/ICMP прокси (10 файлов) -│ ├── lwip_tcp/ # Встроенный lwIP TCP стек (7 файлов) -│ ├── media_delivery/ # Доставка медиафайлов (6 файлов) -│ ├── media_async/ # Асинхронный движок медиа (2 файла) -│ └── *.c/h # Корневые модули: utun, config, TUN, firewall, NAT, NTP, control (~14 файлов) -├── tests/ # ~70 тестов, 64 в check_PROGRAMS +│ ├── transport_layer/ # ETCP, crypto, STCP, normalizer, NCD, BBR +│ ├── routing_layer/ # Routing, группы/BGP, recovery, conn_mgr, etcp_router +│ ├── chat/ # P2P чат, join, db_sync, merkle_sync, member_sync +│ ├── dm/ # Личные сообщения и mailbox +│ ├── call/ # Звонки и общий голосовой стек +│ ├── radio/ # Групповая PTT-рация +│ ├── proxy/ # SOCKS5, TCP/UDP/ICMP прокси +│ ├── lwip_tcp/ # Встроенный lwIP TCP стек +│ ├── media_delivery/ # Доставка медиафайлов +│ ├── media_async/ # Фоновые задачи медиа +│ └── *.c/h # utun, config, TUN, firewall, NAT, NTP, control +├── tests/ # Состав тестов и цели запуска: tests/Makefile.am ├── doc/ # Technical Specifications ├── tools/ # Auxiliary tools │ ├── etcpmon/ # GUI монитор ETCP @@ -278,7 +301,7 @@ MEMBER_SYNC=27 **Core (src/)** - `utun.c` - Main program entry point, CLI parsing, daemon mode -- `utun_instance.c/h` - Root instance lifecycle, config loading, all submodule init +- `utun_instance.c/h` - Создание ядра и независимый жизненный цикл UTUN/чата, владение общими ресурсами - `tun_if.c/h` - TUN interface API (init/write/close, cross-platform) - `tun_linux.c` `tun_freebsd.c` `tun_windows.c` - Platform-specific TUN implementations - `tun_route.c/h` - TUN routing table sync @@ -314,7 +337,7 @@ MEMBER_SYNC=27 - `stcp_server.c/h` - STCP сервер - `stcp_client.c/h` - STCP клиент - `stcp_link.c/h` - STCP линк-уровень -- `node_conn_direct.c/h` - Direct config-based connections +- `node_conn_direct.c/h` - Общий прямой транспорт к пиру с отдельным handle каждого владельца; обновление линков по адресам узла - `socket_monitor.c/h` - Socket state monitoring - `reality.c/h` - REALITY-style TLS ClientHello/ServerHello камуфляж для STCP - `reality_fingerprint.c/h` - Статические отпечатки (cipher suites, groups, ALPN) для REALITY-камуфляжа @@ -329,28 +352,30 @@ MEMBER_SYNC=27 - `route6_lib.c/h` - IPv6 routing - `route_ping.c/h` - Route ping probing (NAT check, liveness) - `route_connectivity.c/h` - Route connectivity checks -- `topo_group.c/h` - BGP-like route exchange between nodes (NODEINFO/WITHDRAW, изолированные группы UTUN/CHAT) -- `topo_group_connect.c/h` - Авто-подключение к узлам CHAT-группы (3 фазы, бесконечный цикл) -- `topo_group_invite.c/h` - Invite/join канала через прямое ncd-подключение (INVITE_INFO_REQ/RESP) +- `topo_group.c/h` - Сессии UTUN/CHAT (JOIN/ACCEPT, эпохи, READY), групповые запросы, NODEINFO/WITHDRAW и владение NCD +- `topo_group_connect.c/h` - Подбор пиров CHAT-группы: исторические → суперузлы/публичные → локальные; отменяемые запросы до READY +- `topo_group_invite.c/h` - Получение описания канала через INVITE_INFO_REQ/RESP; добавление мембера реализовано в chat_join/chat_sync - `topo_node.c/h` - Node management (модель узла: TOPO_NODE/TOPO_GROUP_NODE, сериализация, node_registry) - `topo_node_sqlite.c/h` - SQLite persistence for nodes -- `topo_recovery.c/h` - Восстановление каскадно отвалившихся узлов после разрыва соединения +- `topo_recovery.c/h` - Восстановление потерянных маршрутов группы через READY-сессии, отмена попыток при stop - `conn_mgr.h` + `conn_mgr_core.c`/`conn_mgr_indirect.c`/`conn_mgr_monitor.c` + `conn_mgr_priv.h` — Connection Manager (handle-based API, 3-phase DIR/REV/IND) - `etcp_router.c/h` - ETCP router/multiplexer (мультиплексирование каналов, transit forwarding) - `nat_detection.c/h` - NAT type detection - `route_crypto.c/h` - Пред/пост-обработка SVC_ROUTE пакетов (encrypt-then-sign) **Chat (src/chat/)** — децентрализованный P2P чат: сообщения и участники автоматически синхронизируются между всеми узлами канала без центрального сервера. -- `chat_core.c/h` - Главный модуль чата. Единственная точка входа из GUI: принять сообщение, создать канал, изменить настройку. Управляет БД и жизненным циклом всех chat-модулей +- `chat_core.c/h` - API сообщений, каналов, профиля и настроек; chat_service_start/stop управляют чат-сервисом поверх общей БД ядра - `chat_core_priv.h` - Внутренний API для частей chat_core: общий контекст и утилиты БД. Снаружи не используется - `chat_sync.c/h` - Связывает чат с ETCP-сетью: отслеживает появление/разрыв соединений, обновляет онлайн-статус узлов, запускает синхронизацию участников каналов, обрабатывает приглашения - `chat_event.c/h` - Доставка событий из ядра чата в GUI. Единый механизм: любой модуль отправляет событие (новое сообщение, смена участников, статус), GUI получает через один обработчик -- `chat_setting.c/h` - Настройки чата: проверяет корректность значений, хранит текущие. Используется и headless-парсером конфига, и GUI. Не зависит от других модулей -- `db_sync.c/h` - Автоматическая репликация данных между подключёнными узлами. Запись на одном узле — появляется у всех остальных. Криптоподписи и цепочка хешей защищают от подделок +- `chat_setting.c/h` - Проверка и хранение настроек чата per-instance; общая логика для конфигурации и GUI +- `chat_join.c/h` - Регистрация invite-ключа с ACK, пересылка запроса инвайтеру и добавление подписанного мембера; контракт join в заголовке +- `db_sync.c/h` - Репликация подписанных записей SQLite через прямые ETCP-соединения; расхождения по цепочке хешей, новые записи через PUSH - `member_sync.c/h` - Синхронизация участников каналов поверх merkle_sync. Два подписанных блока (мембер/владелец), по-блочное сравнение версий, верификация подписей. Удаление — только битая запись - `merkle_sync.c/h` - Универсальный движок синхронизации на Merkle-деревьях (5 уровней × 32 бакета, SHA256). Пиры обмениваются хешами и передают только различия; per-session out-очередь с backpressure. Не привязан к конкретному типу данных (коллбэки `data_ops`) +- `merkle_tree.c/h` - Производный индекс Merkle-хешей в SQLite; восстанавливается из данных модели, обновляется с ними в одной транзакции - `chat_profile.c` - Собственный профиль: имя узла, сетевые адреса, сохранение UI-состояния (часть chat_core) -- `chat_channel.c` - Создание и настройка каналов: готовит таблицы в БД, генерирует криптоключи, запускает подключение ко всем участникам (часть chat_core) +- `chat_channel.c` - Создание и настройка каналов: таблицы, криптоключи, CHAT-группа и планировщик подключения (часть chat_core) - `chat_msg.c` - Отправка и приём сообщений: запись в локальную БД, автоматическая рассылка всем участникам канала (часть chat_core) - `chat_status.c` - Сбор диагностики: текущее время, активные соединения, типы NAT. Отправляется в GUI (часть chat_core) - `chat_member.c/h` - Единая структура мембера для отображения в GUI (десктоп/headless/Android), фиксированная сериализация на проводе @@ -358,7 +383,7 @@ MEMBER_SYNC=27 - `chat_whisper.c/h` - Whisper speech-to-text транскрипция голосовых сообщений (опционально, асинхронно через media_async) - `chat_headless_control.c/h` - TCP control-socket для headless chat CLI (JSON line protocol) - `invite_build.c/h` - Сборка invite-ссылок utun:// (выбор лучшего узла + адреса) -- `invite_link.c/h` - Формат invite-ссылок utun:// для каналов (base64: version|password|channel_id|pubkey|addrs) +- `invite_link.c/h` - Кодирование utun://: версия, пароль, channel_id, join_key, Reality-параметры, ключ и адреса узла **Прокси (src/proxy/)** - `socks_proxy.c/h` - SOCKS5 прокси @@ -379,11 +404,11 @@ MEMBER_SYNC=27 - `media_index.c/h` - Индексация медиафайлов **Media Async (src/media_async/)** -- `media_async.c/h` - Асинхронный движок (thread-per-task для crypto/file операций) +- `media_async.c/h` - Фоновые crypto/file-задачи: work в pthread, done в uasync; destroy ждёт workers и завершает ожидающие callbacks с CANCELLED **Libraries (lib/)** - `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap) -- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter +- `ll_queue.c/h` - Двусвязная очередь одного uasync-потока с callbacks, хеш-индексом и ожиданием свободного места - `memory_pool.c/h` - Fast object pool allocator (pre-allocated blocks) - `debug_config.c/h` - Debug logging system (levels, categories, dual output, runtime config) - `timeout_heap.c/h` - Min-heap for timer management (used by u_async) @@ -416,7 +441,7 @@ MEMBER_SYNC=27 ### Key Components - **UASYNC:** One per thread. Async event loop (epoll/poll), timers via timeout_heap -- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter +- **LL_QUEUE:** Очередь с auto-callback, хеш-индексом и threshold waiter; не средство обмена между потоками - **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments) - **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing - **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation @@ -432,6 +457,8 @@ MEMBER_SYNC=27 - Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой. - Порог задаётся через `queue_set_threshold(q, max_packets, max_bytes)`. - Используй `queue_waiter_wait` для ожидания освобождения очереди до заданного порога. +- Handle ожидания обнуляется целиком, живёт до callback/отмены и отменяется до освобождения владельца. + Callback может быть синхронным. `queue_entry_free` не освобождает dgram — его освобождают отдельно. ### Чтение из очереди - Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`. @@ -451,7 +478,8 @@ MEMBER_SYNC=27 ## Config Rules - В серверном конфиге только собственные ключи и нет секций `[client]` - В клиентском конфиге есть собственные ключи и pubkey каждого сервера в секции `[client]` -- Chat-настройки хранятся в секции `[chatserver]` (принимается также `[chat]`). Парсер вызывает `chat_setting_set(key, value)` для каждого ключа — неизвестные ключи вызывают ошибку. Пример: +- Chat-настройки хранятся в `[chatserver]` (также `[chat]`). Парсер обрабатывает общие поля секции, + затем вызывает `chat_setting_set_state(&cfg->global.chat_settings, key, value)`; неизвестные ключи логируются как ошибка. Пример: ```ini [chatserver] storage_autoload=1 @@ -474,7 +502,8 @@ MEMBER_SYNC=27 Если видишь спам-логи - подумай как их выборочно отключить чтобы не мешали. Для отладки добавляй в DEBUG_CATEGORY_DEBUG диагностические сообщения где надо. И включи эту категорию в настройках. Убирай только после проверки (путём запуска) когда все ошибки устранены. -сообщение выводится по или - либо debug_set_level(DEBUG_LEVEL_TRACE) - выводится ВСЁ независимо от настроек по категориям. Тоесть берется max(global level, category lavel) +Явный уровень категории заменяет глобальный. DEBUG_LEVEL_NONE наследует глобальный уровень; +DEBUG_LEVEL_DISABLED полностью отключает категорию, в том числе при глобальном TRACE (см. debug_should_output). Твой бич - ты постоянно гадаешь и анализируешь код. Что приводит к снежному кобу ошибок и неверных гипотез. 10 раз повторяю - только логи логи логи и никакого гадания. Лоооги!!! правильные логи покажут всё с предельной точностью. Вся суть отладки - информативные логи И смотри логи. не задавливай их grep-ом. лучше больше. единственное с чем борись - это бесполезные спам логи. @@ -484,7 +513,8 @@ MEMBER_SYNC=27 Но полезные смотри всегда, и всегда оставляй логи которые выводятся нечасто. Частые логи - это трафик которые >100 раз повторяются. Но которые мало раз обязательно оставлять и выводить. -можешь в тесте включить логи в нужный интервал чтобы не спамить. debug_set_level(DEBUG_LEVEL_TRACE) и выключить debug_set_level(DEBUG_LEVEL_NONE) +В тесте можно временно поднять нужные категории до TRACE, сохранив прежние уровни и восстановив их после участка. +Одного debug_set_level(DEBUG_LEVEL_NONE) недостаточно для отключения категорий с явно заданным уровнем. Если видишь проблему и ее решение неочевидно то выстраивай диагностику вокруг неё пока не будет очевидно где и что происходит не так. Это базовое и обязательное требование к отладке. Подробные логи ты должен выводить не обрезая всегда и анализировать. Если лог большой - выведи в файл и анализируй файл. Рассуждение должно быть примерно таким: @@ -541,9 +571,11 @@ MEMBER_SYNC=27 ## chatgui (GUI Chat Client) -Chatgui — десктопный GUI-чат на Qt 6 (Qt 5 fallback), отдельный проект внутри репозитория. Не связан с autotools-сборкой utun. +Chatgui (бинарник `vibechat`) — десктопный чат на Qt 6 (Qt 5 fallback), отдельная CMake-сборка общего ядра. +Корневой `build.sh` дополнительно вызывает её, если уже существует `tools/chatgui/build/CMakeCache.txt`. -В chat gui интегрированы библиотеки utun. Чат и библиотеки работают в разных потоках. Поэтому нужно использовать семафоры, сокеты или другие механизмы синхронизации (uasync_post, uasync_memsync, uasync_get_wakeup_fd) +GUI работает в Qt-потоке, ядро и чат — в uasync-потоке. Команды передаются через +`gui_bridge_post_uasync()` / `uasync_post()`, события возвращаются через Qt-сигналы. База данных sqlite в chatgui: можно писать (update) из потока utun / instance. можно только читать из gui потока. @@ -551,28 +583,28 @@ Chatgui — десктопный GUI-чат на Qt 6 (Qt 5 fallback), отде - **Язык:** C++20 - **Фреймворк:** Qt 6 (предпочитаемый) или Qt 5.15+ (Widgets + Network) - **Сборка:** CMake 3.16+ -- **БД:** SQLite3 (WAL mode, встроенный `db/sqlite3.c` amalgamation) +- **БД:** SQLite3 (WAL mode, компилируется общий `lib/sqlite3.c`) - **Анимации:** rlottie (C API) + zlib (gzip-декомпрессия TGS) ### Сборка ```bash # Установка зависимостей -sudo apt install librlottie-dev zlib1g-dev qt6-base-dev +sudo apt install librlottie-dev zlib1g-dev qt6-base-dev libssl-dev libx11-dev # или qtbase5-dev для Qt 5 fallback # Сборка cd tools/chatgui && mkdir -p build && cd build cmake .. && make -j4 -./chatgui +./vibechat ``` ### Конфиг и логи чатгуи -- **Конфиг:** `tools/chatgui/build/chatgui.cfg` (в `.gitignore`, содержит приватные ключи) +- **Конфиг:** `vibechat.cfg` рядом с бинарником (обычно `tools/chatgui/build/vibechat.cfg`, содержит приватные ключи) - Секция `[gui]`: `debug_file`, `debug_level`, `debug_categories=cat=level,...` - Секция `[control]`: `ip`, `port` для подключения etcpmon - Секция `[ntp]`: синхронизация времени - **Лог:** `tools/chatgui/build/chatgui.log` (путь задаётся в конфиге `debug_file`) -- **БД чата:** `tools/chatgui/build/chat_data/` (SQLite, путь из `db_path` в конфиге) +- **БД чата:** `chats.db` в каталоге `[gui] db_path` (по умолчанию `chat_data/` рядом с бинарником) ### Структура файлов (src/) @@ -581,7 +613,7 @@ cmake .. && make -j4 | `main.cpp` | Точка входа, инициализация QApplication | | `mainwindow.h/cpp` | Главное окно, QSplitter (ChannelList \| MessageList \| AccountList), трей | | `chatview.h/cpp` | QListView с фоновым изображением, hover-сигнал, drag-to-select | -| `messagelist.h/cpp` | QStandardItemModel, stub-данные, инициализация анимаций, hover→activate | +| `messagelist.h/cpp` | Модель сообщений из БД, обновления доставки/медиа, анимации, состояние просмотра канала | | `messagedelegate.h/cpp` | QStyledItemDelegate: бабблы, текст, цитаты, реакции, оверлей анимаций | | `emoji.h/cpp` | EmojiData/EmojiCategory/EmojiType, 4 категории, `g_animatedEmojiMap` | | `emojipanel.h/cpp` | Эмодзи-пикер: QTabWidget + QGridLayout, EmojiButton с hover-анимацией | @@ -622,7 +654,8 @@ cmake .. && make -j4 | `debug_ui.h` | Отладочный UI | ### Другие поддиректории -- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, node_addresses, channels, msg_, peers_, local_identity, accounts, ui_state) +- `db/` — `db_manager.h/cpp`: доступ GUI к данным nodes, channels, msg_, peers_ и состоянию интерфейса; + копия `db/sqlite3.c/h` не используется текущей CMake-целью, SQLite берётся из `lib/` - `transport/` — интеграция с uTun: `gui_bridge.h/gui_bridge_impl.cpp` (Qt signal-based обмен), `node_config.h/cpp` (конфиг узла), `config_updater.h/cpp` (правка конфига), `utun_node.h/cpp` (обёртка узла), `miniaudio_impl.c` (single-header miniaudio в отдельной единице трансляции). Chat-логика (`chat_core`, `chat_sync` и т.д.) компилируется напрямую из `src/chat/`, роутинг — из `src/routing_layer/` (см. `libutun/CMakeLists.txt`) - `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs` @@ -718,18 +751,19 @@ void lottie_animation_destroy(Lottie_Animation *anim); --- ## Key Documentation Files -- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive) -- `/doc/etcp_arch.md` - ETCP архитектура -- `/doc/etcp_config.txt` - Конфигурация ETCP -- `/doc/etcp_router_arch.md` - ETCP роутер архитектура -- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения -- `/src/route_bgp.txt` - BGP обмен маршрутами (дизайн-документ) -- `/doc/chat_join_test.md` - ТЗ теста join-протокола (chat_join + дерево приглашений) -- `/doc/chat_join_test_tasks.md` - Список задач по тесту join (текущий статус, пошагово) +- `doc/service_lifecycle.md` — ядро, независимые сервисы, владение ресурсами и остановка +- `doc/node_snapshot.md` — подписанная запись узла, timestamp, SQLite и обновление NCD +- `doc/etcp_protocol.txt` — ETCP: кодограммы, ACK, handshake, keepalive +- `doc/etcp_arch.md` — архитектура ETCP +- `doc/etcp_router_arch.md` — архитектура маршрутизатора +- `src/chat/chat_join.h` — нормативный протокол добавления участника +- `src/routing_layer/topo_group.h` — протокол групповой сессии и критерий READY +- `tests/test_chat_join_e2e.c`, `tests/test_topo_recovery.c`, `tests/test_services.c` — проверки join, recovery и жизненного цикла +- `doc/dm_arch.md`, `doc/chat_call_arch.md` — личные сообщения и звонки ## Список задач по проекту -Список задач — `/doc/tasks.md` (текущая работа, берём по одной задаче сверху). +Список задач — `doc/tasks.md` относительно корня репозитория (текущая работа, берём по одной задаче сверху). При нахождении попутных багов/fail/флаков тестов добавляем задачу (либо фиксим сразу если баг простой). Как сделали - помечаем [+] выполнено. @@ -737,8 +771,9 @@ void lottie_animation_destroy(Lottie_Animation *anim); **Расположение:** `tools/chatgui-android/` -Android-версия чатгуи — P2P чат на STCP (TCP), UI на Jetpack Compose (Kotlin). -C-ядро: `libutun_lite` (выборочная компиляция нужных .c из `lib/` и `src/`). +Android-версия чатгуи — P2P чат на общем ETCP/STCP-ядре, UI на Jetpack Compose (Kotlin). +C-библиотека `utun_lite` собирает `lib/` и `src/` по `libutun_lite/utun_sources.cmake`; +`instance_lite` запускает ядро и чат в отдельном потоке, без UTUN-сервиса. Сборка: CMake (headless, Linux) + Gradle/NDK (Android APK). - 'fw' - собрать и обновить chatgui-android на всех подключенных телефонах (clean + сборка + install) @@ -746,9 +781,10 @@ C-ядро: `libutun_lite` (выборочная компиляция нужны **Chat-модули:** все файлы из `src/chat/` (описаны выше в секции «Chat») компилируются в `libutun_lite`. ## Runtime -- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh` -- Стоп utun: `sudo /home/vnc1/proj/utun3/utun_stop1.sh` -- Логи: `utun.log` (stdout), `utun_err.log` (stderr) +- После autotools-сборки бинарник — `src/utun`. Запуск из корня с TUN: `sudo ./src/utun -f -c utun.cfg`. +- Старый `utun_start.sh` ожидает `./utun` в корне. `utun_stop.sh` делает `killall -9 utun`, а не штатную остановку одного экземпляра. +- `-f` оставляет процесс на переднем плане; остановка — SIGINT/SIGTERM. Пути конфигурации, PID и лога задаются через `-c`, `-p`, `-l`. +- `utun_start.sh` перенаправляет stdout в `utun.log`, stderr в `utun_err.log`. - Тестовые логи: `tests/logs/` ## Git Conventions @@ -759,19 +795,19 @@ C-ядро: `libutun_lite` (выборочная компиляция нужны ## Quick Start for New Features 1. Add new source file to `src/Makefile.am` under `utun_SOURCES` -2. Add test file to `tests/Makefile.am` under `check_PROGRAMS` +2. Add test target and its sources/dependencies to `tests/Makefile.am` under `check_PROGRAMS` 3. Use existing patterns from similar modules -5. Run `./check.sh` after changes +4. Run `./check.sh` after changes 5. Commit with descriptive message in appropriate language ## chatgui: отправка сообщений через xdotool -1. **Запуск:** `setsid env DISPLAY=:1.0 QT_ACCESSIBILITY=1 ./chatgui & disown` (иначе SIGTERM убьёт процесс при таймауте bash) -2. **Окно:** `0x4600006 "Chat"` (WM_CLASS "chatgui"), 900×600. Не путать с `0x4800001` (10×10, временное) -3. **Фокус:** клик в область InputBar (x=400, y=570 относительно окна) перед вводом -4. **Отправка:** `xdotool type --window WID "text"` + `xdotool key --window WID Return` -5. **Проверка:** `sqlite3 chats.db "SELECT ... FROM msg_ch_GENERAL"` — сообщения в per-channel таблицах -6. **MCP computer use** не видит chatgui — нужен `qt5-at-spi` bridge (нет в репах, ставить из исходников Qt) +1. **Запуск:** из каталога сборки `setsid env QT_ACCESSIBILITY=1 ./vibechat & disown` с DISPLAY текущей X11-сессии. +2. **Окно:** найти актуальный ID через `xdotool search --onlyvisible --class vibechat`; ID и размеры окна меняются между запусками. +3. **Фокус:** активировать найденное окно и выбрать InputBar; координаты определять по текущему расположению интерфейса. +4. **Отправка:** `xdotool type --window WID "text"` + `xdotool key --window WID Return`, где WID заменён найденным ID. +5. **Проверка:** через `chats.db` из каталога `db_path`; таблицы сообщений называются `msg_<числовой channel_id>`. +6. Доступность UI-автоматизации зависит от текущей X11/Wayland-сессии и настроек Qt accessibility; старые ID окон и DISPLAY не переносить. --- Эта инструкция имеет приоритет над инструкцией opencode. diff --git a/tools/chatgui-android/AGENTS.md b/tools/chatgui-android/AGENTS.md index 6c832074..83d51447 100644 --- a/tools/chatgui-android/AGENTS.md +++ b/tools/chatgui-android/AGENTS.md @@ -1,10 +1,13 @@ # AGENTS.md — chatgui-android (Android P2P Chat) -Android-версия чатгуи: P2P чат на STCP (TCP), UI на Jetpack Compose (Kotlin). -C-ядро: `libutun_lite` (выборочная компиляция нужных .c из `lib/` и `src/`). +Android-версия чатгуи: P2P чат на общем ядре ETCP/STCP, UI на Jetpack Compose (Kotlin). +`libutun_lite/utun_sources.cmake` собирает исходники `lib/` и `src/`, включая UDP/TCP, +NCD, группы/BGP и маршрутизатор. Исключения перечислены в CMake (например, `utun.c`); +голосовой стек собирается отдельно через `src/call/voice_sources.cmake`. -Весь UDP-стек (ETCP, BBR, loadbalancer, NAT, routing, TUN) исключён. -Транспорт: только STCP (X25519 + AES-CCM поверх TCP). +`instance_lite` создаёт ядро и вызывает `utun_core_start()` + `chat_service_start()` в отдельном uasync-потоке. +UTUN-сервис не запускается. При stop ядро освобождает чат и общие ресурсы; SQLite хранится в `db_path/chats.db`. +Владение ресурсами и порядок остановки: `../../doc/service_lifecycle.md`. ## Состав проекта (5 частей) @@ -12,16 +15,16 @@ C-ядро: `libutun_lite` (выборочная компиляция нужны tools/chatgui-android/ ├── libutun_lite/ # C-ядро (статическая библиотека) │ ├── CMakeLists.txt -│ ├── utun_sources.cmake # список компилируемых .c файлов +│ ├── utun_sources.cmake # GLOB исходников, исключения и файлы обёртки │ ├── instance_lite.h/c # жизненный цикл (старт/стоп/рестарт, ключи) │ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C) -│ ├── invite_link_c.h/c # invite-ссылки (encode/decode, совместимы с десктопом) +│ ├── invite_link_c.h/c # старая копия парсера; .c не входит в UTUN_SOURCES │ ├── voice_recorder.h/c # запись голосовых (PCM→Opus→канал) │ └── attachment_sender.h/c # отправка файлов в канал │ ├── headless/ # CLI для Linux (тестирование без Android) │ ├── CMakeLists.txt -│ ├── headless_main.c # точка входа, uasync event loop +│ ├── headless_main.c # отдельный каркас control-loop; сам ядро не запускает │ └── headless_control.c/h # управляющий TCP-сокет (JSON/text протокол) │ ├── jni_bridge/ # JNI прослойка C ↔ Kotlin @@ -59,10 +62,10 @@ tools/chatgui-android/ │ └── res/ │ └── doc/ # документация - ├── AGENTS.md # этот файл ├── ARCHITECTURE.md # подробная архитектура └── IMPL_PLAN.md # план реализации по этапам ``` +Этот `AGENTS.md` лежит в корне `tools/chatgui-android/`. Старые планы в `doc/` сверять с текущим кодом. ## Chat-подсистема (src/chat/) @@ -138,7 +141,7 @@ sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;29.0.14206865" ## Сборка ``` -./build.sh # clean + сборка + прошивка на подключённый телефон +./build.sh # clean + сборка + установка на все подключённые устройства ./build.sh noinstall # только сборка, без прошивки ./build.sh # clean + сборка + прошивка на конкретный девайс ``` @@ -154,6 +157,11 @@ export ANDROID_HOME=/home/user/Android/Sdk ### Headless (Linux, для отладки сетевого стека) +Каталог `headless/` содержит отдельный каркас управления: его `main` не запускает +`instance_lite` и полноценный чат. Для сетевых сценариев использовать основной +`src/utun` с `[chatserver] headless_control_bind` и `tools/chatcli` (см. корневой `AGENTS.md`). +Команды сборки каркаса; требуется заранее собранный `lib/libopus/libopus_internal.a`: + ```bash cd tools/chatgui-android && mkdir -p build && cd build cmake .. && make -j4 @@ -172,11 +180,11 @@ adb -s install -r app/build/outputs/apk/debug/app-debug.apk # Смотреть логи adb logcat -s utun:* -# Control-сокет (при запущенном HeadlessService): +# Control-сокет (если его запуск включён в конфигурации приложения): adb forward tcp:9999 tcp:9999 nc localhost 9999 > {"id":1,"cmd":"status"} -> {"id":2,"cmd":"join","link":"utun://..."} +> {"id":2,"cmd":"connect","link":"utun://..."} ``` ## Конфиг @@ -184,7 +192,7 @@ nc localhost 9999 Конфиг хранится в Android DataStore (`ConfigProvider.kt`) и передаётся в C-ядро при старте как INI-текст через `nativeStart(configText)`. -Формат (генерируется автоматически, пользователь не редактирует): +Сокращённый пример INI; текущие поля и значения генератора — в `ConfigProvider.buildConfigText()`: ```ini [global] @@ -193,9 +201,10 @@ my_public_key=<64 hex chars X25519> my_private_key=<64 hex chars X25519> db_path=/data/data/com.utun.chat/files db_sync_enabled=1 +chatserver_enabled=1 +auto_sockets=android -[server:main] -addr=0.0.0.0: +# Секции [server:<имя>] генерируются из настроенных интерфейсов. [allowed_keys] allow_all=yes @@ -203,10 +212,10 @@ allow_all=yes [ntp] enabled=no -[chatserver] +[chat] storage_autoload=1 -storage_unit_size=10M -storage_total_size=1G +storage_autoload_maxsize_mb=10 +storage_maxsize_gb=1 opus_codec_preset=1 compressor_enabled=0 compressor_max_gain_db=25 @@ -214,7 +223,8 @@ compressor_rise_rate=10 media_download_max_peers=3 [debug] -console_level=info +chat=info +chat_sync=info # Опционально — UDP-лог [log_udp] @@ -238,10 +248,11 @@ tools/logreceiver/logreceiver_restart.sh убивает старый демон и запускает новый на `0.0.0.0:9999`, вывод в `logreceiver_output.log`. При каждом запуске логи фиксируются в истории и начинается свежий лог. -В конфиге Android: +В INI-конфиге Android (генерируется `ConfigProvider.buildConfigText`): ```ini -log_udp_ip = -log_udp_port = 9999 +[log_udp] +ip= +port=9999 ``` ## Ключевые файлы для доработок @@ -249,19 +260,21 @@ log_udp_port = 9999 ### C-слой - `libutun_lite/instance_lite.h/c` — Жизненный цикл uTun для Android: запуск/остановка C-ядра в отдельном потоке, перезапуск при смене конфига, генерация и обновление X25519-ключей, health-check - `jni_bridge/android_jni_bridge.h/c` — JNI-прослойка Kotlin↔C: все операции из UI (отправка сообщений, вход в каналы, голосовые, статус, настройки) и обратные вызовы (логи, события). Здесь же — JNI-функции, компилируемые только для Android -- `libutun_lite/invite_link_c.h/c` — Кодирование и декодирование invite-ссылок `utun://` в бинарный формат. Совместим с десктопной версией +- `../../src/chat/invite_link.c/h` — Общий C-код формата invite-ссылок; Android разбирает ссылки в `data/InviteLink.kt`. + Старый `libutun_lite/invite_link_c.c` не компилируется текущим списком источников - `libutun_lite/utun_config_api.h/c` — Поставщик конфигурации из Kotlin в C-ядро через callback-интерфейс (get_string, get_int64, get_int) - `libutun_lite/voice_recorder.h/c` — Запись голосовых сообщений: накопление PCM-сэмплов с компрессором, кодирование в Opus-файл, отправка в канал через chat_core - `src/call/call_audio.h/c` (+ `voice_jitter.cpp`, `call_tones.c`, SoundTouch) — единый голосовой стек звонка (`libutun_voice`), собирается через `src/call/voice_sources.cmake`; Opus encode (PCM→peer) и decode + адаптивный джиттер-буфер + time-stretch (PCM отдаётся через `nativeCallAudioPull`). Аудио I/O в Kotlin (AudioRecord/AudioTrack) - `libutun_lite/attachment_sender.h/c` — Отправка файлов в канал: копирование в media-директорию и регистрация через chat_core (media_index → db_sync) -- `libutun_lite/utun_sources.cmake` — Список всех .c файлов, компилируемых в libutun_lite. Новые файлы добавлять сюда +- `libutun_lite/utun_sources.cmake` — Общий список источников: `lib/*.c` и рекурсивный `src/*.c` подхватываются автоматически; + файлы самой обёртки перечислены в `_cfg_src`, исключения — через `list(FILTER/REMOVE_ITEM)` ### Kotlin-слой - `data/NativeLib.kt` — Kotlin-обёртка над C-библиотекой: все вызовы из Kotlin транслируются в JNI-функции - `data/CallAudioEngine.kt` — Аудио-движок звонка: AudioRecord/AudioTrack, audio-focus, потоки захвата (feed) / воспроизведения (pull из C-стека) - `data/ChatRepository.kt` — Хранилище данных: буферизация сообщений и каналов между C-ядром и UI через StateFlow - `data/InviteLink.kt` — Разбор invite-ссылок: извлекает ID канала, публичный ключ, адреса для подключения -- `data/ConfigProvider.kt` — Поставщик конфигурации из Android DataStore в C-ядро через callback-интерфейс +- `data/ConfigProvider.kt` — Настройки DataStore и генерация INI-текста для запуска C-ядра - `data/LogManager.kt` — Сбор и хранение логов из C-ядра через log-callback - `viewmodel/ChatViewModel.kt` — ViewModel: StateFlow-состояние для UI (список каналов, сообщения, статус) - `ui/screens/ChannelListScreen.kt` — Главный экран: список каналов, кнопки Join (по invite-ссылке) и Create @@ -270,31 +283,28 @@ log_udp_port = 9999 - `ui/screens/QrScanScreen.kt` — QR-сканер для invite-ссылок (CameraX + ML Kit) - `MainActivity.kt` — Точка входа Android-приложения: навигация между экранами, QrScan → JoinDialog flow -### Headless (тестирование без телефона) -- `headless/headless_main.c` — Точка входа headless-режима: парсинг аргументов, запуск C-ядра в uasync event loop -- `headless/headless_control.c/h` — Управляющий TCP-сокет: JSON-команды (`status`, `send`, `join`, `subscribe`) и асинхронные события +### Headless +- `headless/headless_main.c` — Отдельный select/poll-цикл управления; запуск ядра в этом main не реализован +- `headless/headless_control.c/h` — Каркас TCP-управления. Рабочий headless API общего чата находится в `../../src/chat/chat_headless_control.c/h` ## Invite-ссылки (механика подключения к каналу) -Формат идентичен десктопной версии: - -``` -utun:// + base64( - version(1B, 0x01) | - channel_id(8B BE) | - [header(1B: bits0-1=count-1, bits2-5=family_flags) | pubkey(32B) | - addrs*(socketId(1B) | address(4B v4/16B v6) | port(2B BE))]+ -) -``` +Создаваемые ссылки имеют версию `0x03`: `utun://` + base64(версия, длина/байты пароля, +channel_id, join_key, Reality-параметры, блоки ключа и адресов). У адреса есть socketId, +proto, IP и port. Точный формат: `../../src/chat/invite_link.c` и `data/InviteLink.kt`. +Node ID получает Kotlin через `NativeLib.deriveNodeId`, вызывающий `sc_derive_node_id_from_pubkey()`. -Node ID вычисляется как `SHA256(pubkey)[0:8] & 0x7FFFFFFFFFFFFFFF` (совместимо с `sc_derive_node_id_from_pubkey()`). +Ссылка готова после подтверждённой регистрации join_key. NCD удерживается до результата join; +доступ к группе появляется после сохранения мембера в локальной таблице принимающего узла. +JOIN_READY протокола добавления и READY групповой сессии — разные состояния. +Полный контракт: `../../src/chat/chat_join.h` и `../../src/routing_layer/topo_group.h`. ## Правила разработки - Все C-файлы: C99, стиль как в корневом `AGENTS.md` (4 пробела, snake_case, DEBUG_* макросы) -- Все новые C-файлы добавлять в `libutun_lite/utun_sources.cmake` +- Для новых C-файлов проверить включение через `libutun_lite/utun_sources.cmake`; файлы обёртки добавить в `_cfg_src` - Все новые Kotlin-файлы — Compose, Material3, coroutines/StateFlow - ViewModel не держит ссылки на Context - Логи в C — через `debug_set_log_hook` → bridge → Kotlin `LogManager` -- Конфиг в C — через `utun_config_provider_t` коллбэки в Kotlin +- Основной запуск: Kotlin передаёт INI-текст через `nativeStart(configText)`; парсинг выполняет общий `config_parser` - Перед коммитом: головная C-сборка + `./gradlew assembleDebug`