# AGENTS.md — chatgui-android (Android P2P Chat) 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`. `instance_lite` создаёт ядро и вызывает `utun_core_start()` + `chat_service_start()` в отдельном uasync-потоке. UTUN-сервис не запускается. При stop ядро освобождает чат и общие ресурсы; SQLite хранится в `db_path/chats.db`. Владение ресурсами и порядок остановки: `../../doc/service_lifecycle.md`. ## Состав проекта (5 частей) ``` tools/chatgui-android/ ├── libutun_lite/ # C-ядро (статическая библиотека) │ ├── CMakeLists.txt │ ├── utun_sources.cmake # GLOB исходников, исключения и файлы обёртки │ ├── instance_lite.h/c # жизненный цикл (старт/стоп/рестарт, ключи) │ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C) │ ├── 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 # отдельный каркас control-loop; сам ядро не запускает │ └── headless_control.c/h # управляющий TCP-сокет (JSON/text протокол) │ ├── jni_bridge/ # JNI прослойка C ↔ Kotlin │ └── android_jni_bridge.c/h # C API + JNI-функции (компилятся только для Android) │ ├── app/ # Android приложение (Gradle + NDK + Compose) │ ├── build.gradle.kts │ └── src/main/ │ ├── AndroidManifest.xml │ ├── cpp/ # NDK: jni_bridge + libutun_lite → libutun.so │ │ └── CMakeLists.txt │ ├── java/com/utun/chat/ │ │ ├── ChatApplication.kt # Application, инициализация │ │ ├── MainActivity.kt # Compose UI, навигация экранов │ │ ├── data/ │ │ │ ├── NativeLib.kt # JNI-обёртка │ │ │ ├── ChatRepository.kt # репозиторий (JNI + JSON) │ │ │ ├── InviteLink.kt # парсер utun://base64blob │ │ │ ├── ConfigProvider.kt # конфиг из DataStore │ │ │ ├── LogManager.kt # лог-менеджер │ │ │ └── ServerEntry.kt # модели данных │ │ ├── viewmodel/ │ │ │ └── ChatViewModel.kt # StateFlow для UI │ │ ├── ui/screens/ │ │ │ ├── ChannelListScreen.kt # список каналов + join/create │ │ │ ├── ChatScreen.kt # сообщения + ввод │ │ │ ├── JoinChannelDialog.kt # диалог подключения по invite │ │ │ ├── QrScanScreen.kt # CameraX + ML Kit QR-сканер │ │ │ ├── SettingsScreen.kt # настройки │ │ │ └── LogScreen.kt # лог-просмотр │ │ ├── ui/components/ │ │ │ └── MessageBubble.kt # бабблы сообщений + InputBar │ │ └── headless/ │ │ └── HeadlessService.kt # фоновый сервис │ └── res/ │ └── doc/ # документация ├── ARCHITECTURE.md # подробная архитектура └── IMPL_PLAN.md # план реализации по этапам ``` Этот `AGENTS.md` лежит в корне `tools/chatgui-android/`. Старые планы в `doc/` сверять с текущим кодом. ## Chat-подсистема (src/chat/) Все модули из `src/chat/` компилируются в `libutun_lite` через `file(GLOB_RECURSE)`. Описания каждого модуля — см. корневой `AGENTS.md`, секция «Chat (src/chat/)». ## Технологии | Слой | Язык | Фреймворк | |------|------|-----------| | C-ядро | C99 | OpenSSL, SQLite3 | | Bridge | C + JNI | Android NDK 29 | | Android UI | Kotlin | Jetpack Compose, Material3, CameraX, ML Kit | | Сборка C | CMake 3.16+ | GCC (Linux) / Clang (Android NDK) | | Сборка APK | Gradle 8.x | AGP 8.x, Kotlin 1.9 | ## Зависимости ### Для сборки APK (Android) | Компонент | Версия | Установка | |-----------|--------|-----------| | JDK | 17+ | `apt install openjdk-17-jdk` | | CMake | 3.22+ | `apt install cmake` | | Android SDK | API 36 | через [sdkmanager](https://developer.android.com/studio#command-line-tools-only) или Android Studio | | Android NDK | 29.0.14206865 | `sdkmanager "ndk;29.0.14206865"` | | Gradle | 8.5 (wrapper) | `./gradlew` скачает сам | | Kotlin | 1.9.20 | встроен в Gradle плагин | | adb | platform-tools | `apt install adb` (для прошивки) | OpenSSL для APK **предсобран** под `arm64-v8a` и `x86_64`, лежит в `app/src/main/cpp/openssl/`. Собирать отдельно не нужно. ### Для Headless (Linux, отладка без телефона) | Компонент | Установка | |-----------|-----------| | CMake 3.16+ | `apt install cmake` | | GCC / Clang | `apt install build-essential` | | OpenSSL dev | `apt install libssl-dev` | | pthread | встроен в libc | ### Настройка окружения Создать `local.properties` в корне `tools/chatgui-android/`: ```properties sdk.dir=/home/user/Android/Sdk ``` Путь `ndk.dir` можно не указывать — Gradle скачает NDK сам (версия из `ndkVersion` в `app/build.gradle.kts`). Если NDK уже установлен отдельно: ```properties ndk.dir=/home/user/Android/Sdk/ndk/29.0.14206865 ``` ### Установка Android SDK (без Android Studio) ```bash cd /tmp && wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip unzip commandlinetools-linux-*.zip -d ~/Android mkdir -p ~/Android/Sdk/cmdline-tools mv ~/Android/cmdline-tools ~/Android/Sdk/cmdline-tools/latest export ANDROID_HOME=~/Android/Sdk export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH yes | sdkmanager --licenses sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;29.0.14206865" ``` ## Сборка ``` ./build.sh # clean + сборка + установка на все подключённые устройства ./build.sh noinstall # только сборка, без прошивки ./build.sh # clean + сборка + прошивка на конкретный девайс ``` ### APK (Android) ```bash cd tools/chatgui-android export ANDROID_HOME=/home/user/Android/Sdk ./gradlew assembleDebug # APK: app/build/outputs/apk/debug/app-debug.apk ``` ### 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 ./headless/headless ``` ## Прошивка на телефон ```bash # Проверить доступные устройства adb devices # Установить APK adb -s install -r app/build/outputs/apk/debug/app-debug.apk # Смотреть логи adb logcat -s utun:* # Control-сокет (если его запуск включён в конфигурации приложения): adb forward tcp:9999 tcp:9999 nc localhost 9999 > {"id":1,"cmd":"status"} > {"id":2,"cmd":"connect","link":"utun://..."} ``` ## Конфиг Конфиг хранится в Android DataStore (`ConfigProvider.kt`) и передаётся в C-ядро при старте как INI-текст через `nativeStart(configText)`. Сокращённый пример INI; текущие поля и значения генератора — в `ConfigProvider.buildConfigText()`: ```ini [global] my_node_name=MyDevice 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:<имя>] генерируются из настроенных интерфейсов. [allowed_keys] allow_all=yes [ntp] enabled=no [chat] storage_autoload=1 storage_autoload_maxsize_mb=10 storage_maxsize_gb=1 opus_codec_preset=1 compressor_enabled=0 compressor_max_gain_db=25 compressor_rise_rate=10 media_download_max_peers=3 [debug] chat=info chat_sync=info # Опционально — UDP-лог [log_udp] ip= port=9999 ``` Ключи (`my_public_key`, `my_private_key`) генерируются автоматически при первом запуске и сохраняются в `keys.conf` в `db_path`. Имя узла (`my_node_name`) берётся из настроек DataStore или `Build.MODEL`. ## Логирование через logreceiver (только Android) Debug-сообщения с телефона дублируются через UDP на приёмник `tools/logreceiver/`. Запуск (всегда через скрипт, до старта utun): ```bash tools/logreceiver/logreceiver_restart.sh ``` Скрипт ротирует логи (история 10 запусков: `.log` → `.log.1` → … → `.log.10`), убивает старый демон и запускает новый на `0.0.0.0:9999`, вывод в `logreceiver_output.log`. При каждом запуске логи фиксируются в истории и начинается свежий лог. В INI-конфиге Android (генерируется `ConfigProvider.buildConfigText`): ```ini [log_udp] ip= 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 - `../../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` — Общий список источников: `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` — Настройки DataStore и генерация INI-текста для запуска C-ядра - `data/LogManager.kt` — Сбор и хранение логов из C-ядра через log-callback - `viewmodel/ChatViewModel.kt` — ViewModel: StateFlow-состояние для UI (список каналов, сообщения, статус) - `ui/screens/ChannelListScreen.kt` — Главный экран: список каналов, кнопки Join (по invite-ссылке) и Create - `ui/screens/ChatScreen.kt` — Экран чата: список сообщений + поле ввода - `ui/screens/JoinChannelDialog.kt` — Диалог подключения к каналу: ввод/вставка invite-ссылки, предпросмотр, кнопка Connect - `ui/screens/QrScanScreen.kt` — QR-сканер для invite-ссылок (CameraX + ML Kit) - `MainActivity.kt` — Точка входа Android-приложения: навигация между экранами, QrScan → JoinDialog flow ### Headless - `headless/headless_main.c` — Отдельный select/poll-цикл управления; запуск ядра в этом main не реализован - `headless/headless_control.c/h` — Каркас TCP-управления. Рабочий headless API общего чата находится в `../../src/chat/chat_headless_control.c/h` ## Invite-ссылки (механика подключения к каналу) Создаваемые ссылки имеют версию `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()`. Ссылка готова после подтверждённой регистрации 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`; файлы обёртки добавить в `_cfg_src` - Все новые Kotlin-файлы — Compose, Material3, coroutines/StateFlow - ViewModel не держит ссылки на Context - Логи в C — через `debug_set_log_hook` → bridge → Kotlin `LogManager` - Основной запуск: Kotlin передаёт INI-текст через `nativeStart(configText)`; парсинг выполняет общий `config_parser` - Перед коммитом: головная C-сборка + `./gradlew assembleDebug`