18 KiB
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 или 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/:
sdk.dir=/home/user/Android/Sdk
Путь ndk.dir можно не указывать — Gradle скачает NDK сам (версия из ndkVersion в app/build.gradle.kts).
Если NDK уже установлен отдельно:
ndk.dir=/home/user/Android/Sdk/ndk/29.0.14206865
Установка Android SDK (без Android Studio)
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 <SN> # clean + сборка + прошивка на конкретный девайс
APK (Android)
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:
cd tools/chatgui-android && mkdir -p build && cd build
cmake .. && make -j4
./headless/headless
Прошивка на телефон
# Проверить доступные устройства
adb devices
# Установить APK
adb -s <serial> 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():
[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=<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):
tools/logreceiver/logreceiver_restart.sh
Скрипт ротирует логи (история 10 запусков: .log → .log.1 → … → .log.10),
убивает старый демон и запускает новый на 0.0.0.0:9999, вывод в logreceiver_output.log.
При каждом запуске логи фиксируются в истории и начинается свежий лог.
В INI-конфиге Android (генерируется ConfigProvider.buildConfigText):
[log_udp]
ip=<ip хоста>
port=9999
Ключевые файлы для доработок
C-слой
libutun_lite/instance_lite.h/c— Жизненный цикл uTun для Android: запуск/остановка C-ядра в отдельном потоке, перезапуск при смене конфига, генерация и обновление X25519-ключей, health-checkjni_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_coresrc/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 через StateFlowdata/InviteLink.kt— Разбор invite-ссылок: извлекает ID канала, публичный ключ, адреса для подключенияdata/ConfigProvider.kt— Настройки DataStore и генерация INI-текста для запуска C-ядраdata/LogManager.kt— Сбор и хранение логов из C-ядра через log-callbackviewmodel/ChatViewModel.kt— ViewModel: StateFlow-состояние для UI (список каналов, сообщения, статус)ui/screens/ChannelListScreen.kt— Главный экран: список каналов, кнопки Join (по invite-ссылке) и Createui/screens/ChatScreen.kt— Экран чата: список сообщений + поле вводаui/screens/JoinChannelDialog.kt— Диалог подключения к каналу: ввод/вставка invite-ссылки, предпросмотр, кнопка Connectui/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 → KotlinLogManager - Основной запуск: Kotlin передаёт INI-текст через
nativeStart(configText); парсинг выполняет общийconfig_parser - Перед коммитом: головная C-сборка +
./gradlew assembleDebug