Browse Source

docs: AGENTS.md chat module descriptions, improved ETCP debug logging, android AGENTS update

topo_upd
evgeny 2 months ago
parent
commit
df13b16fad
  1. 18
      AGENTS.md
  2. 24
      src/chat/chat_sync.h
  3. 4
      src/transport_layer/etcp.c
  4. 39
      src/transport_layer/etcp_connections.c
  5. 2
      src/transport_layer/etcp_loadbalancer.c
  6. 46
      tools/chatgui-android/AGENTS.md

18
AGENTS.md

@ -297,6 +297,20 @@ KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24
- `eim_nat.c/h` - Endpoint-Independent Mapping NAT engine - `eim_nat.c/h` - Endpoint-Independent Mapping NAT engine
- `nat_transport.c/h` - NAT transport layer (packet relay) - `nat_transport.c/h` - NAT transport layer (packet relay)
**Chat (src/chat/)** — децентрализованный P2P чат: сообщения и участники автоматически синхронизируются между всеми узлами канала без центрального сервера.
- `chat_core.c/h` - Главный модуль чата. Единственная точка входа из GUI: принять сообщение, создать канал, изменить настройку. Управляет БД и жизненным циклом всех chat-модулей
- `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` - Автоматическая репликация данных между подключёнными узлами. Запись на одном узле — появляется у всех остальных. Криптоподписи и цепочка хешей защищают от подделок
- `member_sync.c/h` - Списки участников каналов в актуальном состоянии у всех. Добавил/удалил участника — изменение разойдётся по всем узлам. Работает поверх merkle_sync
- `merkle_sync.c/h` - Быстрое сравнение и синхронизация данных между двумя узлами. Обмениваются контрольными суммами и передают только различия. Универсальный движок: не привязан к конкретному типу данных
- `chat_profile.c` - Собственный профиль: имя узла, сетевые адреса, сохранение UI-состояния (часть chat_core)
- `chat_channel.c` - Создание и настройка каналов: готовит таблицы в БД, генерирует криптоключи, запускает подключение ко всем участникам (часть chat_core)
- `chat_msg.c` - Отправка и приём сообщений: запись в локальную БД, автоматическая рассылка всем участникам канала (часть chat_core)
- `chat_status.c` - Сбор диагностики: текущее время, активные соединения, типы NAT. Отправляется в GUI (часть chat_core)
**Config (src/)** **Config (src/)**
- `config_parser.c/h` - INI-style config file parsing - `config_parser.c/h` - INI-style config file parsing
- `config_updater.c/h` - Config file modification utilities - `config_updater.c/h` - Config file modification utilities
@ -304,9 +318,6 @@ KEEPALIVE=21, ETCPROUTE=22, BBR=23, ETCP_DUMP=24
**Control (src/)** **Control (src/)**
- `control_server.c/h` - Control/monitoring server (etcpmon backend API) - `control_server.c/h` - Control/monitoring server (etcpmon backend API)
**Транспорт сообщений (src/)**
- `db_sync.c/h` - Синхронизация БД между узлами
**Прокси (src/proxy/)** **Прокси (src/proxy/)**
- `socks_proxy.c/h` - SOCKS5 прокси - `socks_proxy.c/h` - SOCKS5 прокси
- `udp_proxy.c/h` - UDP прокси - `udp_proxy.c/h` - UDP прокси
@ -626,6 +637,7 @@ C-ядро: `libutun_lite` (выборочная компиляция нужны
Сборка: CMake (headless, Linux) + Gradle/NDK (Android APK). Сборка: CMake (headless, Linux) + Gradle/NDK (Android APK).
**Подробная инструкция:** `tools/chatgui-android/AGENTS.md` **Подробная инструкция:** `tools/chatgui-android/AGENTS.md`
**Chat-модули:** все файлы из `src/chat/` (описаны выше в секции «Chat») компилируются в `libutun_lite`.
## Runtime ## Runtime
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh` - Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`

24
src/chat/chat_sync.h

@ -1,3 +1,27 @@
/*
* chat_sync.h — оркестратор синхронизации чата по ETCP
*
* При поднятии / разрыве ETCP-соединений:
* - обновляет online-статус пиров в БД и GUI
* - запускает member_sync (синхронизацию участников каналов) через merkle_sync
* - управляет кешем каналов (периодический refresh из БД, 30s)
*
* Обслуживает протокол входа в канал между joiner и inviter:
* CHANNEL_INFO_REQ → CHANNEL_INFO_RESP → CHANNEL_JOIN → WELCOME
* Все сообщения с Ed25519-верификацией.
*
* Обрабатывает invite-ссылки (chat_sync_connect_from_invite):
* - сохраняет pubkey и адреса invite-узла в БД (nodes, node_addresses)
* - создаёт TOPO_GROUP, запускает conn_mgr для подключения
* - после поднятия ETCP-соединения запускает join-протокол
*
* Использование:
* 1. chat_sync_init(inst) — вызывается при старте, биндит ETCP_RT_ID_CHAT_SYNC (0x30)
* 2. chat_sync_connect_from_invite(ch_id, node_id, pubkey, addrs, count) — вход по invite
* 3. chat_sync_join_channel(inst, ch_id, target_node_id) — вход через уже подключённый узел
* 4. chat_sync_destroy(inst) — вызывается при завершении
*/
#ifndef CHAT_SYNC_H #ifndef CHAT_SYNC_H
#define CHAT_SYNC_H #define CHAT_SYNC_H

4
src/transport_layer/etcp.c

@ -993,10 +993,10 @@ static void etcp_link_ready_callback(struct ETCP_CONN* etcp) {
} }
if (etcp->links_up==0) { if (etcp->links_up==0) {
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "switch ETCP down -> up"); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "[%s] etcp_link_ready_callback: links_up 0→1, calling etcp_on_up (initialized=%d tx_state=%d)", etcp->log_name, etcp->initialized, etcp->tx_state);
etcp->links_up=1; etcp->links_up=1;
etcp_on_up(etcp); etcp_on_up(etcp);
} else DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "skip on_up - ETCP already up"); } else DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] etcp_link_ready_callback: links_up=%d already up", etcp->log_name, etcp->links_up);
if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return; if (etcp->tx_state!=ETCP_TX_STATE_LINK_WAIT) return;

39
src/transport_layer/etcp_connections.c

@ -195,10 +195,10 @@ static void etcp_link_init_timer_cbk(void* arg) {
} }
link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, etcp_link_init_timer_cbk, "link_init"); link->init_timer = uasync_set_timeout(link->etcp->instance->ua, link->init_timeout, link, etcp_link_init_timer_cbk, "link_init");
if (link->link_state == 3 && link->initialized) return; if (link->link_state == 3 && link->initialized) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] init_timer: SUPPRESSED state=%d init=%d link_status=%d recv_ka=%d remote_ka=%d links_up=%d", link->etcp->log_name, link->link_state, link->initialized, link->link_status, link->recv_keepalive, link->remote_keepalive, link->etcp->links_up); return; }
if (link->etcp->links_up > 0) return; /* another link is already UP on this connection */ if (link->etcp->links_up > 0) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] init_timer: SUPPRESSED (links_up=%d > 0)", link->etcp->log_name, link->etcp->links_up); return; }
if (link->link_state == 1) etcp_link_send_init(link,1,0);// init (with etcp reset) if (link->etcp->got_initial_pkt == 0) etcp_link_send_init(link,1,0);
else etcp_link_send_init(link,0,0);// no etcp reset (reinit) else etcp_link_send_init(link,0,0);
} }
void etcp_link_restart_init_timer(struct ETCP_LINK* link) { void etcp_link_restart_init_timer(struct ETCP_LINK* link) {
@ -223,7 +223,8 @@ void etcp_link_enter_reinit(struct ETCP_LINK* link) {
link->link_state = 2; // reconnect link->link_state = 2; // reconnect
etcp_on_link_down(link->etcp); etcp_on_link_down(link->etcp);
if (link->is_server != 0) return; if (link->is_server != 0) return;
etcp_link_send_init(link,0,0);// init without reset etcp_conn_reinit(link->etcp);
etcp_link_send_init(link,1,0);
if (link->keepalive_timer) {// keepalive заменяяется reinit запросами if (link->keepalive_timer) {// keepalive заменяяется reinit запросами
uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer); uasync_cancel_timeout(link->etcp->instance->ua, link->keepalive_timer);
@ -323,7 +324,7 @@ static void keepalive_timer_cb(void* arg) {
link->recv_keepalive = 0; link->recv_keepalive = 0;
link->link_status = 0; link->link_status = 0;
etcp_on_link_down(link->etcp); etcp_on_link_down(link->etcp);
DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] Conn:%s Link down: link_id=%d ka=%d remote_ka=%d tmo: %d>%d", link->etcp->log_name, link->conn?link->conn->name:"???", link->local_link_id, link->recv_keepalive, link->remote_keepalive, elapsed, timeout_units); DEBUG_INFO(DEBUG_CATEGORY_KEEPALIVE, "[%s] Conn:%s Link down: link_id=%d state=%d init=%d ka=%d remote_ka=%d tmo: %llu>%llu els=%llums", link->etcp->log_name, link->conn?link->conn->name:"???", link->local_link_id, link->link_state, link->initialized, link->recv_keepalive, link->remote_keepalive, (unsigned long long)timeout_units, (unsigned long long)elapsed, (unsigned long long)(elapsed/10));
DEBUG_WARN(DEBUG_CATEGORY_KEEPALIVE, "[%s] Link %p (local_id=%d) recv status changed to DOWN - no packets for %llu ms", link->etcp->log_name, link, link->local_link_id, (unsigned long long)(elapsed/10)); DEBUG_WARN(DEBUG_CATEGORY_KEEPALIVE, "[%s] Link %p (local_id=%d) recv status changed to DOWN - no packets for %llu ms", link->etcp->log_name, link, link->local_link_id, (unsigned long long)(elapsed/10));
} }
} }
@ -1535,7 +1536,11 @@ static int handle_init_response_client(struct ETCP_SOCKET* e_sock, struct ETCP_D
link->init_timer = NULL; link->init_timer = NULL;
} }
if (pkt_code == ETCP_INIT_RESPONSE && link->etcp->got_initial_pkt) {
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] REINIT from client: INIT_RESPONSE(0x03) received, reinit conn=%p",
link->etcp->log_name, (void*)link->etcp);
etcp_conn_reinit(link->etcp);
}
if (link->etcp->initialized == 0) { if (link->etcp->initialized == 0) {
etcp_conn_ready(link->etcp); etcp_conn_ready(link->etcp);
@ -1908,13 +1913,13 @@ void etcp_connections_read_callback_socket(socket_t sock, void* arg) {
if (ml->init_timer) { uasync_cancel_timeout(conn->instance->ua, ml->init_timer); ml->init_timer = NULL; } if (ml->init_timer) { uasync_cancel_timeout(conn->instance->ua, ml->init_timer); ml->init_timer = NULL; }
} }
/* Sync session_id; reset only if new session (not when yielding to master) */ /* Sync session_id; reset if remote is clean and we're dirty, or request reset if we're clean and remote is dirty */
conn->session_id = session_id; conn->session_id = session_id;
if (!yielding && conn->got_initial_pkt) { if (req->code == ETCP_INIT_REQUEST) {
send_reset = 1; if (conn->got_initial_pkt) etcp_conn_reinit(conn);
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] REINIT existing link: code=0x%02x sess=%08x→%08x got_init=%d initialized=%d links_up=%d", send_reset = 0;
conn->log_name, code, conn->session_id, session_id, conn->got_initial_pkt, conn->initialized, conn->links_up); } else {
etcp_conn_reinit(conn); if (conn->got_initial_pkt == 0) send_reset = 1;
} }
} }
@ -1994,14 +1999,16 @@ process_decrypted:
/* restore recv_keepalive BEFORE computing link_status = remote && local */ /* restore recv_keepalive BEFORE computing link_status = remote && local */
if (link->recv_keepalive != 1) { if (link->recv_keepalive != 1) {
link->recv_keepalive = 1; link->recv_keepalive = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] Link %d status changed to UP - packet received", DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] Link %d recv_keepalive restored to 1 (was 0) link_status=%d remote_ka=%d state=%d init=%d links_up=%d",
link->etcp->log_name, link->local_link_id); link->etcp->log_name, link->local_link_id, link->link_status, link->remote_keepalive, link->link_state, link->initialized, link->etcp->links_up);
} }
int was_up = link->link_status; int was_up = link->link_status;
link->link_status = link->remote_keepalive && link->recv_keepalive; link->link_status = link->remote_keepalive && link->recv_keepalive;
if (link->link_status && !was_up && link->initialized) if (link->link_status && !was_up && link->initialized) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "[%s] Link %d status popped UP: recv_ka=%d remote_ka=%d state=%d init=%d → calling loadbalancer_link_ready", link->etcp->log_name, link->local_link_id, link->recv_keepalive, link->remote_keepalive, link->link_state, link->initialized);
loadbalancer_link_ready(link); loadbalancer_link_ready(link);
}
link->last_recv_local_time=get_time_tb(); link->last_recv_local_time=get_time_tb();
link->last_recv_timestamp=pkt->timestamp; link->last_recv_timestamp=pkt->timestamp;

2
src/transport_layer/etcp_loadbalancer.c

@ -194,7 +194,7 @@ void loadbalancer_link_ready(struct ETCP_LINK* link) {
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper"); DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
return; return;
} }
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link); DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "[%s] loadbalancer_link_ready: link=%p links_up=%d initialized=%d tx_state=%d", link->etcp->log_name, (void*)link, link->etcp->links_up, link->etcp->initialized, link->etcp->tx_state);
if (link->etcp->link_ready_for_send_fn) { if (link->etcp->link_ready_for_send_fn) {
link->etcp->link_ready_for_send_fn(link->etcp); link->etcp->link_ready_for_send_fn(link->etcp);
} else { } else {

46
tools/chatgui-android/AGENTS.md

@ -13,8 +13,11 @@ tools/chatgui-android/
├── libutun_lite/ # C-ядро (статическая библиотека) ├── libutun_lite/ # C-ядро (статическая библиотека)
│ ├── CMakeLists.txt │ ├── CMakeLists.txt
│ ├── utun_sources.cmake # список компилируемых .c файлов │ ├── utun_sources.cmake # список компилируемых .c файлов
│ ├── instance_lite.h/c # жизненный цикл (старт/стоп/рестарт, ключи)
│ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C) │ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C)
│ └── invite_link_c.h/c # invite-ссылки (encode/decode, совместимы с десктопом) │ ├── invite_link_c.h/c # invite-ссылки (encode/decode, совместимы с десктопом)
│ ├── voice_recorder.h/c # запись голосовых (PCM→Opus→канал)
│ └── attachment_sender.h/c # отправка файлов в канал
│ │
├── headless/ # CLI для Linux (тестирование без Android) ├── headless/ # CLI для Linux (тестирование без Android)
│ ├── CMakeLists.txt │ ├── CMakeLists.txt
@ -61,6 +64,11 @@ tools/chatgui-android/
└── IMPL_PLAN.md # план реализации по этапам └── IMPL_PLAN.md # план реализации по этапам
``` ```
## Chat-подсистема (src/chat/)
Все модули из `src/chat/` компилируются в `libutun_lite` через `file(GLOB_RECURSE)`.
Описания каждого модуля — см. корневой `AGENTS.md`, секция «Chat (src/chat/)».
## Технологии ## Технологии
| Слой | Язык | Фреймворк | | Слой | Язык | Фреймворк |
@ -233,22 +241,30 @@ log_udp_port = 9999
## Ключевые файлы для доработок ## Ключевые файлы для доработок
### C-слой ### C-слой
- `libutun_lite/invite_link_c.h/c` — invite-ссылки (encode/decode, бинарный формат идентичен десктопу `tools/chatgui/src/invite_link.cpp`) - `libutun_lite/instance_lite.h/c` — Жизненный цикл uTun для Android: запуск/остановка C-ядра в отдельном потоке, перезапуск при смене конфига, генерация и обновление X25519-ключей, health-check
- `jni_bridge/android_jni_bridge.h/c` — bridge API: `utun_bridge_join_channel()`, `utun_bridge_send_message()`, JNI-функции - `jni_bridge/android_jni_bridge.h/c` — JNI-прослойка Kotlin↔C: все операции из UI (отправка сообщений, вход в каналы, голосовые, статус, настройки) и обратные вызовы (логи, события). Здесь же — JNI-функции, компилируемые только для Android
- `libutun_lite/utun_config_api.h/c` — конфиг-провайдер (Kotlin → C через коллбэки) - `libutun_lite/invite_link_c.h/c` — Кодирование и декодирование invite-ссылок `utun://` в бинарный формат. Совместим с десктопной версией
- `libutun_lite/utun_sources.cmake` — список всех .c файлов, компилируемых в libutun_lite - `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
- `libutun_lite/attachment_sender.h/c` — Отправка файлов в канал: копирование в media-директорию и регистрация через chat_core (media_index → db_sync)
- `libutun_lite/utun_sources.cmake` — Список всех .c файлов, компилируемых в libutun_lite. Новые файлы добавлять сюда
### Kotlin-слой ### Kotlin-слой
- `data/InviteLink.kt` — парсинг `utun://base64blob`, `deriveNodeId()` (SHA256 от pubkey), `serializeAddrs()` - `data/NativeLib.kt` — Kotlin-обёртка над C-библиотекой: все вызовы из Kotlin транслируются в JNI-функции
- `data/NativeLib.kt` — JNI-обёртка, все `external fun native*` - `data/ChatRepository.kt` — Хранилище данных: буферизация сообщений и каналов между C-ядром и UI через StateFlow
- `ui/screens/JoinChannelDialog.kt` — диалог: ввод/вставка ссылки, debounce-декодинг, preview, Connect - `data/InviteLink.kt` — Разбор invite-ссылок: извлекает ID канала, публичный ключ, адреса для подключения
- `ui/screens/QrScanScreen.kt` — CameraX + ML Kit сканер, фильтрует `utun://` ссылки - `data/ConfigProvider.kt` — Поставщик конфигурации из Android DataStore в C-ядро через callback-интерфейс
- `ui/screens/ChannelListScreen.kt` — список каналов, кнопка Join (QrCode), кнопка Create - `data/LogManager.kt` — Сбор и хранение логов из C-ядра через log-callback
- `MainActivity.kt` — навигация экранов, QrScan → JoinDialog flow - `viewmodel/ChatViewModel.kt` — ViewModel: StateFlow-состояние для UI (список каналов, сообщения, статус)
- `ui/screens/ChannelListScreen.kt` — Главный экран: список каналов, кнопки Join (по invite-ссылке) и Create
### Headless - `ui/screens/ChatScreen.kt` — Экран чата: список сообщений + поле ввода
- `headless/headless_control.c` — управляющий TCP-сокет, команды: `join`, `connect`, `subscribe`, `debug_level` - `ui/screens/JoinChannelDialog.kt` — Диалог подключения к каналу: ввод/вставка invite-ссылки, предпросмотр, кнопка Connect
- `headless/headless_main.c` — точка входа, парсинг аргументов, uasync event loop - `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`) и асинхронные события
## Invite-ссылки (механика подключения к каналу) ## Invite-ссылки (механика подключения к каналу)

Loading…
Cancel
Save